3 #include <cairo/cairo.h>
35 if (isBinary && isatty(fileno(stdout)) && (filePath ==
"stdout" || filePath ==
"-")) {
36 std::cerr <<
"WARNING: output contains binary contents. Not printed to terminal." << std::endl;
37 filePath =
"/dev/null";
40 if (filePath ==
"stdout" || filePath ==
"-") {
44 std::fstream *fs =
new std::fstream();
45 fs->open(filePath, std::fstream::out);
58 std::vector<CatalogStar>
BscParse(std::string tsvPath) {
59 std::vector<CatalogStar> result;
62 int magnitudeHigh, magnitudeLow, name;
65 file = fopen(tsvPath.c_str(),
"r");
68 printf(
"Error opening file: %s\n", strerror(errno));
73 #ifdef LOST_FLOAT_MODE
74 std::string format =
"%f|%f|%d|%c|%d.%d";
76 std::string format =
"%lf|%lf|%d|%c|%d.%d";
79 while (EOF != fscanf(file, format.c_str(),
82 &magnitudeHigh, &magnitudeLow)) {
85 magnitudeHigh*100 + (magnitudeHigh < 0 ? -magnitudeLow : magnitudeLow),
90 assert(result.size() > 9000);
94 #ifndef DEFAULT_BSC_PATH
95 #define DEFAULT_BSC_PATH "bright-star-catalog.tsv"
100 static bool readYet =
false;
101 static std::vector<CatalogStar> catalog;
105 char *tsvPath = getenv(
"LOST_BSC_PATH");
110 return a.spatial.x < b.spatial.x;
112 for (
int i = catalog.size()-1; i > 0; i--) {
113 if ((catalog[i].spatial - catalog[i-1].spatial).Magnitude() <
DECIMAL(5e-5)) {
114 if (catalog[i].magnitude > catalog[i-1].magnitude) {
115 catalog.erase(catalog.begin() + i);
117 catalog.erase(catalog.begin() + i - 1);
129 unsigned char *result;
130 uint32_t *cairoImage, pixel;
132 if (cairo_image_surface_get_format(cairoSurface) != CAIRO_FORMAT_ARGB32 &&
133 cairo_image_surface_get_format(cairoSurface) != CAIRO_FORMAT_RGB24) {
134 puts(
"Can't convert weird image formats to grayscale.");
138 width = cairo_image_surface_get_width(cairoSurface);
139 height = cairo_image_surface_get_height(cairoSurface);
141 result =
new unsigned char[width*height];
142 cairoImage = (uint32_t *)cairo_image_surface_get_data(cairoSurface);
144 for (
int i = 0; i < height * width; i++) {
145 pixel = cairoImage[i];
148 (pixel>>16 &0xFF) *0.21 +
149 (pixel>>8 &0xFF) *0.71 +
150 (pixel &0xFF) *0.07);
157 const int width,
const int height) {
159 cairo_surface_t *result = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
160 uint32_t *resultData = (uint32_t *)cairo_image_surface_get_data(result);
162 cairo_surface_flush(result);
163 for (
long i = 0; i < width * height; i++) {
165 resultData[i] = (image[i] << 16) + (image[i] << 8) + (image[i]);
167 cairo_surface_mark_dirty(result);
179 cairo_surface_t *cairoSurface,
189 bool rawStarIndexes =
false) {
191 std::string metadata = description +
" ";
193 cairoCtx = cairo_create(cairoSurface);
194 cairo_set_source_rgba(cairoCtx, red, green, blue, alpha);
195 cairo_set_line_width(cairoCtx, 1.0);
196 cairo_set_antialias(cairoCtx, CAIRO_ANTIALIAS_NONE);
197 cairo_font_options_t *cairoFontOptions = cairo_font_options_create();
198 cairo_font_options_set_antialias(cairoFontOptions, CAIRO_ANTIALIAS_NONE);
199 cairo_set_font_options(cairoCtx, cairoFontOptions);
200 cairo_text_extents_t cairoTextExtents;
201 cairo_text_extents(cairoCtx,
"1234567890", &cairoTextExtents);
202 decimal textHeight = cairoTextExtents.height;
204 for (
const Star ¢roid : stars) {
206 if (centroid.radiusX >
DECIMAL(0.0)) {
207 decimal radiusX = centroid.radiusX;
209 centroid.radiusY : radiusX;
213 cairo_rectangle(cairoCtx,
214 centroid.position.x - radiusX,
215 centroid.position.y - radiusY,
218 cairo_stroke(cairoCtx);
220 cairo_rectangle(cairoCtx,
224 cairo_fill(cairoCtx);
228 metadata += std::to_string(stars.size()) +
" centroids ";
230 if (starIds != NULL) {
231 assert(catalog != NULL);
234 const Star ¢roid = stars[starId.starIndex];
235 cairo_move_to(cairoCtx,
245 int plotName = rawStarIndexes ? starId.catalogIndex : (*catalog)[starId.catalogIndex].name;
246 cairo_show_text(cairoCtx, std::to_string(plotName).c_str());
248 metadata += std::to_string(starIds->size()) +
" identified ";
251 if (attitude != NULL) {
254 "RA: " + std::to_string(
RadToDeg(spherical.
ra)) +
" " +
255 "DE: " + std::to_string(
RadToDeg(spherical.
de)) +
" " +
256 "Roll: " + std::to_string(
RadToDeg(spherical.
roll)) +
" ";
260 cairo_move_to(cairoCtx, 3, 3 + textHeight);
261 cairo_show_text(cairoCtx, metadata.c_str());
263 cairo_font_options_destroy(cairoFontOptions);
264 cairo_destroy(cairoCtx);
277 return SerializeContext(values.swapIntegerEndianness, values.swapDecimalEndianness);
288 if (values.kvector) {
291 long numBins = values.kvectorNumDistanceBins;
296 std::cerr <<
"No database builder selected -- no database generated." << std::endl;
305 os <<
"camera_focal_length " << camera.
FocalLength() << std::endl
306 <<
"camera_fov " << camera.
Fov() << std::endl
307 <<
"camera_resolution_x " << camera.
XResolution() << std::endl
308 <<
"camera_resolution_y " << camera.
YResolution() << std::endl;
320 if ((values.pixelSize != -1) ^ (values.focalLength != 0)) {
321 std::cerr <<
"ERROR: Exactly one of --pixel-size or --focal-length were set." << std::endl;
326 if (values.pixelSize != -1 && values.fov != 20) {
327 std::cerr <<
"ERROR: Both focal length and FOV were provided. We only need one of the two methods (pixel size + focal length, or fov) to determine fov, please only provide one." << std::endl;
331 if (values.pixelSize == -1) {
334 return values.focalLength * 1000 / values.pixelSize;
367 : camera(camera), catalog(catalog) {
370 image.
width = cairo_image_surface_get_width(cairoSurface);
371 image.
height = cairo_image_surface_get_height(cairoSurface);
375 delete[] image.
image;
383 cairo_surface_t *cairoSurface = NULL;
384 std::string pngPath = values.png;
386 cairoSurface = cairo_image_surface_create_from_png(pngPath.c_str());
387 std::cerr <<
"PNG Read status: " << cairo_status_to_string(cairo_surface_status(cairoSurface)) << std::endl;
388 if (cairoSurface == NULL || cairo_surface_status(cairoSurface) != CAIRO_STATUS_SUCCESS) {
392 int xResolution = cairo_image_surface_get_width(cairoSurface);
393 int yResolution = cairo_image_surface_get_height(cairoSurface);
395 Camera cam =
Camera(focalLengthPixels, xResolution, yResolution);
398 cairo_surface_destroy(cairoSurface);
447 const Vec2 d0 = p0 - pixel;
456 static decimal StaticPixelBrightness(
const Vec2 &pixel,
const GeneratedStar &generatedStar,
458 const Vec2 d0 = generatedStar.position - pixel;
459 return generatedStar.peakBrightness * t *
DECIMAL_EXP(-d0.MagnitudeSq() / (2 * stddev * stddev));
488 std::default_random_engine *rng,
502 int falseStarMinMagnitude,
503 int falseStarMaxMagnitude,
506 : camera(camera), attitude(attitude), catalog(catalog) {
508 assert(falseStarMaxMagnitude <= falseStarMinMagnitude);
509 assert(perturbationStddev >=
DECIMAL(0.0));
515 assert(oversampling >= 1);
517 if (oversamplingPerAxis*oversamplingPerAxis != oversampling) {
518 std::cerr <<
"WARNING: oversampling was not a perfect square. Rounding up to "
519 << oversamplingPerAxis*oversamplingPerAxis <<
"." << std::endl;
521 assert(exposureTime > 0);
527 Quaternion futureAttitude = motionBlurDirectionQ*currentAttitude;
528 std::vector<GeneratedStar> generatedStars;
532 decimal zeroMagPeakPhotonDensity = zeroMagTotalPhotons / (2*
DECIMAL_M_PI * starSpreadStdDev*starSpreadStdDev);
535 std::normal_distribution<decimal> perturbation1DDistribution(
DECIMAL(0.0), perturbationStddev);
537 Catalog catalogWithFalse = catalog;
539 std::uniform_real_distribution<decimal> uniformDistribution(
DECIMAL(0.0),
DECIMAL(1.0));
540 std::uniform_int_distribution<int> magnitudeDistribution(falseStarMaxMagnitude, falseStarMinMagnitude);
541 for (
int i = 0; i < numFalseStars; i++) {
545 decimal magnitude = magnitudeDistribution(*rng);
547 catalogWithFalse.push_back(
CatalogStar(ra, de, magnitude, -1));
550 for (
int i = 0; i < (int)catalogWithFalse.size(); i++) {
551 bool isTrueStar = i < (int)catalog.size();
553 const CatalogStar &catalogStar = catalogWithFalse[i];
554 Vec3 rotated = attitude.
Rotate(catalogWithFalse[i].spatial);
555 if (rotated.
x <= 0) {
561 Vec3 futureSpatial = futureAttitude.
Rotate(catalogWithFalse[i].spatial);
563 if (!motionBlurEnabled) {
579 generatedStars.push_back(
GeneratedStar(star, peakBrightnessPerTime, delta));
587 expectedStars.push_back(star);
590 expectedStarIds.push_back(
StarIdentifier(expectedStars.size()-1, i));
594 Star inputStar = star;
595 if (perturbationStddev >
DECIMAL(0.0)) {
597 inputStar.
position.
x += std::max(std::min(perturbation1DDistribution(*rng), 2*perturbationStddev), -2*perturbationStddev);
598 inputStar.
position.
y += std::max(std::min(perturbation1DDistribution(*rng), 2*perturbationStddev), -2*perturbationStddev);
603 && (cutoffMag >= 10000
605 || std::bernoulli_distribution(CentroidImagingProbability(catalogStar.
magnitude, cutoffMag))(*rng))) {
606 inputStars.push_back(inputStar);
619 std::vector<decimal> photonsBuffer(image.
width*image.
height, 0);
623 Vec2 earliestPosition = star.position - star.delta*(exposureTime/
DECIMAL(2.0) + readoutTime/
DECIMAL(2.0));
624 Vec2 latestPosition = star.position + star.delta*(exposureTime/
DECIMAL(2.0) + readoutTime/
DECIMAL(2.0));
625 int xMin = std::max(0, (
int)std::min(earliestPosition.
x - star.radiusX, latestPosition.
x - star.radiusX));
626 int xMax = std::min(image.
width-1, (
int)std::max(earliestPosition.
x + star.radiusX, latestPosition.
x + star.radiusX));
627 int yMin = std::max(0, (
int)std::min(earliestPosition.
y - star.radiusX, latestPosition.
y - star.radiusX));
628 int yMax = std::min(image.
height-1, (
int)std::max(earliestPosition.
y + star.radiusX, latestPosition.
y + star.radiusX));
632 decimal oversamplingBrightnessFactor = oversamplingPerAxis*oversamplingPerAxis;
638 for (
int xPixel = xMin; xPixel <= xMax; xPixel++) {
639 for (
int yPixel = yMin; yPixel <= yMax; yPixel++) {
647 for (
int xSample = 0; xSample < oversamplingPerAxis; xSample++) {
648 for (
int ySample = 0; ySample < oversamplingPerAxis; ySample++) {
653 if (motionBlurEnabled) {
655 (MotionBlurredPixelBrightness({x, y}, star, tEnd, starSpreadStdDev)
656 - MotionBlurredPixelBrightness({x, y}, star, tStart, starSpreadStdDev))
657 / oversamplingBrightnessFactor;
659 curPhotons = StaticPixelBrightness({x, y}, star, exposureTime, starSpreadStdDev)
660 / oversamplingBrightnessFactor;
663 assert(
DECIMAL(0.0) <= curPhotons);
665 photonsBuffer[xPixel + yPixel*image.
width] += curPhotons;
672 std::normal_distribution<decimal> readNoiseDist(
DECIMAL(0.0), readNoiseStdDev);
675 imageData = std::vector<unsigned char>(image.
width*image.
height);
676 image.
image = imageData.data();
677 for (
int i = 0; i < image.
width * image.
height; i++) {
681 curBrightness += darkCurrent;
684 curBrightness += readNoiseDist(*rng);
687 long quantizedPhotons;
693 decimal photons = photonsBuffer[i];
695 std::cout <<
"ERROR: One of the pixels had too many photons. Generated image would not be physically accurate, exiting." << std::endl;
698 std::poisson_distribution<long> shotNoiseDist(photonsBuffer[i]);
699 quantizedPhotons = shotNoiseDist(*rng);
701 quantizedPhotons = round(photonsBuffer[i]);
703 curBrightness += quantizedPhotons / saturationPhotons;
715 static Attitude RandomAttitude(std::default_random_engine* pReng) {
716 std::uniform_real_distribution<decimal> randomAngleDistribution(0, 1);
740 if (values.timeSeed) {
743 seed = values.generateSeed;
746 std::default_random_engine attitudeRng(seed);
747 std::default_random_engine noiseRng(seed);
756 DegToRad(values.generateBlurRoll)));
762 for (
int i = 0; i < values.generate; i++) {
766 if (values.generateRandomAttitudes) {
767 inputAttitude = RandomAttitude(&attitudeRng);
769 inputAttitude = attitude;
775 Camera(focalLength, values.generateXRes, values.generateYRes),
778 values.generateCentroidsOnly,
779 values.generateZeroMagPhotons,
780 values.generateSpreadStdDev,
781 values.generateSaturationPhotons,
782 values.generateDarkCurrent,
783 values.generateReadNoiseStdDev,
785 values.generateExposure,
786 values.generateReadoutTime,
787 values.generateShotNoise,
788 values.generateOversampling,
789 values.generateNumFalseStars,
790 (values.generateFalseMinMag * 100),
791 (values.generateFalseMaxMag * 100),
792 (values.generateCutoffMag * 100),
793 values.generatePerturbationStddev);
795 result.push_back(std::unique_ptr<PipelineInput>(curr));
810 if (values.png !=
"") {
824 unsigned char *database)
826 if (centroidAlgorithm) {
827 this->centroidAlgorithm = std::unique_ptr<CentroidAlgorithm>(centroidAlgorithm);
829 if (starIdAlgorithm) {
830 this->starIdAlgorithm = std::unique_ptr<StarIdAlgorithm>(starIdAlgorithm);
832 if (attitudeEstimationAlgorithm) {
833 this->attitudeEstimationAlgorithm = std::unique_ptr<AttitudeEstimationAlgorithm>(attitudeEstimationAlgorithm);
836 this->database = std::unique_ptr<unsigned char[]>(database);
851 if (values.centroidAlgo ==
"dummy") {
852 result.centroidAlgorithm = std::unique_ptr<CentroidAlgorithm>(
new DummyCentroidAlgorithm(values.centroidDummyNumStars));
853 }
else if (values.centroidAlgo ==
"cog") {
855 }
else if (values.centroidAlgo ==
"iwcog") {
857 }
else if (values.centroidAlgo !=
"") {
858 std::cout <<
"Illegal centroid algorithm." << std::endl;
863 if (values.centroidMagFilter > 0) result.centroidMinMagnitude = values.centroidMagFilter;
864 if (values.centroidFilterBrightest > 0) result.centroidMinStars = values.centroidFilterBrightest;
867 if (values.databasePath !=
"") {
869 fs.open(values.databasePath, std::fstream::in | std::fstream::binary);
871 long length = fs.tellg();
874 std::cerr <<
"Error reading database! " << strerror(errno) << std::endl;
877 std::cerr <<
"Reading " << length <<
" bytes of database" << std::endl;
878 result.database = std::unique_ptr<unsigned char[]>(
new unsigned char[length]);
879 fs.read((
char *)result.database.get(), length);
880 std::cerr <<
"Done" << std::endl;
883 if (values.idAlgo ==
"dummy") {
885 }
else if (values.idAlgo ==
"gv") {
887 }
else if (values.idAlgo ==
"py") {
888 result.starIdAlgorithm = std::unique_ptr<StarIdAlgorithm>(
new PyramidStarIdAlgorithm(
DegToRad(values.angularTolerance), values.estimatedNumFalseStars, values.maxMismatchProb, 1000));
889 }
else if (values.idAlgo !=
"") {
890 std::cout <<
"Illegal id algorithm." << std::endl;
894 if (values.attitudeAlgo ==
"dqm") {
895 result.attitudeEstimationAlgorithm = std::unique_ptr<AttitudeEstimationAlgorithm>(
new DavenportQAlgorithm());
896 }
else if (values.attitudeAlgo ==
"triad") {
897 result.attitudeEstimationAlgorithm = std::unique_ptr<AttitudeEstimationAlgorithm>(
new TriadAlgorithm());
898 }
else if (values.attitudeAlgo ==
"quest") {
899 result.attitudeEstimationAlgorithm = std::unique_ptr<AttitudeEstimationAlgorithm>(
new QuestAlgorithm());
900 }
else if (values.attitudeAlgo !=
"") {
901 std::cout <<
"Illegal attitude algorithm." << std::endl;
927 if (catalogBuffer != NULL) {
931 std::cerr <<
"WARNING: That database does not include a catalog. Proceeding with the full catalog." << std::endl;
938 if (centroidAlgorithm && inputImage) {
941 std::chrono::time_point<std::chrono::steady_clock> start = std::chrono::steady_clock::now();
944 Stars unfilteredStars = centroidAlgorithm->Go(inputImage->
image, inputImage->
width, inputImage->
height);
946 std::chrono::time_point<std::chrono::steady_clock> end = std::chrono::steady_clock::now();
947 result.
centroidingTimeNs = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
950 int minMagnitude = centroidMinMagnitude;
951 if (centroidMinStars > 0
953 && centroidMinStars < (
int)unfilteredStars.size()) {
955 Stars magSortedStars = unfilteredStars;
957 std::sort(magSortedStars.begin(), magSortedStars.end(), [](
const Star &a,
const Star &b) { return a.magnitude > b.magnitude; });
958 minMagnitude = std::max(minMagnitude, magSortedStars[centroidMinStars - 1].magnitude);
961 Stars *filteredStars =
new std::vector<Star>();
962 for (
const Star &star : unfilteredStars) {
963 assert(star.magnitude >= 0);
965 if (star.magnitude >= minMagnitude) {
966 filteredStars->push_back(star);
969 result.
stars = std::unique_ptr<Stars>(filteredStars);
970 inputStars = filteredStars;
976 }
else if (centroidAlgorithm) {
977 std::cerr <<
"ERROR: Centroid algorithm specified, but no input image to run it on." << std::endl;
981 if (starIdAlgorithm && database && inputStars && input.
InputCamera()) {
983 std::chrono::time_point<std::chrono::steady_clock> start = std::chrono::steady_clock::now();
985 result.
starIds = std::unique_ptr<StarIdentifiers>(
new std::vector<StarIdentifier>(
986 starIdAlgorithm->Go(database.get(), *inputStars, result.
catalog, *input.
InputCamera())));
988 std::chrono::time_point<std::chrono::steady_clock> end = std::chrono::steady_clock::now();
989 result.
starIdTimeNs = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
991 inputStarIds = result.
starIds.get();
992 }
else if (starIdAlgorithm) {
993 std::cerr <<
"ERROR: Star ID algorithm specified but cannot run because database, centroids, or camera are missing." << std::endl;
997 if (attitudeEstimationAlgorithm && inputStarIds && input.
InputCamera()) {
999 std::chrono::time_point<std::chrono::steady_clock> start = std::chrono::steady_clock::now();
1001 result.
attitude = std::unique_ptr<Attitude>(
1004 std::chrono::time_point<std::chrono::steady_clock> end = std::chrono::steady_clock::now();
1006 }
else if (attitudeEstimationAlgorithm) {
1007 std::cerr <<
"ERROR: Attitude estimation algorithm set, but either star IDs or camera are missing. One reason this can happen: Setting a centroid algorithm and attitude algorithm, but no star-id algorithm -- that can't work because the input star-ids won't properly correspond to the output centroids!" << std::endl;
1016 std::vector<PipelineOutput> result;
1019 for (
const std::unique_ptr<PipelineInput> &input : inputs) {
1020 result.push_back(
Go(*input));
1061 static std::multimap<int, int> FindClosestCentroids(
decimal threshold,
1064 std::multimap<int, int> result;
1066 for (
int i = 0; i < (int)one.size(); i++) {
1067 std::vector<std::pair<decimal, int>> closest;
1068 for (
int k = 0; k < (int)two.size(); k++) {
1069 decimal currDistance = (one[i].position - two[k].position).Magnitude();
1070 if (currDistance <= threshold) {
1071 closest.emplace_back(currDistance, k);
1074 std::sort(closest.begin(), closest.end());
1075 for (
const std::pair<decimal, int> &pair : closest) {
1076 result.emplace(i, pair.second);
1089 const Stars &expected,
1090 const Stars &actual) {
1098 std::multimap<int, int> actualToExpected = FindClosestCentroids(threshold, actual, expected);
1100 for (
int i = 0; i < (int)actualToExpected.size(); i++) {
1101 auto closest = actualToExpected.find(i);
1102 if (closest == actualToExpected.end()) {
1105 result.
meanError += (actual[i].position - expected[closest->second].position).Magnitude();
1115 assert(comparisons.size() > 0);
1120 result.
meanError += comparison.meanError;
1137 const Stars &expectedStars,
const Stars &inputStars) {
1148 std::vector<int> expectedCatalogIndices(expectedStars.size(), -1);
1150 assert(0 <= starId.starIndex && starId.starIndex <= (
int)expectedStars.size());
1151 assert(0 <= starId.catalogIndex && starId.catalogIndex <= (
int)expectedCatalog.size());
1152 expectedCatalogIndices[starId.starIndex] = starId.catalogIndex;
1157 std::multimap<int, int> inputToExpectedCentroids = FindClosestCentroids(centroidThreshold, inputStars, expectedStars);
1162 for (
int i = 0; i < (int)inputStars.size(); i++) {
1164 auto closestRange = inputToExpectedCentroids.equal_range(i);
1166 for (
auto it = closestRange.first; it != closestRange.second; it++) {
1167 if (expectedCatalogIndices[it->second] != -1) {
1179 std::vector<bool> identifiedInputCentroids(inputStars.size(),
false);
1182 assert(!identifiedInputCentroids[starId.starIndex]);
1183 identifiedInputCentroids[starId.starIndex] =
true;
1184 assert(0 <= starId.starIndex && starId.starIndex <= (
int)inputStars.size());
1185 assert(0 <= starId.catalogIndex && starId.catalogIndex <= (
int)actualCatalog.size());
1188 auto expectedCentroidsInRange = inputToExpectedCentroids.equal_range(starId.starIndex);
1190 for (
auto it = expectedCentroidsInRange.first; it != expectedCentroidsInRange.second; it++) {
1191 int expectedCatalogIndex = expectedCatalogIndices[it->second];
1192 if (expectedCatalogIndex != -1
1193 && expectedCatalog[expectedCatalogIndex].name == actualCatalog[starId.catalogIndex].name) {
1216 const std::vector<PipelineOutput> &,
1220 static cairo_status_t OstreamPlotter(
void *closure,
const unsigned char *data,
unsigned int length) {
1221 std::ostream *os = (std::ostream *)closure;
1222 os->write((
const char *)data, length);
1223 return CAIRO_STATUS_SUCCESS;
1227 static void PipelineComparatorPlotRawInput(std::ostream &os,
1229 const std::vector<PipelineOutput> &,
1230 const PipelineOptions &) {
1232 cairo_surface_t *cairoSurface = expected[0]->InputImageSurface();
1233 cairo_surface_write_to_png_stream(cairoSurface, OstreamPlotter, &os);
1234 cairo_surface_destroy(cairoSurface);
1239 static void PipelineComparatorPlotInput(std::ostream &os,
1241 const std::vector<PipelineOutput> &,
1242 const PipelineOptions &) {
1243 cairo_surface_t *cairoSurface = expected[0]->InputImageSurface();
1244 assert(expected[0]->InputStars() != NULL);
1247 *expected[0]->InputStars(),
1248 expected[0]->InputStarIds(),
1249 &expected[0]->GetCatalog(),
1250 expected[0]->InputAttitude(),
1252 0.0, 1.0, 0.0, 0.6);
1253 cairo_surface_write_to_png_stream(cairoSurface, OstreamPlotter, &os);
1254 cairo_surface_destroy(cairoSurface);
1257 static void PipelineComparatorPlotExpected(std::ostream &os,
1259 const std::vector<PipelineOutput> &,
1260 const PipelineOptions &) {
1261 cairo_surface_t *cairoSurface = expected[0]->InputImageSurface();
1262 assert(expected[0]->ExpectedStars() != NULL);
1265 *expected[0]->ExpectedStars(),
1266 expected[0]->ExpectedStarIds(),
1267 &expected[0]->GetCatalog(),
1268 expected[0]->ExpectedAttitude(),
1270 0.2, 0.5, 1.0, 0.7);
1271 cairo_surface_write_to_png_stream(cairoSurface, OstreamPlotter, &os);
1272 cairo_surface_destroy(cairoSurface);
1276 static void PipelineComparatorCentroids(std::ostream &os,
1278 const std::vector<PipelineOutput> &actual,
1279 const PipelineOptions &values) {
1280 int size = (int)expected.size();
1282 decimal threshold = values.centroidCompareThreshold;
1284 std::vector<CentroidComparison> comparisons;
1285 for (
int i = 0; i < size; i++) {
1287 *(expected[i]->ExpectedStars()),
1288 *(actual[i].stars)));
1292 os <<
"centroids_num_correct " << result.numCorrectCentroids << std::endl
1293 <<
"centroids_num_extra " << result.numExtraCentroids << std::endl
1294 <<
"centroids_mean_error " << result.meanError << std::endl;
1297 static void PrintCentroids(
const std::string &prefix,
1300 const std::vector<Stars> &starses,
1303 assert(starses.size() > 0);
1305 for (
const Stars &stars : starses) {
1306 avgNumStars += stars.size();
1308 avgNumStars /= starses.size();
1310 os <<
"num_" << prefix <<
"_centroids " << avgNumStars << std::endl;
1311 if (starses.size() == 1) {
1312 const Stars &stars = starses[0];
1313 for (
int i = 0; i < (int)stars.size(); i++) {
1314 os << prefix <<
"_centroid_" << i <<
"_x " << stars[i].position.x << std::endl;
1315 os << prefix <<
"_centroid_" << i <<
"_y " << stars[i].position.y << std::endl;
1317 for (
const StarIdentifier &starId : *starIds) {
1318 if (starId.starIndex == i) {
1319 os << prefix <<
"_centroid_" << i <<
"_id " << catalog[starId.catalogIndex].name << std::endl;
1328 static void PipelineComparatorPrintExpectedCentroids(std::ostream &os,
1330 const std::vector<PipelineOutput> &,
1331 const PipelineOptions &) {
1332 assert(expected.size() > 0);
1333 assert(expected[0]->ExpectedStars());
1335 std::vector<Stars> expectedStarses;
1336 for (
const auto &input : expected) {
1337 expectedStarses.push_back(*input->ExpectedStars());
1339 PrintCentroids(
"expected",
1341 expected[0]->GetCatalog(),
1343 expected[0]->ExpectedStarIds());
1346 static void PipelineComparatorPrintInputCentroids(std::ostream &os,
1348 const std::vector<PipelineOutput> &,
1349 const PipelineOptions &) {
1350 assert(expected.size() > 0);
1351 assert(expected[0]->InputStars());
1353 std::vector<Stars> inputStarses;
1354 for (
const auto &input : expected) {
1355 inputStarses.push_back(*input->InputStars());
1357 PrintCentroids(
"input",
1359 expected[0]->GetCatalog(),
1361 expected[0]->InputStarIds());
1364 static void PipelineComparatorPrintActualCentroids(std::ostream &os,
1366 const std::vector<PipelineOutput> &actual,
1367 const PipelineOptions &values) {
1368 assert(actual.size() > 0);
1369 assert(actual[0].stars);
1371 std::vector<Stars> actualStarses;
1372 for (
const auto &output : actual) {
1373 actualStarses.push_back(*output.stars);
1375 PrintCentroids(
"actual",
1379 actual[0].starIds.get());
1381 if (expected.size() == 1 && expected[0]->ExpectedStars() && expected[0]->ExpectedStarIds()) {
1383 const Stars &actualStars = *actual[0].stars;
1384 const Stars &expectedStars = *expected[0]->ExpectedStars();
1385 std::multimap<int, int> actualToExpectedCentroids = FindClosestCentroids(values.centroidCompareThreshold, actualStars, expectedStars);
1386 for (
int i = 0; i < (int)actualStars.size(); i++) {
1387 auto range = actualToExpectedCentroids.equal_range(i);
1388 auto it = range.first;
1389 auto end = range.second;
1394 int expectedCentroidIndex = it->second;
1395 bool foundIt =
false;
1396 for (
const StarIdentifier &starId : *expected[0]->ExpectedStarIds()) {
1397 if (starId.starIndex == expectedCentroidIndex) {
1399 int expectedName = expected[0]->GetCatalog()[starId.catalogIndex].name;
1400 std::cout <<
"actual_centroid_" << i <<
"_expected_id_" << j <<
" " << expectedName << std::endl;
1413 const std::vector<PipelineOutput> &actual,
1415 const Stars &stars = actual[0].stars ? *actual[0].stars : *expected[0]->InputStars();
1417 for (
int i = 0; i < (int)stars.size(); i++) {
1420 cairo_surface_t *cairoSurface = expected[0]->InputImageSurface();
1431 cairo_surface_write_to_png_stream(cairoSurface, OstreamPlotter, &os);
1432 cairo_surface_destroy(cairoSurface);
1436 static void PipelineComparatorPlotOutput(std::ostream &os,
1438 const std::vector<PipelineOutput> &actual,
1439 const PipelineOptions &) {
1441 cairo_surface_t *cairoSurface = expected[0]->InputImageSurface();
1444 actual[0].stars ? *actual[0].stars : *expected[0]->InputStars(),
1445 actual[0].starIds.get(),
1447 actual[0].attitude.get(),
1449 1.0, 0.0, 0.0, 0.5);
1450 cairo_surface_write_to_png_stream(cairoSurface, OstreamPlotter, &os);
1451 cairo_surface_destroy(cairoSurface);
1455 static void PipelineComparatorStarIds(std::ostream &os,
1457 const std::vector<PipelineOutput> &actual,
1458 const PipelineOptions &values) {
1459 int numImagesCorrect = 0;
1460 int numImagesIncorrect = 0;
1461 int numImagesTotal = expected.size();
1462 for (
int i = 0; i < numImagesTotal; i++) {
1466 assert(actual[i].stars.get() || expected[i]->InputStars());
1468 const Stars &inputStars = actual[i].stars.get()
1469 ? *actual[i].stars.get()
1470 : *expected[i]->InputStars();
1471 StarIdComparison comparison =
1472 StarIdsCompare(*expected[i]->ExpectedStarIds(), *actual[i].starIds,
1473 expected[i]->GetCatalog(), actual[i].catalog,
1474 values.centroidCompareThreshold, *expected[i]->ExpectedStars(), inputStars);
1476 if (numImagesTotal == 1) {
1477 os <<
"starid_num_correct " << comparison.numCorrect << std::endl;
1478 os <<
"starid_num_incorrect " << comparison.numIncorrect << std::endl;
1479 os <<
"starid_num_total " << comparison.numTotal << std::endl;
1482 if (comparison.numCorrect > 0 && comparison.numIncorrect == 0) {
1485 if (comparison.numIncorrect > 0) {
1486 numImagesIncorrect++;
1491 os <<
"starid_num_images_correct " << numImagesCorrect << std::endl;
1492 os <<
"starid_num_images_incorrect " << numImagesIncorrect << std::endl;
1495 static void PrintAttitude(std::ostream &os,
const std::string &prefix,
const Attitude &attitude) {
1496 if (attitude.IsKnown()) {
1497 os << prefix <<
"attitude_known 1" << std::endl;
1499 EulerAngles spherical = attitude.ToSpherical();
1500 os << prefix <<
"attitude_ra " <<
RadToDeg(spherical.ra) << std::endl;
1501 os << prefix <<
"attitude_de " <<
RadToDeg(spherical.de) << std::endl;
1502 os << prefix <<
"attitude_roll " <<
RadToDeg(spherical.roll) << std::endl;
1504 Quaternion q = attitude.GetQuaternion();
1505 os << prefix <<
"attitude_i " << q.i << std::endl;
1506 os << prefix <<
"attitude_j " << q.j << std::endl;
1507 os << prefix <<
"attitude_k " << q.k << std::endl;
1508 os << prefix <<
"attitude_real " << q.real << std::endl;
1511 os << prefix <<
"attitude_known 0" << std::endl;
1516 static void PipelineComparatorPrintAttitude(std::ostream &os,
1518 const std::vector<PipelineOutput> &actual,
1519 const PipelineOptions &) {
1520 assert(actual.size() == 1);
1521 assert(actual[0].attitude);
1522 PrintAttitude(os,
"", *actual[0].attitude);
1525 static void PipelineComparatorPrintExpectedAttitude(std::ostream &os,
1527 const std::vector<PipelineOutput> &,
1528 const PipelineOptions &) {
1529 assert(expected.size() == 1);
1530 assert(expected[0]->ExpectedAttitude());
1531 PrintAttitude(os,
"expected_", *expected[0]->ExpectedAttitude());
1535 static void PipelineComparatorAttitude(std::ostream &os,
1537 const std::vector<PipelineOutput> &actual,
1538 const PipelineOptions &values) {
1545 decimal attitudeErrorSum = 0.0f;
1547 int numIncorrect = 0;
1549 for (
int i = 0; i < (int)expected.size(); i++) {
1550 if (actual[i].attitude->IsKnown()) {
1551 Quaternion expectedQuaternion = expected[i]->ExpectedAttitude()->GetQuaternion();
1552 Quaternion actualQuaternion = actual[i].attitude->GetQuaternion();
1553 decimal attitudeError = (expectedQuaternion * actualQuaternion.Conjugate()).SmallestAngle();
1554 assert(attitudeError >= 0);
1556 if (attitudeError <= angleThreshold) {
1557 attitudeErrorSum += attitudeError;
1565 decimal attitudeErrorMean =
DECIMAL(attitudeErrorSum) / numCorrect;
1567 decimal fractionIncorrect =
DECIMAL(numIncorrect) / expected.size();
1569 os <<
"attitude_error_mean " << attitudeErrorMean << std::endl;
1570 os <<
"attitude_availability " << fractionCorrect << std::endl;
1571 os <<
"attitude_error_rate " << fractionIncorrect << std::endl;
1574 static void PrintTimeStats(std::ostream &os,
const std::string &prefix,
const std::vector<long long> ×) {
1575 assert(times.size() > 0);
1579 long long min = LONG_MAX;
1581 for (
int i = 0; i < (int)times.size(); i++) {
1582 assert(times[i] > 0);
1584 min = std::min(min, times[i]);
1585 max = std::max(max, times[i]);
1587 long average = sum / times.size();
1588 std::vector<long long> sortedTimes = times;
1589 std::sort(sortedTimes.begin(), sortedTimes.end());
1593 int ninetyFiveIndex = (int)std::ceil(0.95 * times.size()) - 1;
1594 assert(ninetyFiveIndex >= 0);
1595 long long ninetyFifthPercentile = sortedTimes[ninetyFiveIndex];
1597 os << prefix <<
"_average_ns " << average << std::endl;
1598 os << prefix <<
"_min_ns " << min << std::endl;
1599 os << prefix <<
"_max_ns " << max << std::endl;
1600 os << prefix <<
"_95%_ns " << ninetyFifthPercentile << std::endl;
1604 static void PipelineComparatorPrintSpeed(std::ostream &os,
1606 const std::vector<PipelineOutput> &actual,
1607 const PipelineOptions &) {
1608 std::vector<long long> centroidingTimes;
1609 std::vector<long long> starIdTimes;
1610 std::vector<long long> attitudeTimes;
1611 std::vector<long long> totalTimes;
1612 for (
int i = 0; i < (int)actual.size(); i++) {
1613 long long totalTime = 0;
1614 if (actual[i].centroidingTimeNs > 0) {
1615 centroidingTimes.push_back(actual[i].centroidingTimeNs);
1616 totalTime += actual[i].centroidingTimeNs;
1618 if (actual[i].starIdTimeNs > 0) {
1619 starIdTimes.push_back(actual[i].starIdTimeNs);
1620 totalTime += actual[i].starIdTimeNs;
1622 if (actual[i].attitudeEstimationTimeNs > 0) {
1623 attitudeTimes.push_back(actual[i].attitudeEstimationTimeNs);
1624 totalTime += actual[i].attitudeEstimationTimeNs;
1626 totalTimes.push_back(totalTime);
1628 if (centroidingTimes.size() > 0) {
1629 PrintTimeStats(os,
"centroiding", centroidingTimes);
1631 if (starIdTimes.size() > 0) {
1632 PrintTimeStats(os,
"starid", starIdTimes);
1634 if (attitudeTimes.size() > 0) {
1635 PrintTimeStats(os,
"attitude", attitudeTimes);
1637 if (centroidingTimes.size() > 0 || starIdTimes.size() > 0 || attitudeTimes.size() > 0) {
1638 PrintTimeStats(os,
"total", totalTimes);
1704 const std::vector<PipelineOutput> &actual,
1706 if (actual.size() == 0) {
1707 std::cerr <<
"ERROR: No output! Did you specify any input images? Try --png or --generate." << std::endl;
1711 assert(expected.size() == actual.size() && expected.size() > 0);
1715 #define LOST_PIPELINE_COMPARE(precondition, errmsg, comparator, path, isBinary) do { \
1716 if (precondition) { \
1717 UserSpecifiedOutputStream pos(path, isBinary); \
1718 comparator(pos.Stream(), expected, actual, values); \
1720 std::cerr << "ERROR: Comparator not applicable: " << errmsg << std::endl; \
1725 if (values.plotRawInput !=
"") {
1727 "--plot-raw-input requires exactly 1 input image, but " + std::to_string(expected.size()) +
" many were provided.",
1728 PipelineComparatorPlotRawInput, values.plotRawInput,
true);
1731 if (values.plotInput !=
"") {
1732 LOST_PIPELINE_COMPARE(expected[0]->InputImage() && expected.size() == 1 && expected[0]->InputStars(),
1733 "--plot-input requires exactly 1 input image, and for centroids to be available on that input image. " + std::to_string(expected.size()) +
" many input images were provided.",
1734 PipelineComparatorPlotInput, values.plotInput,
true);
1736 if (values.plotExpected !=
"") {
1737 LOST_PIPELINE_COMPARE(expected[0]->InputImage() && expected.size() == 1 && expected[0]->ExpectedStars(),
1738 "--plot-expected-input requires exactly 1 input image, and for expected centroids to be available on that input image. " + std::to_string(expected.size()) +
" many input images were provided.",
1739 PipelineComparatorPlotExpected, values.plotExpected,
true);
1741 if (values.plotOutput !=
"") {
1743 "--plot-output requires exactly 1 output image, and for either centroids or star IDs to be available on that output image. " + std::to_string(actual.size()) +
" many output images were provided.",
1744 PipelineComparatorPlotOutput, values.plotOutput,
true);
1746 if (values.printExpectedCentroids !=
"") {
1748 "--print-expected-centroids requires at least 1 input with expected centroids. " + std::to_string(expected.size()) +
" many input images were provided.",
1749 PipelineComparatorPrintExpectedCentroids, values.printExpectedCentroids,
false);
1751 if (values.printInputCentroids !=
"") {
1753 "--print-input-centroids requires at least 1 input with centroids. " + std::to_string(expected.size()) +
" many input images were provided.",
1754 PipelineComparatorPrintInputCentroids, values.printInputCentroids,
false);
1756 if (values.printActualCentroids !=
"") {
1758 "--print-actual-centroids requires at least 1 output image, and for centroids to be available on the output images. " + std::to_string(actual.size()) +
" many output images were provided.",
1759 PipelineComparatorPrintActualCentroids, values.printActualCentroids,
false);
1761 if (values.plotCentroidIndices !=
"") {
1763 "--plot-centroid-indices requires exactly 1 input with image. " + std::to_string(expected.size()) +
" many inputs were provided.",
1766 if (values.compareCentroids !=
"") {
1767 LOST_PIPELINE_COMPARE(actual[0].stars && expected[0]->ExpectedStars() && values.centroidCompareThreshold,
1768 "--compare-centroids requires at least 1 output image, and for expected centroids to be available on the input image. " + std::to_string(actual.size()) +
" many output images were provided.",
1769 PipelineComparatorCentroids, values.compareCentroids,
false);
1771 if (values.compareStarIds !=
"") {
1772 LOST_PIPELINE_COMPARE(expected[0]->ExpectedStarIds() && actual[0].starIds && expected[0]->ExpectedStars(),
1773 "--compare-star-ids requires at least 1 output image, and for expected star IDs and centroids to be available on the input image. " + std::to_string(actual.size()) +
" many output images were provided.",
1774 PipelineComparatorStarIds, values.compareStarIds,
false);
1776 if (values.printAttitude !=
"") {
1778 "--print-attitude requires exactly 1 output image, and for attitude to be available on that output image. " + std::to_string(actual.size()) +
" many output images were provided.",
1779 PipelineComparatorPrintAttitude, values.printAttitude,
false);
1781 if (values.printExpectedAttitude !=
"") {
1783 "--print-expected-attitude requires exactly 1 input image, and for expected attitude to be available on that input image. " + std::to_string(expected.size()) +
" many input images were provided.",
1784 PipelineComparatorPrintExpectedAttitude, values.printExpectedAttitude,
false);
1786 if (values.compareAttitudes !=
"") {
1787 LOST_PIPELINE_COMPARE(actual[0].attitude && expected[0]->ExpectedAttitude() && values.attitudeCompareThreshold,
1788 "--compare-attitudes requires at least 1 output image, and for expected attitude to be available on the input image. " + std::to_string(actual.size()) +
" many output images were provided.",
1789 PipelineComparatorAttitude, values.compareAttitudes,
false);
1791 if (values.printSpeed !=
"") {
1794 "--print-speed requires at least 1 output image. " + std::to_string(actual.size()) +
" many output images were provided.",
1795 PipelineComparatorPrintSpeed, values.printSpeed,
false);
1798 #undef LOST_PIPELINE_COMPARE
An attitude estimation algorithm estimates the orientation of the camera based on identified stars.
The attitude (orientation) of a spacecraft.
EulerAngles ToSpherical() const
Get the euler angles from the attitude, converting from whatever format is stored.
Vec3 Rotate(const Vec3 &) const
Convert a vector from the reference frame to the body frame.
Quaternion GetQuaternion() const
Get the quaternion representing the attitude, converting from whatever format is stored.
A full description of a camera. Enough information to reconstruct the camera matrix and then some.
Vec2 SpatialToCamera(const Vec3 &) const
Converts from a 3D point in space to a 2D point on the camera sensor.
bool InSensor(const Vec2 &vector) const
Returns whether a given pixel is actually in the camera's field of view.
int XResolution() const
Width of the sensor in pixels.
int YResolution() const
Height of the sensor in pixels.
decimal Fov() const
Horizontal field of view in radians.
decimal FocalLength() const
Focal length in pixels.
A star from the Bright Star Catalog.
int magnitude
The magnitude of the star, with the decimal point shifted two places right.
A simple, fast, and pretty decent centroid algorithm.
An algorithm that detects the (x,y) coordinates of bright points in an image, called "centroids".
The result of comparing actual and expected centroids Used for debugging and benchmarking.
decimal numExtraCentroids
Stars in actual but not expected.
decimal numCorrectCentroids
Number of actual stars within the centroiding threshold of an expected star.
decimal meanError
Average distance from actual to expected centroids (in pixels) Only correct centroids are considered ...
Commannd line options when using the database command.
A slow but reliable attitude estimation algorithm.
A centroid algorithm for debugging that returns random centroids.
A star-id algorithm that returns random results. For debugging.
A "human-readable" way to represent a 3d rotation or orientation.
decimal roll
How far we roll counterclockwise. Roll is performed last (after yaw and pitch).
decimal de
Declination. How far we pitch up (or down if negative). Pitch is performed second,...
decimal ra
Right ascension. How far we yaw left. Yaw is performed first.
A star used in simulated image generation. Contains extra data about how to simulate the star.
decimal peakBrightness
the brightness density per time unit at the center of the star. 0.0 is black, 1.0 is white.
GeneratedStar(Star star, decimal peakBrightness, Vec2 motionBlurDelta)
Vec2 delta
(only meaningful with motion blur) Where the star will appear one time unit in the future.
A star-id algorithm based on assigning votes for each centroid-catalog pair then choosing the highest...
An 8-bit grayscale 2d image.
unsigned char * image
The raw pixel data in the image.
A more complicated centroid algorithm which doesn't perform much better than CenterOfGravityAlgorithm...
const unsigned char * SubDatabasePointer(int32_t magicValue) const
MultiDatabase memory layout:
static const int32_t kMagicValue
Magic value to use when storing inside a MultiDatabase.
A set of algorithms that describes all or part of the star-tracking "pipeline".
PipelineOutput Go(const PipelineInput &)
Run all stages of a pipeline.
The command line options passed when running a pipeline.
The "de facto" star-id algorithm used in many real-world missions.
A quaternion is a common way to represent a 3d rotation.
decimal Angle() const
How many radians the rotation represented by this quaternion has.
Vec3 Rotate(const Vec3 &) const
Rotate a 3d vector according to the rotation represented by the quaternion.
A faster and just as accurate attitude estimator as the Davenport Q algorithm.
std::vector< unsigned char > buffer
A "centroid" detected in an image.
decimal radiusY
Approximate vertical radius of the bright area in pixels.
Vec2 position
The (x,y) pixel coordinates in the image (top left is 0,0)
decimal radiusX
Approximate horizontal radius of the bright area in pixels.
A star idenification algorithm.
Records that a certain Star (detected in the image) corresponds to a certain CatalogStar.
A fast attitude estimator which only takes into account information from two stars.
~UserSpecifiedOutputStream()
UserSpecifiedOutputStream(std::string filePath, bool isBinary)
Create a PromptedOutputStream which will output to the given file.
Three dimensional vector with decimaling point components.
#define DECIMAL_POW(base, power)
#define LOST_PIPELINE_COMPARE(precondition, errmsg, comparator, path, isBinary)
decimal FovToFocalLength(decimal xFov, decimal xResolution)
CentroidComparison CentroidComparisonsCombine(std::vector< CentroidComparison > comparisons)
unsigned char * SurfaceToGrayscaleImage(cairo_surface_t *cairoSurface)
Convert a colored Cairo image surface into a row-major array of grayscale pixels.
decimal FocalLengthFromOptions(const PipelineOptions &values, int xResolution)
Calculate the focal length, in pixels, based on the given command line options.
const int32_t kCatalogMagicValue
std::vector< StarIdentifier > StarIdentifiers
void SerializeCatalog(SerializeContext *ser, const Catalog &catalog, bool inclMagnitude, bool inclName)
Serialize the catalog to buffer.
std::vector< CatalogStar > BscParse(std::string tsvPath)
Parse the bright star catalog from the TSV file on disk.
Catalog DeserializeCatalog(DeserializeContext *des, bool *inclMagnitudeReturn, bool *inclNameReturn)
Deserialize a catalog.
const Catalog & CatalogRead()
Read and parse the full catalog from disk. If called multiple times, will re-use the first result.
decimal MagToBrightness(int mag)
returns some relative brightness measure, which is proportional to the total number of photons receiv...
std::vector< Star > Stars
decimal DegToRad(decimal deg)
PipelineInputList(* PipelineInputFactory)()
Quaternion SphericalToQuaternion(decimal ra, decimal dec, decimal roll)
Return a quaternion that will reorient the coordinate axes so that the x-axis points at the given rig...
decimal RadToDeg(decimal rad)
void PipelineComparatorPlotCentroidIndices(std::ostream &os, const PipelineInputList &expected, const std::vector< PipelineOutput > &actual, const PipelineOptions &)
Plot an annotated image where centroids are annotated with their centroid index.
PipelineInputList GetPngPipelineInput(const PipelineOptions &values)
Create a PngPipelineInput using command line options.
void SurfacePlot(std::string description, cairo_surface_t *cairoSurface, const Stars &stars, const StarIdentifiers *starIds, const Catalog *catalog, const Attitude *attitude, double red, double green, double blue, double alpha, bool rawStarIndexes=false)
Plot information about an image onto the image using Cairo.
StarIdComparison StarIdsCompare(const StarIdentifiers &expected, const StarIdentifiers &actual, const Catalog &expectedCatalog, const Catalog &actualCatalog, decimal centroidThreshold, const Stars &expectedStars, const Stars &inputStars)
Compare expected and actual star identifications.
void SerializePairDistanceKVector(SerializeContext *ser, const Catalog &catalog, decimal minDistance, decimal maxDistance, long numBins)
Serialize a pair-distance KVector into buffer.
std::vector< MultiDatabaseEntry > MultiDatabaseDescriptor
void(* PipelineComparator)(std::ostream &os, const PipelineInputList &, const std::vector< PipelineOutput > &, const PipelineOptions &)
MultiDatabaseDescriptor GenerateDatabases(const Catalog &catalog, const DatabaseOptions &values)
Appropriately create descriptors for all requested databases according to command-line options.
SerializeContext serFromDbValues(const DatabaseOptions &values)
std::vector< std::unique_ptr< PipelineInput > > PipelineInputList
PipelineInputList GetPipelineInput(const PipelineOptions &values)
Come up with a list of pipeline inputs based on command line options.
void PipelineComparison(const PipelineInputList &expected, const std::vector< PipelineOutput > &actual, const PipelineOptions &values)
Print or otherwise analyze the results of (perhaps multiple) runs of a star tracking pipeline.
Pipeline SetPipeline(const PipelineOptions &values)
Create a pipeline from command line options.
CentroidComparison CentroidsCompare(decimal threshold, const Stars &expected, const Stars &actual)
Compare expected and actual centroids.
PipelineInputList GetGeneratedPipelineInput(const PipelineOptions &values)
Create a GeneratedPipelineInput based on the command line options in values
std::vector< CatalogStar > Catalog
cairo_surface_t * GrayscaleImageToSurface(const unsigned char *image, const int width, const int height)
std::ostream & operator<<(std::ostream &os, const Camera &camera)
Print information about the camera in machine and human-readable form.
The result of running a pipeline.
Catalog catalog
The catalog that the indices in starIds refer to.
long long centroidingTimeNs
How many nanoseconds the centroiding stage of the pipeline took.
std::unique_ptr< Stars > stars
long long attitudeEstimationTimeNs
std::unique_ptr< Attitude > attitude
std::unique_ptr< StarIdentifiers > starIds
The result of comparing an actual star identification with the true star idenification,...
int numCorrect
The number of centroids in the image which are close to an expected centroid that had an expected ide...
int numTotal
The number of centroids sufficiently close to a true expected star.
int numIncorrect
The number of centroids which were either:
A two dimensional vector with decimaling point components.
decimal MagnitudeSq() const
The square of the magnitude.
decimal Magnitude() const