LOST  0.0.1
LOST: Open-source Star Tracker
centroiders.cpp
Go to the documentation of this file.
1 #include "centroiders.hpp"
2 
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 
7 #include <cmath>
8 #include <vector>
9 #include <iostream>
10 #include <unordered_map>
11 #include <unordered_set>
12 
13 #include "decimal.hpp"
14 
15 namespace lost {
16 
17 // DUMMY
18 
19 std::vector<Star> DummyCentroidAlgorithm::Go(unsigned char *, int imageWidth, int imageHeight) const {
20  std::vector<Star> result;
21 
22  unsigned int randomSeed = 123456;
23  for (int i = 0; i < numStars; i++) {
24  result.push_back(Star(rand_r(&randomSeed) % imageWidth, rand_r(&randomSeed) % imageHeight, DECIMAL(10.0)));
25  }
26 
27  return result;
28 }
29 
30 // a poorly designed thresholding algorithm
31 int BadThreshold(unsigned char *image, int imageWidth, int imageHeight) {
32  //loop through entire array, find sum of magnitudes
33  long totalMag = 0;
34  for (long i = 0; i < imageHeight * imageWidth; i++) {
35  totalMag += image[i];
36  }
37  return (((totalMag/(imageHeight * imageWidth)) + 1) * 15) / 10;
38 }
39 
40 // a more sophisticated thresholding algorithm, not tailored to star images
41 int OtsusThreshold(unsigned char *image, int imageWidth, int imageHeight) {
42  // code here, duh
43  long total = imageWidth * imageHeight;
44  //decimal top = 255;
45  decimal sumB = 0;
46  decimal sum1 = 0;
47  decimal wB = 0;
48  decimal maximum = 0;
49  int level = 0;
50  // make the histogram (array length 256)
51  int histogram[256];
52 
53  memset(histogram, 0, sizeof(int)*256);
54 
55  for (long i = 0; i < total; i++) {
56  histogram[image[i]]++;
57  }
58  for (int i = 0; i < 256; i ++) {
59  sum1 += i * histogram[i];
60  }
61  for (int i = 0; i < 256; i ++) {
62  decimal wF = total - wB;
63  //std::cout << "wF\n" << wB << "\n";
64  //std::cout << "wB\n" << wF << "\n";
65  if (wB > 0 && wF > 0) {
66  decimal mF = (sum1 - sumB) / wF;
67  decimal val = wB * wF * ((sumB / wB) - mF) * ((sumB / wB) - mF);
68  //std::cout << val << "\n";
69  if (val >= maximum) {
70  level = i;
71  maximum = val;
72  }
73  }
74  wB = wB + histogram[i];
75  sumB = sumB + i * histogram[i];
76  }
77  return level;
78 }
79 
80 // a simple, but well tested thresholding algorithm that works well with star images
81 int BasicThreshold(unsigned char *image, int imageWidth, int imageHeight) {
82  unsigned long totalMag = 0;
83  decimal std = 0;
84  long totalPixels = imageHeight * imageWidth;
85  for (long i = 0; i < totalPixels; i++) {
86  totalMag += image[i];
87  }
88  decimal mean = totalMag / totalPixels;
89  for (long i = 0; i < totalPixels; i++) {
90  std += DECIMAL_POW(image[i] - mean, 2);
91  }
92  std = DECIMAL_SQRT(std / totalPixels);
93  return mean + (std * 5);
94 }
95 
96 // basic thresholding, but do it faster (trade off of some accuracy?)
97 int BasicThresholdOnePass(unsigned char *image, int imageWidth, int imageHeight) {
98  unsigned long totalMag = 0;
99  decimal std = 0;
100  decimal sq_totalMag = 0;
101  long totalPixels = imageHeight * imageWidth;
102  for (long i = 0; i < totalPixels; i++) {
103  totalMag += image[i];
104  sq_totalMag += image[i] * image[i];
105  }
106  decimal mean = totalMag / totalPixels;
107  decimal variance = (sq_totalMag / totalPixels) - (mean * mean);
108  std = DECIMAL_SQRT(variance);
109  return mean + (std * 5);
110 }
111 
115  long magSum;
116  int xMin;
117  int xMax;
118  int yMin;
119  int yMax;
120  int cutoff;
121  bool isValid;
122  std::unordered_set<int> checkedIndices;
123 };
124 
125 //recursive helper here
126 void CogHelper(CentroidParams *p, long i, unsigned char *image, int imageWidth, int imageHeight) {
127 
128  if (i >= 0 && i < imageWidth * imageHeight && image[i] >= p->cutoff && p->checkedIndices.count(i) == 0) {
129  //check if pixel is on the edge of the image, if it is, we dont want to centroid this star
130  if (i % imageWidth == 0 || i % imageWidth == imageWidth - 1 || i / imageWidth == 0 || i / imageWidth == imageHeight - 1) {
131  p->isValid = false;
132  }
133  p->checkedIndices.insert(i);
134  if (i % imageWidth > p->xMax) {
135  p->xMax = i % imageWidth;
136  } else if (i % imageWidth < p->xMin) {
137  p->xMin = i % imageWidth;
138  }
139  if (i / imageWidth > p->yMax) {
140  p->yMax = i / imageWidth;
141  } else if (i / imageWidth < p->yMin) {
142  p->yMin = i / imageWidth;
143  }
144  p->magSum += image[i];
145  p->xCoordMagSum += ((i % imageWidth)) * image[i];
146  p->yCoordMagSum += ((i / imageWidth)) * image[i];
147  if (i % imageWidth != imageWidth - 1) {
148  CogHelper(p, i + 1, image, imageWidth, imageHeight);
149  }
150  if (i % imageWidth != 0) {
151  CogHelper(p, i - 1, image, imageWidth, imageHeight);
152  }
153  CogHelper(p, i + imageWidth, image, imageWidth, imageHeight);
154  CogHelper(p, i - imageWidth, image, imageWidth, imageHeight);
155  }
156 }
157 
158 std::vector<Star> CenterOfGravityAlgorithm::Go(unsigned char *image, int imageWidth, int imageHeight) const {
159  CentroidParams p;
160 
161  std::vector<Star> result;
162 
163  p.cutoff = BasicThreshold(image, imageWidth, imageHeight);
164  for (long i = 0; i < imageHeight * imageWidth; i++) {
165  if (image[i] >= p.cutoff && p.checkedIndices.count(i) == 0) {
166 
167  //iterate over pixels that are part of the star
168  int xDiameter = 0; //radius of current star
169  int yDiameter = 0;
170  p.yCoordMagSum = 0; //y coordinate of current star
171  p.xCoordMagSum = 0; //x coordinate of current star
172  p.magSum = 0; //sum of magnitudes of current star
173 
174  p.xMax = i % imageWidth;
175  p.xMin = i % imageWidth;
176  p.yMax = i / imageWidth;
177  p.yMin = i / imageWidth;
178  p.isValid = true;
179 
180  int sizeBefore = p.checkedIndices.size();
181 
182  CogHelper(&p, i, image, imageWidth, imageHeight);
183  xDiameter = (p.xMax - p.xMin) + 1;
184  yDiameter = (p.yMax - p.yMin) + 1;
185 
186  //use the sums to finish CoG equation and add stars to the result
187  decimal xCoord = (p.xCoordMagSum / (p.magSum * DECIMAL(1.0)));
188  decimal yCoord = (p.yCoordMagSum / (p.magSum * DECIMAL(1.0)));
189 
190  if (p.isValid) {
191  result.push_back(Star(xCoord + DECIMAL(0.5), yCoord + DECIMAL(0.5), (xDiameter)/DECIMAL(2.0), (yDiameter)/DECIMAL(2.0), p.checkedIndices.size() - sizeBefore));
192  }
193  }
194  }
195  return result;
196 }
197 
198 //Determines how accurate and how much iteration is done by the IWCoG algorithm,
199 //smaller means more accurate and more iterations.
201 
202 struct IWCoGParams {
203  int xMin;
204  int xMax;
205  int yMin;
206  int yMax;
207  int cutoff;
209  int guess;
210  bool isValid;
211  std::unordered_set<int> checkedIndices;
212 };
213 
214 void IWCoGHelper(IWCoGParams *p, long i, unsigned char *image, int imageWidth, int imageHeight, std::vector<int> *starIndices) {
215  if (i >= 0 && i < imageWidth * imageHeight && image[i] >= p->cutoff && p->checkedIndices.count(i) == 0) {
216  //check if pixel is on the edge of the image, if it is, we dont want to centroid this star
217  if (i % imageWidth == 0 || i % imageWidth == imageWidth - 1 || i / imageWidth == 0 || i / imageWidth == imageHeight - 1) {
218  p->isValid = false;
219  }
220  p->checkedIndices.insert(i);
221  starIndices->push_back(i);
222  if (image[i] > p->maxIntensity) {
223  p->maxIntensity = image[i];
224  p->guess = i;
225  }
226  if (i % imageWidth > p->xMax) {
227  p->xMax = i % imageWidth;
228  } else if (i % imageWidth < p->xMin) {
229  p->xMin = i % imageWidth;
230  }
231  if (i / imageWidth > p->yMax) {
232  p->yMax = i / imageWidth;
233  } else if (i / imageWidth < p->yMin) {
234  p->yMin = i / imageWidth;
235  }
236  if (i % imageWidth != imageWidth - 1) {
237  IWCoGHelper(p, i + 1, image, imageWidth, imageHeight, starIndices);
238  }
239  if (i % imageWidth != 0) {
240  IWCoGHelper(p, i - 1, image, imageWidth, imageHeight, starIndices);
241  }
242  IWCoGHelper(p, i + imageWidth, image, imageWidth, imageHeight, starIndices);
243  IWCoGHelper(p, i - imageWidth, image, imageWidth, imageHeight, starIndices);
244  }
245 }
246 
247 Stars IterativeWeightedCenterOfGravityAlgorithm::Go(unsigned char *image, int imageWidth, int imageHeight) const {
248  IWCoGParams p;
249  std::vector<Star> result;
250  p.cutoff = BasicThreshold(image, imageWidth, imageHeight);
251  for (long i = 0; i < imageHeight * imageWidth; i++) {
252  //check if pixel is part of a "star" and has not been iterated over
253  if (image[i] >= p.cutoff && p.checkedIndices.count(i) == 0) {
254  // TODO: store longs --Mark
255  std::vector<int> starIndices; //indices of the current star
256  p.maxIntensity = 0;
257  int xDiameter = 0;
258  int yDiameter = 0;
259  decimal yWeightedCoordMagSum = 0;
260  decimal xWeightedCoordMagSum = 0;
261  decimal weightedMagSum = 0;
262  decimal fwhm; //fwhm variable
263  decimal standardDeviation;
264  decimal w; //weight value
265 
266  p.xMax = i % imageWidth;
267  p.xMin = i % imageWidth;
268  p.yMax = i / imageWidth;
269  p.yMin = i / imageWidth;
270  p.isValid = true;
271 
272 
273  IWCoGHelper(&p, i, image, imageWidth, imageHeight, &starIndices);
274 
275  xDiameter = (p.xMax - p.xMin) + 1;
276  yDiameter = (p.yMax - p.yMin) + 1;
277 
278  //calculate fwhm
279  decimal count = 0;
280  for (int j = 0; j < (int) starIndices.size(); j++) {
281  if (image[starIndices.at(j)] > p.maxIntensity / 2) {
282  count++;
283  }
284  }
285  fwhm = DECIMAL_SQRT(count);
286  standardDeviation = fwhm / (DECIMAL(2.0) * DECIMAL_SQRT(DECIMAL(2.0) * DECIMAL_LOG(2.0)));
287  decimal modifiedStdDev = DECIMAL(2.0) * DECIMAL_POW(standardDeviation, 2);
288  // TODO: Why are these decimals? --Mark
289  decimal guessXCoord = (p.guess % imageWidth);
290  decimal guessYCoord = (p.guess / imageWidth);
291  //how much our new centroid estimate changes w each iteration
292  decimal change = INFINITY;
293  int stop = 0;
294  //while we see some large enough change in estimated, maybe make it a global variable
295  while (change > iWCoGMinChange && stop < 100000) {
296  //traverse through star indices, calculate W at each coordinate, add to final coordinate sums
297  yWeightedCoordMagSum = 0;
298  xWeightedCoordMagSum = 0;
299  weightedMagSum = 0;
300  stop++;
301  for (long j = 0; j < (long)starIndices.size(); j++) {
302  //calculate w
303  decimal currXCoord = starIndices.at(j) % imageWidth;
304  decimal currYCoord = starIndices.at(j) / imageWidth;
305  w = p.maxIntensity * DECIMAL_EXP(DECIMAL(-1.0) * ((DECIMAL_POW(currXCoord - guessXCoord, 2) / modifiedStdDev) + (DECIMAL_POW(currYCoord - guessYCoord, 2) / modifiedStdDev)));
306 
307  xWeightedCoordMagSum += w * currXCoord * DECIMAL(image[starIndices.at(j)]);
308  yWeightedCoordMagSum += w * currYCoord * DECIMAL(image[starIndices.at(j)]);
309  weightedMagSum += w * DECIMAL(image[starIndices.at(j)]);
310  }
311  decimal xTemp = xWeightedCoordMagSum / weightedMagSum;
312  decimal yTemp = yWeightedCoordMagSum / weightedMagSum;
313 
314  change = abs(guessXCoord - xTemp) + abs(guessYCoord - yTemp);
315 
316  guessXCoord = xTemp;
317  guessYCoord = yTemp;
318  }
319  if (p.isValid) {
320  result.push_back(Star(guessXCoord + DECIMAL(0.5), guessYCoord + DECIMAL(0.5), xDiameter/DECIMAL(2.0), yDiameter/DECIMAL(2.0), starIndices.size()));
321  }
322  }
323  }
324  return result;
325 }
326 
327 }
Stars Go(unsigned char *image, int imageWidth, int imageHeight) const override
Actually perform the centroid detection.
Stars Go(unsigned char *image, int imageWidth, int imageHeight) const override
Actually perform the centroid detection.
Definition: centroiders.cpp:19
Stars Go(unsigned char *image, int imageWidth, int imageHeight) const override
Actually perform the centroid detection.
A "centroid" detected in an image.
Definition: star-utils.hpp:49
double decimal
Definition: decimal.hpp:11
#define DECIMAL_LOG(x)
Definition: decimal.hpp:41
#define DECIMAL(x)
Definition: decimal.hpp:21
#define DECIMAL_POW(base, power)
Definition: decimal.hpp:39
#define DECIMAL_SQRT(x)
Definition: decimal.hpp:40
#define DECIMAL_EXP(x)
Definition: decimal.hpp:42
LOST starting point.
int OtsusThreshold(unsigned char *image, int imageWidth, int imageHeight)
Definition: centroiders.cpp:41
int BadThreshold(unsigned char *image, int imageWidth, int imageHeight)
Definition: centroiders.cpp:31
std::vector< Star > Stars
Definition: star-utils.hpp:101
void IWCoGHelper(IWCoGParams *p, long i, unsigned char *image, int imageWidth, int imageHeight, std::vector< int > *starIndices)
decimal iWCoGMinChange
void CogHelper(CentroidParams *p, long i, unsigned char *image, int imageWidth, int imageHeight)
int BasicThresholdOnePass(unsigned char *image, int imageWidth, int imageHeight)
Definition: centroiders.cpp:97
int BasicThreshold(unsigned char *image, int imageWidth, int imageHeight)
Definition: centroiders.cpp:81
std::unordered_set< int > checkedIndices
std::unordered_set< int > checkedIndices