summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJasem Mutlaq <mutlaqja@ikarustech.com>2016-10-16 10:50:27 (GMT)
committerJasem Mutlaq <mutlaqja@ikarustech.com>2016-10-16 10:50:27 (GMT)
commit46999cdea88435870d1af6401b3371b477f9ec3c (patch)
tree95d58dd8bfc408186bf8206a500e2b126462087e
parent97732ecbe56a8e60e08d85584099022068ff5cfe (diff)
Starting on canny based single star detector
-rw-r--r--kstars/ekos/align/onlineastrometryparser.cpp2
-rw-r--r--kstars/fitsviewer/fitsdata.cpp299
-rw-r--r--kstars/fitsviewer/fitsdata.h61
-rw-r--r--kstars/fitsviewer/fitshistogram.cpp2
-rw-r--r--kstars/fitsviewer/fitsview.cpp27
-rw-r--r--kstars/fitsviewer/fitsview.h3
6 files changed, 323 insertions, 71 deletions
diff --git a/kstars/ekos/align/onlineastrometryparser.cpp b/kstars/ekos/align/onlineastrometryparser.cpp
index 8fd2b61..e0efe89 100644
--- a/kstars/ekos/align/onlineastrometryparser.cpp
+++ b/kstars/ekos/align/onlineastrometryparser.cpp
@@ -102,7 +102,7 @@ bool OnlineAstrometryParser::startSovler(const QString &in_filename, const QStri
rc = image_data->loadFITS(in_filename);
if (rc)
{
- double image_width,image_height;
+ uint16_t image_width,image_height;
double bscale, bzero;
double min, max;
int val;
diff --git a/kstars/fitsviewer/fitsdata.cpp b/kstars/fitsviewer/fitsdata.cpp
index 629d52f..ee52186 100644
--- a/kstars/fitsviewer/fitsdata.cpp
+++ b/kstars/fitsviewer/fitsdata.cpp
@@ -73,13 +73,16 @@ FITSData::FITSData(FITSMode fitsMode)
wcs_coord = NULL;
fptr = NULL;
histogram = NULL;
- maxHFRStar = NULL;
- darkFrame = NULL;
+ maxHFRStar = NULL;
tempFile = false;
starsSearched = false;
HasWCS = false;
HasDebayer=false;
mode = fitsMode;
+ channels=1;
+
+ stats.bitpix=8;
+ stats.ndim = 2;
debayerParams.method = DC1394_BAYER_METHOD_NEAREST;
debayerParams.filter = DC1394_COLOR_FILTER_RGGB;
@@ -242,11 +245,7 @@ bool FITSData::loadFITS (const QString &inFilename, bool silent)
return false;
}
- if (darkFrame != NULL)
- subtract(darkFrame);
-
- if (darkFrame == NULL)
- calculateStats();
+ calculateStats();
if (mode == FITS_NORMAL)
checkWCS();
@@ -417,7 +416,7 @@ int FITSData::calculateMinMax(bool refresh)
status = 0;
- if (refresh == false)
+ if (fptr && refresh == false)
{
if (fits_read_key_dbl(fptr, "DATAMIN", &(stats.min[0]), NULL, &status) ==0)
nfound++;
@@ -557,6 +556,179 @@ bool FITSData::checkCollision(Edge* s1, Edge*s2)
return false;
}
+int FITSData::findCannyStar(FITSData *data, const QRectF &boundary)
+{
+ int subX = boundary.isNull() ? 0 : boundary.x();
+ int subY = boundary.isNull() ? 0 : boundary.y();
+ int subW = subX + (boundary.isNull() ? data->getWidth() : boundary.width());
+ int subH = subY + (boundary.isNull() ? data->getHeight(): boundary.height());
+
+ // #1 Find offsets
+ uint32_t size = subW * subH;
+ uint32_t offset = subX + subY * subW;
+
+ // #2 Create new buffer
+ float *buffer = new float[size];
+ memcpy(buffer, data->getImageBuffer() + offset, size);
+
+ // #3 Create new FITSData to hold it
+ FITSData *boundedImage = new FITSData();
+ boundedImage->stats.width = subW;
+ boundedImage->stats.height = subH;
+ boundedImage->stats.bitpix = data->stats.bitpix;
+ boundedImage->stats.samples_per_channel = size;
+ boundedImage->stats.ndim = 2;
+
+ // #4 Set image buffer and calculate stats.
+ boundedImage->setImageBuffer(buffer);
+
+ boundedImage->calculateStats(true);
+
+ // #5 Apply Median + High Contrast filter to remove noise and move data to non-linear domain
+ boundedImage->applyFilter(FITS_MEDIAN);
+ boundedImage->applyFilter(FITS_HIGH_CONTRAST);
+
+ // #6 Perform Sobel to find gradients and their directions
+ QVector<float> gradients;
+ QVector<float> directions;
+ boundedImage->sobel(gradients, directions);
+
+ // #7 Calculate center of mass
+ float massX=0, massY=0, totalMass=0;
+
+ for (int y=subY; y < subH; y++)
+ {
+ for (int x=subX; x < subW; x++)
+ {
+ float pixel = gradients[x+y*subW];
+
+ totalMass += pixel;
+ massX += x * pixel;
+ massY += y * pixel;
+ }
+ }
+
+ qDebug() << "Weighted Center is X: " << massX/totalMass << " Y: " << massY/totalMass;
+
+ return 1;
+
+#if 0
+
+ Edge *center = new Edge;
+ center->width = -1;
+ center->x = massX/totalMass + 0.5;
+ center->y = massY/totalMass + 0.5;
+ center->HFR = 1;
+
+ // Maximum Radius
+ int maxR = qMin(subW-1, subH-1) / 2;
+
+ // Critical threshold
+ double critical_threshold = threshold * 0.7;
+ double running_threshold = threshold;
+
+ while (running_threshold >= critical_threshold)
+ {
+ for (int r=maxR; r > 1; r--)
+ {
+ int pass=0;
+
+ for (float theta=0; theta < 2*M_PI; theta += (2*M_PI)/10.0)
+ {
+ int testX = center->x + cos(theta) * r;
+ int testY = center->y + sin(theta) * r;
+
+ // if out of bound, break;
+ if (testX < subX || testX > subW || testY < subY || testY > subH)
+ break;
+
+ if (image_buffer[testX + testY * stats.width] > running_threshold)
+ pass++;
+ }
+
+ //qDebug() << "Testing for radius " << r << " passes # " << pass << " @ threshold " << running_threshold;
+ //if (pass >= 6)
+ if (pass >= 5)
+ {
+ center->width = r*2;
+ break;
+ }
+ }
+
+ if (center->width > 0)
+ break;
+
+ // Increase threshold fuzziness by 10%
+ running_threshold -= running_threshold * 0.1;
+ }
+
+ // If no stars were detected
+ if (center->width == -1)
+ return 0;
+
+ // 30% fuzzy
+ //center->width += center->width*0.3 * (running_threshold / threshold);
+
+ starCenters.append(center);
+
+ double FSum=0, HF=0, TF=0, min = stats.min[0];
+ const double resolution = 1.0/20.0;
+
+ int cen_y = round(center->y);
+
+ double rightEdge = center->x + center->width / 2.0;
+ double leftEdge = center->x - center->width / 2.0;
+
+ QVector<double> subPixels;
+ subPixels.reserve(center->width / resolution);
+
+ for (double x=leftEdge; x <= rightEdge; x += resolution)
+ {
+ //subPixels[x] = resolution * (image_buffer[static_cast<int>(floor(x)) + cen_y * stats.width] - min);
+ double slice = resolution * (image_buffer[static_cast<int>(floor(x)) + cen_y * stats.width] - min);
+ FSum += slice;
+ subPixels.append(slice);
+ }
+
+ // Half flux
+ HF = FSum / 2.0;
+
+ //double subPixelCenter = center->x - fmod(center->x,resolution);
+ int subPixelCenter = (center->width / resolution) / 2;
+
+ // Start from center
+ TF = subPixels[subPixelCenter];
+ double lastTF = TF;
+ // Integrate flux along radius axis until we reach half flux
+ //for (double k=resolution; k < (center->width/(2*resolution)); k += resolution)
+ for (int k=1; k < subPixelCenter; k ++)
+ {
+ TF += subPixels[subPixelCenter+k];
+ TF += subPixels[subPixelCenter-k];
+
+ if (TF >= HF)
+ {
+ // We have two ways to calculate HFR. The first is the correct method but it can get quite variable within 10% due to random fluctuations of the measured star.
+ // The second method is not truly HFR but is much more resistant to noise.
+
+
+ // #1 Approximate HFR, accurate and reliable but quite variable to small changes in star flux
+ center->HFR = (k - 1 + ( (HF-lastTF)/(TF-lastTF) ) ) * resolution;
+
+ // #2 Not exactly HFR, but much more stable
+ //center->HFR = (k*resolution) * (HF/TF);
+ break;
+ }
+
+ lastTF = TF;
+ }
+
+ return starCenters.size();
+
+#endif
+
+}
+
int FITSData::findOneStar(const QRectF &boundary)
{
int subX = boundary.x();
@@ -1429,18 +1601,6 @@ void FITSData::applyFilter(FITSScale type, float *image, float min, float max)
}
-void FITSData::subtract(float *dark_buffer)
-{
- for (int i=0; i < stats.width*stats.height; i++)
- {
- image_buffer[i] -= dark_buffer[i];
- if (image_buffer[i] < 0)
- image_buffer[i] = 0;
- }
-
- calculateStats(true);
-}
-
int FITSData::findStars(const QRectF &boundary, bool force)
{
//if (histogram == NULL)
@@ -1556,15 +1716,6 @@ void FITSData::checkWCS()
#endif
}
-float *FITSData::getDarkFrame() const
-{
- return darkFrame;
-}
-
-void FITSData::setDarkFrame(float *value)
-{
- darkFrame = value;
-}
int FITSData::getFlipVCounter() const
{
@@ -2309,6 +2460,7 @@ double FITSData::getADU()
* Web-Site: http://github.com/hipersayanX/CannyDetector
*/
+#if 0
void FITSData::sobel(const QImage &image, QVector<int> &gradient, QVector<int> &direction)
{
int size = image.width() * image.height();
@@ -2390,7 +2542,94 @@ void FITSData::sobel(const QImage &image, QVector<int> &gradient, QVector<int> &
}
}
}
+#endif
+
+void FITSData::sobel(QVector<float> &gradient, QVector<float> &direction)
+{
+ //int size = image.width() * image.height();
+ gradient.resize(stats.samples_per_channel);
+ direction.resize(stats.samples_per_channel);
+
+ for (int y = 0; y < stats.height; y++)
+ {
+ size_t yOffset = y * stats.width;
+ const float *grayLine = image_buffer + yOffset;
+
+ const float *grayLine_m1 = y < 1? grayLine: grayLine - stats.width;
+ const float *grayLine_p1 = y >= stats.height - 1? grayLine: grayLine + stats.width;
+
+ float *gradientLine = gradient.data() + yOffset;
+ float *directionLine = direction.data() + yOffset;
+
+ for (int x = 0; x < stats.width; x++)
+ {
+ int x_m1 = x < 1? x: x - 1;
+ int x_p1 = x >= stats.width - 1? x: x + 1;
+
+ int gradX = grayLine_m1[x_p1]
+ + 2 * grayLine[x_p1]
+ + grayLine_p1[x_p1]
+ - grayLine_m1[x_m1]
+ - 2 * grayLine[x_m1]
+ - grayLine_p1[x_m1];
+
+ int gradY = grayLine_m1[x_m1]
+ + 2 * grayLine_m1[x]
+ + grayLine_m1[x_p1]
+ - grayLine_p1[x_m1]
+ - 2 * grayLine_p1[x]
+ - grayLine_p1[x_p1];
+
+ gradientLine[x] = qAbs(gradX) + qAbs(gradY);
+
+ /* Gradient directions are classified in 4 possible cases
+ *
+ * dir 0
+ *
+ * x x x
+ * - - -
+ * x x x
+ *
+ * dir 1
+ *
+ * x x /
+ * x / x
+ * / x x
+ *
+ * dir 2
+ *
+ * \ x x
+ * x \ x
+ * x x \
+ *
+ * dir 3
+ *
+ * x | x
+ * x | x
+ * x | x
+ */
+ if (gradX == 0 && gradY == 0)
+ directionLine[x] = 0;
+ else if (gradX == 0)
+ directionLine[x] = 3;
+ else
+ {
+ qreal a = 180. * atan(qreal(gradY) / gradX) / M_PI;
+
+ if (a >= -22.5 && a < 22.5)
+ directionLine[x] = 0;
+ else if (a >= 22.5 && a < 67.5)
+ directionLine[x] = 2;
+ else if (a >= -67.5 && a < -22.5)
+ directionLine[x] = 1;
+ else
+ directionLine[x] = 3;
+ }
+ }
+ }
+}
+#if 0
QVector<int> FITSData::thinning(int width, int height, const QVector<int> &gradient, const QVector<int> &direction)
{
QVector<int> thinned(gradient.size());
@@ -2519,3 +2758,5 @@ QVector<int> FITSData::hysteresis(int width, int height, const QVector<int> &ima
return canny;
}
+
+#endif
diff --git a/kstars/fitsviewer/fitsdata.h b/kstars/fitsviewer/fitsdata.h
index e5cff85..798c8e9 100644
--- a/kstars/fitsviewer/fitsdata.h
+++ b/kstars/fitsviewer/fitsdata.h
@@ -90,10 +90,6 @@ public:
void runningAverageStdDev();
// Access functions
- //double getValue(float *buffer, int i);
- //void setValue(float *buffer, int i, double value);
- //double getValue(int i);
- //void setValue(int i, float value);
void clearImageBuffers();
void setImageBuffer(float *buffer);
float * getImageBuffer();
@@ -101,11 +97,11 @@ public:
// Stats
int getDataType() { return data_type; }
unsigned int getSize() { return stats.samples_per_channel; }
- void getDimensions(double *w, double *h) { *w = stats.width; *h = stats.height; }
- void setWidth(long w) { stats.width = w;}
- void setHeight(long h) { stats.height = h;}
- long getWidth() { return stats.width; }
- long getHeight() { return stats.height; }
+ void getDimensions(uint16_t *w, uint16_t *h) { *w = stats.width; *h = stats.height; }
+ void setWidth(uint16_t w) { stats.width = w; stats.samples_per_channel = stats.width * stats.height;}
+ void setHeight(uint16_t h) { stats.height = h; stats.samples_per_channel = stats.width * stats.height;}
+ uint16_t getWidth() { return stats.width; }
+ uint16_t getHeight() { return stats.height; }
// Statistics
int getNumOfChannels() { return channels;}
@@ -135,6 +131,9 @@ public:
void getCenterSelection(int *x, int *y);
int findOneStar(const QRectF &boundary);
+ // Find single star based on partially customized Canny edge detection
+ static int findCannyStar(FITSData *data, const QRectF &boundary = QRectF());
+
// Half Flux Radius
Edge * getMaxHFRStar() { return maxHFRStar;}
double getHFR(HFRType type=HFR_AVERAGE);
@@ -179,26 +178,6 @@ public:
int getFlipVCounter() const;
void setFlipVCounter(int value);
- // Dark frame
- float *getDarkFrame() const;
- void setDarkFrame(float *value);
- void subtract(float *darkFrame);
-
- /* stats struct to hold statisical data about the FITS data */
- struct
- {
- double min[3], max[3];
- double mean[3];
- double stddev[3];
- double median[3];
- double SNR;
- int bitpix;
- int ndim;
- uint32_t samples_per_channel;
- uint16_t width;
- uint16_t height;
- } stats;
-
private:
bool rotFITS (int rotate, int mirror);
@@ -210,19 +189,20 @@ private:
void readWCSKeys();
// Canny Edge detector by Gonzalo Exequiel Pedone
- void sobel(const QImage &image, QVector<int> &gradient, QVector<int> &direction);
+ void sobel(QVector<float> &gradient, QVector<float> &direction);
+ #if 0
QVector<int> thinning(int width, int height, const QVector<int> &gradient, const QVector<int> &direction);
QVector<int> threshold(int thLow, int thHi, const QVector<int> &image);
void trace(int width, int height, QVector<int> &image, int x, int y);
QVector<int> hysteresis(int width, int height, const QVector<int> &image);
+ #endif
FITSHistogram *histogram; // Pointer to the FITS data histogram
fitsfile* fptr; // Pointer to CFITSIO FITS file struct
- int data_type; // FITS image data type
+ int data_type; // FITS image data type (TBYTE, TUSHORT, TINT, TFLOAT, TLONGLONG, TDOUBLE)
int channels; // Number of channels
- float *image_buffer; // Current image buffer
- float *darkFrame; // Optional dark frame pointer
+ float *image_buffer; // Current image buffer
bool tempFile; // Is this a tempoprary file or one loaded from disk?
@@ -245,6 +225,21 @@ private:
float *bayer_buffer; // Bayer buffer
BayerParams debayerParams; // Bayer parameters
+ /* stats struct to hold statisical data about the FITS data */
+ struct
+ {
+ double min[3], max[3];
+ double mean[3];
+ double stddev[3];
+ double median[3];
+ double SNR;
+ int bitpix;
+ int ndim;
+ uint32_t samples_per_channel;
+ uint16_t width;
+ uint16_t height;
+ } stats;
+
};
#endif
diff --git a/kstars/fitsviewer/fitshistogram.cpp b/kstars/fitsviewer/fitshistogram.cpp
index 23973c6..9aec501 100644
--- a/kstars/fitsviewer/fitshistogram.cpp
+++ b/kstars/fitsviewer/fitshistogram.cpp
@@ -100,7 +100,7 @@ FITSHistogram::~FITSHistogram()
void FITSHistogram::constructHistogram()
{
- double fits_w=0, fits_h=0;
+ uint16_t fits_w=0, fits_h=0;
FITSData *image_data = tab->getView()->getImageData();
float *buffer = image_data->getImageBuffer();
diff --git a/kstars/fitsviewer/fitsview.cpp b/kstars/fitsviewer/fitsview.cpp
index 38e0b12..53070c8 100644
--- a/kstars/fitsviewer/fitsview.cpp
+++ b/kstars/fitsviewer/fitsview.cpp
@@ -90,13 +90,21 @@ This method looks at what mouse mode is currently selected and updates the curso
*/
void FITSView::updateMouseCursor(){
- if(mouseMode==dragMouse)
- viewport()->setCursor(Qt::OpenHandCursor);
+ if(mouseMode==dragMouse){
+ if(horizontalScrollBar()->maximum()>0||verticalScrollBar()->maximum()>0){
+ if(!image_frame->getMouseButtonDown())
+ viewport()->setCursor(Qt::PointingHandCursor);
+ else
+ viewport()->setCursor(Qt::ClosedHandCursor);
+ }
+ else
+ viewport()->setCursor(Qt::CrossCursor);
+ }
if(mouseMode==selectMouse){
viewport()->setCursor(Qt::CrossCursor);
}
if(mouseMode==scopeMouse){
- QPixmap scope_pix=QPixmap(":/icons/32-apps-kstars.png");
+ QPixmap scope_pix=QPixmap(":/icons/breeze/default/kstars_telescope.svg").scaled(32,32,Qt::KeepAspectRatio,Qt::FastTransformation);
viewport()->setCursor(QCursor(scope_pix,0,0));
}
}
@@ -116,6 +124,10 @@ void FITSView::setMouseMode(int mode){
}
}
+bool FITSLabel::getMouseButtonDown(){
+ return mouseButtonDown;
+}
+
int FITSView::getMouseMode(){
return mouseMode;
}
@@ -127,7 +139,7 @@ If the mouse button is released, it resets mouseButtonDown variable and the mous
void FITSLabel::mouseReleaseEvent(QMouseEvent *e){
if(image->getMouseMode()==FITSView::dragMouse){
mouseButtonDown=false;
- image->viewport()->setCursor(Qt::OpenHandCursor);
+ image->updateMouseCursor();
}
}
/**
@@ -213,7 +225,7 @@ void FITSLabel::mousePressEvent(QMouseEvent *e)
{
mouseButtonDown=true;
lastMousePoint=e->globalPos();
- image->viewport()->setCursor(Qt::ClosedHandCursor);
+ image->updateMouseCursor();
} else if(image->getMouseMode()==FITSView::scopeMouse)
{
#ifdef HAVE_INDI
@@ -1204,6 +1216,7 @@ void FITSView::toggleStars(bool enable)
qApp->processEvents();
int count = -1;
+ #if 0
if (trackingBoxEnabled)
{
count = image_data->findStars(trackingBox, trackingBoxUpdated);
@@ -1211,10 +1224,11 @@ void FITSView::toggleStars(bool enable)
}
else
count = image_data->findStars();
-
+ #endif
//QRectF boundary(0,0, image_data->getWidth(), image_data->getHeight());
//count = image_data->findOneStar(boundary);
+ count = FITSData::findCannyStar(image_data);
if (count >= 0 && isVisible())
emit newStatus(i18np("1 star detected.", "%1 stars detected.", count), FITS_MESSAGE);
@@ -1293,6 +1307,7 @@ void FITSView::cleanUpZoom(QPoint viewCenter)
y0 = viewCenter.y() * scale;
}
ensureVisible(x0,y0, width()/2 , height()/2);
+ updateMouseCursor();
}
/**
diff --git a/kstars/fitsviewer/fitsview.h b/kstars/fitsviewer/fitsview.h
index eb00c65..0697611 100644
--- a/kstars/fitsviewer/fitsview.h
+++ b/kstars/fitsviewer/fitsview.h
@@ -63,6 +63,7 @@ public:
virtual ~FITSLabel();
void setSize(double w, double h);
void centerTelescope(double raJ2000, double decJ2000);
+ bool getMouseButtonDown();
protected:
virtual void mouseMoveEvent(QMouseEvent *e);
@@ -185,7 +186,7 @@ private:
FITSData *image_data;
int image_width, image_height;
- double currentWidth,currentHeight; /* Current width and height due to zoom */
+ uint16_t currentWidth,currentHeight; /* Current width and height due to zoom */
const double zoomFactor; /* Image zoom factor */
double currentZoom; /* Current Zoom level */