4#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);
58std::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 (
double) (centroid.position.x - radiusX),
215 (
double) (centroid.position.y - radiusY),
216 (
double) (radiusX * 2),
217 (
double) (radiusY * 2));
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, (
double) (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;
298 decimal maxFovDeg = values.tetraMaxFov;
300 std::vector<uint16_t> catalogPatternStarInds =
302 std::cerr <<
"Number of pattern stars: " << catalogPatternStarInds.size() << std::endl;
306 &ser, catalog, maxFovDeg, catalogPatternStarInds, values.tetraEdgeRatioBins);
310 if (dbEntries.size() <= 1) {
311 std::cerr <<
"No database builder selected -- no database generated." << std::endl;
320 os <<
"camera_focal_length " << camera.
FocalLength() << std::endl
321 <<
"camera_fov " << camera.
Fov() << std::endl
322 <<
"camera_resolution_x " << camera.
XResolution() << std::endl
323 <<
"camera_resolution_y " << camera.
YResolution() << std::endl;
335 if ((values.pixelSize != -1) ^ (values.focalLength != 0)) {
336 std::cerr <<
"ERROR: Exactly one of --pixel-size or --focal-length were set." << std::endl;
341 if (values.pixelSize != -1 && values.fov != 20) {
342 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;
346 if (values.pixelSize == -1) {
349 return values.focalLength * 1000 / values.pixelSize;
382 : camera(camera), catalog(catalog) {
385 image.width = cairo_image_surface_get_width(cairoSurface);
386 image.height = cairo_image_surface_get_height(cairoSurface);
390 delete[] image.image;
398 cairo_surface_t *cairoSurface = NULL;
399 std::string pngPath = values.png;
401 cairoSurface = cairo_image_surface_create_from_png(pngPath.c_str());
402 std::cerr <<
"PNG Read status: " << cairo_status_to_string(cairo_surface_status(cairoSurface)) << std::endl;
403 if (cairoSurface == NULL || cairo_surface_status(cairoSurface) != CAIRO_STATUS_SUCCESS) {
407 int xResolution = cairo_image_surface_get_width(cairoSurface);
408 int yResolution = cairo_image_surface_get_height(cairoSurface);
410 Camera cam =
Camera(focalLengthPixels, xResolution, yResolution);
413 cairo_surface_destroy(cairoSurface);
462 const Vec2 d0 = p0 - pixel;
471static decimal StaticPixelBrightness(
const Vec2 &pixel,
const GeneratedStar &generatedStar,
473 const Vec2 d0 = generatedStar.position - pixel;
474 return generatedStar.peakBrightness * t *
DECIMAL_EXP(-d0.MagnitudeSq() / (2 * stddev * stddev));
503 std::default_random_engine *rng,
517 int falseStarMinMagnitude,
518 int falseStarMaxMagnitude,
521 : camera(camera), attitude(attitude), catalog(catalog) {
523 assert(falseStarMaxMagnitude <= falseStarMinMagnitude);
524 assert(perturbationStddev >=
DECIMAL(0.0));
526 image.width = camera.XResolution();
527 image.height = camera.YResolution();
530 assert(oversampling >= 1);
532 if (oversamplingPerAxis*oversamplingPerAxis != oversampling) {
533 std::cerr <<
"WARNING: oversampling was not a perfect square. Rounding up to "
534 << oversamplingPerAxis*oversamplingPerAxis <<
"." << std::endl;
536 assert(exposureTime > 0);
540 Quaternion currentAttitude = attitude.GetQuaternion();
542 Quaternion futureAttitude = motionBlurDirectionQ*currentAttitude;
543 std::vector<GeneratedStar> generatedStars;
547 decimal zeroMagPeakPhotonDensity = zeroMagTotalPhotons / (2*
DECIMAL_M_PI * starSpreadStdDev*starSpreadStdDev);
549 Catalog catalogWithFalse = catalog;
551 std::uniform_real_distribution<decimal> uniformDistribution(
DECIMAL(0.0),
DECIMAL(1.0));
552 std::uniform_int_distribution<int> magnitudeDistribution(falseStarMaxMagnitude, falseStarMinMagnitude);
553 for (
int i = 0; i < numFalseStars; i++) {
557 decimal magnitude = magnitudeDistribution(*rng);
559 catalogWithFalse.push_back(
CatalogStar(ra, de, magnitude, -1));
562 for (
int i = 0; i < (int)catalogWithFalse.size(); i++) {
563 bool isTrueStar = i < (int)catalog.size();
565 const CatalogStar &catalogStar = catalogWithFalse[i];
566 Vec3 rotated = attitude.Rotate(catalogWithFalse[i].spatial);
567 if (rotated.
x <= 0) {
570 Vec2 camCoords = camera.SpatialToCamera(rotated);
572 if (camera.InSensor(camCoords)) {
573 Vec3 futureSpatial = futureAttitude.
Rotate(catalogWithFalse[i].spatial);
574 Vec2 delta = camera.SpatialToCamera(futureSpatial) - camCoords;
575 if (!motionBlurEnabled) {
591 generatedStars.push_back(
GeneratedStar(star, peakBrightnessPerTime, delta));
599 expectedStars.push_back(star);
602 expectedStarIds.push_back(
StarIdentifier(expectedStars.size()-1, i));
606 Star inputStar = star;
607 if (perturbationStddev >
DECIMAL(0.0)) {
608 std::normal_distribution<decimal> perturbation1DDistribution(
DECIMAL(0.0), perturbationStddev);
610 inputStar.
position.
x += std::max(std::min(perturbation1DDistribution(*rng), 2*perturbationStddev), -2*perturbationStddev);
611 inputStar.
position.
y += std::max(std::min(perturbation1DDistribution(*rng), 2*perturbationStddev), -2*perturbationStddev);
614 if (camera.InSensor(inputStar.
position)
616 && (cutoffMag >= 10000
618 || std::bernoulli_distribution((
double) CentroidImagingProbability(catalogStar.
magnitude, cutoffMag))(*rng))) {
619 inputStars.push_back(inputStar);
632 std::vector<decimal> photonsBuffer(image.width*image.height, 0);
636 Vec2 earliestPosition = star.position - star.delta*(exposureTime/
DECIMAL(2.0) + readoutTime/
DECIMAL(2.0));
637 Vec2 latestPosition = star.position + star.delta*(exposureTime/
DECIMAL(2.0) + readoutTime/
DECIMAL(2.0));
638 int xMin = std::max(0, (
int)std::min(earliestPosition.
x - star.radiusX, latestPosition.
x - star.radiusX));
639 int xMax = std::min(image.width-1, (
int)std::max(earliestPosition.
x + star.radiusX, latestPosition.
x + star.radiusX));
640 int yMin = std::max(0, (
int)std::min(earliestPosition.
y - star.radiusX, latestPosition.
y - star.radiusX));
641 int yMax = std::min(image.height-1, (
int)std::max(earliestPosition.
y + star.radiusX, latestPosition.
y + star.radiusX));
645 decimal oversamplingBrightnessFactor = oversamplingPerAxis*oversamplingPerAxis;
651 for (
int xPixel = xMin; xPixel <= xMax; xPixel++) {
652 for (
int yPixel = yMin; yPixel <= yMax; yPixel++) {
655 decimal readoutOffset = readoutTime * (yPixel - image.height/
DECIMAL(2.0)) / image.height;
660 for (
int xSample = 0; xSample < oversamplingPerAxis; xSample++) {
661 for (
int ySample = 0; ySample < oversamplingPerAxis; ySample++) {
666 if (motionBlurEnabled) {
668 (MotionBlurredPixelBrightness({x, y}, star, tEnd, starSpreadStdDev)
669 - MotionBlurredPixelBrightness({x, y}, star, tStart, starSpreadStdDev))
670 / oversamplingBrightnessFactor;
672 curPhotons = StaticPixelBrightness({x, y}, star, exposureTime, starSpreadStdDev)
673 / oversamplingBrightnessFactor;
676 assert(
DECIMAL(0.0) <= curPhotons);
678 photonsBuffer[xPixel + yPixel*image.width] += curPhotons;
685 std::normal_distribution<decimal> standardNormalDist(
DECIMAL(0.0),
DECIMAL(1.0));
688 imageData = std::vector<unsigned char>(image.width*image.height);
689 image.image = imageData.data();
690 for (
int i = 0; i < image.width * image.height; i++) {
694 curBrightness += darkCurrent;
697 curBrightness += readNoiseStdDev * standardNormalDist(*rng);
700 long quantizedPhotons;
706 decimal photons = photonsBuffer[i];
708 std::cout <<
"ERROR: One of the pixels had too many photons. Generated image would not be physically accurate, exiting." << std::endl;
713 std::poisson_distribution<long> shotNoiseDist((
double) photons);
714 quantizedPhotons = shotNoiseDist(*rng);
716 quantizedPhotons = 0;
719 quantizedPhotons = round(photonsBuffer[i]);
721 curBrightness += quantizedPhotons / saturationPhotons;
733static Attitude RandomAttitude(std::default_random_engine* pReng) {
734 std::uniform_real_distribution<decimal> randomAngleDistribution(0, 1);
758 if (values.timeSeed) {
761 seed = values.generateSeed;
764 std::default_random_engine attitudeRng(seed);
765 std::default_random_engine noiseRng(seed);
774 DegToRad(values.generateBlurRoll)));
780 for (
int i = 0; i < values.generate; i++) {
784 if (values.generateRandomAttitudes) {
785 inputAttitude = RandomAttitude(&attitudeRng);
787 inputAttitude = attitude;
793 Camera(focalLength, values.generateXRes, values.generateYRes),
796 values.generateCentroidsOnly,
797 values.generateZeroMagPhotons,
798 values.generateSpreadStdDev,
799 values.generateSaturationPhotons,
800 values.generateDarkCurrent,
801 values.generateReadNoiseStdDev,
803 values.generateExposure,
804 values.generateReadoutTime,
805 values.generateShotNoise,
806 values.generateOversampling,
807 values.generateNumFalseStars,
808 (values.generateFalseMinMag * 100),
809 (values.generateFalseMaxMag * 100),
810 (values.generateCutoffMag * 100),
811 values.generatePerturbationStddev);
813 result.push_back(std::unique_ptr<PipelineInput>(curr));
828 if (values.png !=
"") {
842 unsigned char *database)
844 if (centroidAlgorithm) {
845 this->centroidAlgorithm = std::unique_ptr<CentroidAlgorithm>(centroidAlgorithm);
847 if (starIdAlgorithm) {
848 this->starIdAlgorithm = std::unique_ptr<StarIdAlgorithm>(starIdAlgorithm);
850 if (attitudeEstimationAlgorithm) {
851 this->attitudeEstimationAlgorithm = std::unique_ptr<AttitudeEstimationAlgorithm>(attitudeEstimationAlgorithm);
854 this->database = std::unique_ptr<unsigned char[]>(database);
869 if (values.centroidAlgo ==
"dummy") {
870 result.centroidAlgorithm = std::unique_ptr<CentroidAlgorithm>(
new DummyCentroidAlgorithm(values.centroidDummyNumStars));
871 }
else if (values.centroidAlgo ==
"cog") {
873 }
else if (values.centroidAlgo ==
"iwcog") {
875 }
else if (values.centroidAlgo !=
"") {
876 std::cout <<
"Illegal centroid algorithm." << std::endl;
881 if (values.centroidMagFilter > 0) result.centroidMinMagnitude = values.centroidMagFilter;
882 if (values.centroidFilterBrightest > 0) result.centroidMinStars = values.centroidFilterBrightest;
885 if (values.databasePath !=
"") {
887 fs.open(values.databasePath, std::fstream::in | std::fstream::binary);
889 long length = fs.tellg();
892 std::cerr <<
"Error reading database! " << strerror(errno) << std::endl;
895 std::cerr <<
"Reading " << length <<
" bytes of database" << std::endl;
896 result.database = std::unique_ptr<unsigned char[]>(
new unsigned char[length]);
897 fs.read((
char *)result.database.get(), length);
898 std::cerr <<
"Done" << std::endl;
901 if (values.idAlgo ==
"dummy") {
903 }
else if (values.idAlgo ==
"gv") {
905 }
else if (values.idAlgo ==
"py") {
907 DegToRad(values.angularTolerance), values.estimatedNumFalseStars,
908 values.maxMismatchProb, 1000));
909 }
else if (values.idAlgo ==
"tetra") {
911 if (values.tetraPairingMode ==
"triad") {
913 }
else if (values.tetraPairingMode ==
"centroid-radius") {
916 std::cerr <<
"Illegal tetra pairing mode: " << values.tetraPairingMode << std::endl;
919 result.starIdAlgorithm = std::unique_ptr<StarIdAlgorithm>(
920 new TetraStarIdAlgorithm(pairingMode, values.tetraPatternSearchRange, values.tetraPatternMatchTolerance));
921 }
else if (values.idAlgo !=
"") {
922 std::cout <<
"Illegal id algorithm." << std::endl;
926 if (values.attitudeAlgo ==
"dqm") {
927 result.attitudeEstimationAlgorithm = std::unique_ptr<AttitudeEstimationAlgorithm>(
new DavenportQAlgorithm());
928 }
else if (values.attitudeAlgo ==
"triad") {
929 result.attitudeEstimationAlgorithm = std::unique_ptr<AttitudeEstimationAlgorithm>(
new TriadAlgorithm());
930 }
else if (values.attitudeAlgo ==
"quest") {
931 result.attitudeEstimationAlgorithm = std::unique_ptr<AttitudeEstimationAlgorithm>(
new QuestAlgorithm());
932 }
else if (values.attitudeAlgo !=
"") {
933 std::cout <<
"Illegal attitude algorithm." << std::endl;
959 if (catalogBuffer != NULL) {
963 std::cerr <<
"WARNING: That database does not include a catalog. Proceeding with the full catalog." << std::endl;
970 if (centroidAlgorithm && inputImage) {
973 std::chrono::time_point<std::chrono::steady_clock> start = std::chrono::steady_clock::now();
976 Stars unfilteredStars = centroidAlgorithm->Go(inputImage->
image, inputImage->
width, inputImage->
height);
978 std::chrono::time_point<std::chrono::steady_clock> end = std::chrono::steady_clock::now();
979 result.
centroidingTimeNs = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
982 int minMagnitude = centroidMinMagnitude;
983 if (centroidMinStars > 0
985 && centroidMinStars < (
int)unfilteredStars.size()) {
987 Stars magSortedStars = unfilteredStars;
989 std::sort(magSortedStars.begin(), magSortedStars.end(), [](
const Star &a,
const Star &b) { return a.magnitude > b.magnitude; });
990 minMagnitude = std::max(minMagnitude, magSortedStars[centroidMinStars - 1].magnitude);
993 Stars *filteredStars =
new std::vector<Star>();
994 for (
const Star &star : unfilteredStars) {
995 assert(star.magnitude >= 0);
997 if (star.magnitude >= minMagnitude) {
998 filteredStars->push_back(star);
1001 result.
stars = std::unique_ptr<Stars>(filteredStars);
1002 inputStars = filteredStars;
1006 inputStarIds = NULL;
1008 }
else if (centroidAlgorithm) {
1009 std::cerr <<
"ERROR: Centroid algorithm specified, but no input image to run it on." << std::endl;
1013 if (starIdAlgorithm && database && inputStars && input.
InputCamera()) {
1015 std::chrono::time_point<std::chrono::steady_clock> start = std::chrono::steady_clock::now();
1017 result.
starIds = std::unique_ptr<StarIdentifiers>(
new std::vector<StarIdentifier>(
1018 starIdAlgorithm->Go(database.get(), *inputStars, result.
catalog, *input.
InputCamera())));
1020 std::chrono::time_point<std::chrono::steady_clock> end = std::chrono::steady_clock::now();
1021 result.
starIdTimeNs = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
1023 inputStarIds = result.
starIds.get();
1024 }
else if (starIdAlgorithm) {
1025 std::cerr <<
"ERROR: Star ID algorithm specified but cannot run because database, centroids, or camera are missing." << std::endl;
1029 if (attitudeEstimationAlgorithm && inputStarIds && input.
InputCamera()) {
1031 std::chrono::time_point<std::chrono::steady_clock> start = std::chrono::steady_clock::now();
1033 result.
attitude = std::unique_ptr<Attitude>(
1036 std::chrono::time_point<std::chrono::steady_clock> end = std::chrono::steady_clock::now();
1038 }
else if (attitudeEstimationAlgorithm) {
1039 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;
1048 std::vector<PipelineOutput> result;
1051 for (
const std::unique_ptr<PipelineInput> &input : inputs) {
1052 result.push_back(
Go(*input));
1093static std::multimap<int, int> FindClosestCentroids(
decimal threshold,
1096 std::multimap<int, int> result;
1098 for (
int i = 0; i < (int)one.size(); i++) {
1099 std::vector<std::pair<decimal, int>> closest;
1100 for (
int k = 0; k < (int)two.size(); k++) {
1101 decimal currDistance = (one[i].position - two[k].position).Magnitude();
1102 if (currDistance <= threshold) {
1103 closest.emplace_back(currDistance, k);
1106 std::sort(closest.begin(), closest.end());
1107 for (
const std::pair<decimal, int> &pair : closest) {
1108 result.emplace(i, pair.second);
1121 const Stars &expected,
1122 const Stars &actual) {
1130 std::multimap<int, int> actualToExpected = FindClosestCentroids(threshold, actual, expected);
1132 for (
int i = 0; i < (int)actualToExpected.size(); i++) {
1133 auto closest = actualToExpected.find(i);
1134 if (closest == actualToExpected.end()) {
1137 result.
meanError += (actual[i].position - expected[closest->second].position).Magnitude();
1147 assert(comparisons.size() > 0);
1152 result.
meanError += comparison.meanError;
1169 const Stars &expectedStars,
const Stars &inputStars) {
1180 std::vector<int> expectedCatalogIndices(expectedStars.size(), -1);
1182 assert(0 <= starId.starIndex && starId.starIndex <= (
int)expectedStars.size());
1183 assert(0 <= starId.catalogIndex && starId.catalogIndex <= (
int)expectedCatalog.size());
1184 expectedCatalogIndices[starId.starIndex] = starId.catalogIndex;
1189 std::multimap<int, int> inputToExpectedCentroids = FindClosestCentroids(centroidThreshold, inputStars, expectedStars);
1194 for (
int i = 0; i < (int)inputStars.size(); i++) {
1196 auto closestRange = inputToExpectedCentroids.equal_range(i);
1198 for (
auto it = closestRange.first; it != closestRange.second; it++) {
1199 if (expectedCatalogIndices[it->second] != -1) {
1211 std::vector<bool> identifiedInputCentroids(inputStars.size(),
false);
1214 assert(!identifiedInputCentroids[starId.starIndex]);
1215 identifiedInputCentroids[starId.starIndex] =
true;
1216 assert(0 <= starId.starIndex && starId.starIndex <= (
int)inputStars.size());
1217 assert(0 <= starId.catalogIndex && starId.catalogIndex <= (
int)actualCatalog.size());
1220 auto expectedCentroidsInRange = inputToExpectedCentroids.equal_range(starId.starIndex);
1222 for (
auto it = expectedCentroidsInRange.first; it != expectedCentroidsInRange.second; it++) {
1223 int expectedCatalogIndex = expectedCatalogIndices[it->second];
1224 if (expectedCatalogIndex != -1
1225 && expectedCatalog[expectedCatalogIndex].name == actualCatalog[starId.catalogIndex].name) {
1248 const std::vector<PipelineOutput> &,
1252static cairo_status_t OstreamPlotter(
void *closure,
const unsigned char *data,
unsigned int length) {
1253 std::ostream *os = (std::ostream *)closure;
1254 os->write((
const char *)data, length);
1255 return CAIRO_STATUS_SUCCESS;
1259static void PipelineComparatorPlotRawInput(std::ostream &os,
1261 const std::vector<PipelineOutput> &,
1262 const PipelineOptions &) {
1264 cairo_surface_t *cairoSurface = expected[0]->InputImageSurface();
1265 cairo_surface_write_to_png_stream(cairoSurface, OstreamPlotter, &os);
1266 cairo_surface_destroy(cairoSurface);
1271static void PipelineComparatorPlotInput(std::ostream &os,
1273 const std::vector<PipelineOutput> &,
1275 cairo_surface_t *cairoSurface = expected[0]->InputImageSurface();
1276 assert(expected[0]->InputStars() != NULL);
1279 *expected[0]->InputStars(),
1280 expected[0]->InputStarIds(),
1281 &expected[0]->GetCatalog(),
1282 expected[0]->InputAttitude(),
1284 0.0, 1.0, 0.0, 0.6);
1285 cairo_surface_write_to_png_stream(cairoSurface, OstreamPlotter, &os);
1286 cairo_surface_destroy(cairoSurface);
1289static void PipelineComparatorPlotExpected(std::ostream &os,
1291 const std::vector<PipelineOutput> &,
1293 cairo_surface_t *cairoSurface = expected[0]->InputImageSurface();
1294 assert(expected[0]->ExpectedStars() != NULL);
1297 *expected[0]->ExpectedStars(),
1298 expected[0]->ExpectedStarIds(),
1299 &expected[0]->GetCatalog(),
1300 expected[0]->ExpectedAttitude(),
1302 0.2, 0.5, 1.0, 0.7);
1303 cairo_surface_write_to_png_stream(cairoSurface, OstreamPlotter, &os);
1304 cairo_surface_destroy(cairoSurface);
1308static void PipelineComparatorCentroids(std::ostream &os,
1310 const std::vector<PipelineOutput> &actual,
1312 int size = (int)expected.size();
1314 decimal threshold = values.centroidCompareThreshold;
1316 std::vector<CentroidComparison> comparisons;
1317 for (
int i = 0; i < size; i++) {
1319 *(expected[i]->ExpectedStars()),
1320 *(actual[i].stars)));
1324 os <<
"centroids_num_correct " << result.numCorrectCentroids << std::endl
1325 <<
"centroids_num_extra " << result.numExtraCentroids << std::endl
1326 <<
"centroids_mean_error " << result.meanError << std::endl;
1329static void PrintCentroids(
const std::string &prefix,
1332 const std::vector<Stars> &starses,
1335 assert(starses.size() > 0);
1337 for (
const Stars &stars : starses) {
1338 avgNumStars += stars.size();
1340 avgNumStars /= starses.size();
1342 os <<
"num_" << prefix <<
"_centroids " << avgNumStars << std::endl;
1343 if (starses.size() == 1) {
1344 const Stars &stars = starses[0];
1345 for (
int i = 0; i < (int)stars.size(); i++) {
1346 os << prefix <<
"_centroid_" << i <<
"_x " << stars[i].position.x << std::endl;
1347 os << prefix <<
"_centroid_" << i <<
"_y " << stars[i].position.y << std::endl;
1350 if (starId.starIndex == i) {
1351 os << prefix <<
"_centroid_" << i <<
"_id " << catalog[starId.catalogIndex].name << std::endl;
1360static void PipelineComparatorPrintExpectedCentroids(std::ostream &os,
1362 const std::vector<PipelineOutput> &,
1364 assert(expected.size() > 0);
1365 assert(expected[0]->ExpectedStars());
1367 std::vector<Stars> expectedStarses;
1368 for (
const auto &input : expected) {
1369 expectedStarses.push_back(*input->ExpectedStars());
1371 PrintCentroids(
"expected",
1373 expected[0]->GetCatalog(),
1375 expected[0]->ExpectedStarIds());
1378static void PipelineComparatorPrintInputCentroids(std::ostream &os,
1380 const std::vector<PipelineOutput> &,
1382 assert(expected.size() > 0);
1383 assert(expected[0]->InputStars());
1385 std::vector<Stars> inputStarses;
1386 for (
const auto &input : expected) {
1387 inputStarses.push_back(*input->InputStars());
1389 PrintCentroids(
"input",
1391 expected[0]->GetCatalog(),
1393 expected[0]->InputStarIds());
1396static void PipelineComparatorPrintActualCentroids(std::ostream &os,
1398 const std::vector<PipelineOutput> &actual,
1400 assert(actual.size() > 0);
1401 assert(actual[0].stars);
1403 std::vector<Stars> actualStarses;
1404 for (
const auto &output : actual) {
1405 actualStarses.push_back(*output.stars);
1407 PrintCentroids(
"actual",
1411 actual[0].starIds.get());
1413 if (expected.size() == 1 && expected[0]->ExpectedStars() && expected[0]->ExpectedStarIds()) {
1415 const Stars &actualStars = *actual[0].stars;
1416 const Stars &expectedStars = *expected[0]->ExpectedStars();
1417 std::multimap<int, int> actualToExpectedCentroids = FindClosestCentroids(values.centroidCompareThreshold, actualStars, expectedStars);
1418 for (
int i = 0; i < (int)actualStars.size(); i++) {
1419 auto range = actualToExpectedCentroids.equal_range(i);
1420 auto it = range.first;
1421 auto end = range.second;
1426 int expectedCentroidIndex = it->second;
1427 bool foundIt =
false;
1428 for (
const StarIdentifier &starId : *expected[0]->ExpectedStarIds()) {
1429 if (starId.starIndex == expectedCentroidIndex) {
1431 int expectedName = expected[0]->GetCatalog()[starId.catalogIndex].name;
1432 std::cout <<
"actual_centroid_" << i <<
"_expected_id_" << j <<
" " << expectedName << std::endl;
1445 const std::vector<PipelineOutput> &actual,
1447 const Stars &stars = actual[0].stars ? *actual[0].stars : *expected[0]->InputStars();
1449 for (
int i = 0; i < (int)stars.size(); i++) {
1452 cairo_surface_t *cairoSurface = expected[0]->InputImageSurface();
1463 cairo_surface_write_to_png_stream(cairoSurface, OstreamPlotter, &os);
1464 cairo_surface_destroy(cairoSurface);
1468static void PipelineComparatorPlotOutput(std::ostream &os,
1470 const std::vector<PipelineOutput> &actual,
1471 const PipelineOptions &) {
1473 cairo_surface_t *cairoSurface = expected[0]->InputImageSurface();
1476 actual[0].stars ? *actual[0].stars : *expected[0]->InputStars(),
1477 actual[0].starIds.get(),
1479 actual[0].attitude.get(),
1481 1.0, 0.0, 0.0, 0.5);
1482 cairo_surface_write_to_png_stream(cairoSurface, OstreamPlotter, &os);
1483 cairo_surface_destroy(cairoSurface);
1487static void PipelineComparatorStarIds(std::ostream &os,
1489 const std::vector<PipelineOutput> &actual,
1491 int numImagesCorrect = 0;
1492 int numImagesIncorrect = 0;
1493 int numImagesTotal = expected.size();
1494 for (
int i = 0; i < numImagesTotal; i++) {
1498 assert(actual[i].stars.get() || expected[i]->InputStars());
1500 const Stars &inputStars = actual[i].stars.get()
1501 ? *actual[i].stars.get()
1502 : *expected[i]->InputStars();
1504 StarIdsCompare(*expected[i]->ExpectedStarIds(), *actual[i].starIds,
1505 expected[i]->GetCatalog(), actual[i].catalog,
1506 values.centroidCompareThreshold, *expected[i]->ExpectedStars(), inputStars);
1508 if (numImagesTotal == 1) {
1509 os <<
"starid_num_correct " << comparison.numCorrect << std::endl;
1510 os <<
"starid_num_incorrect " << comparison.numIncorrect << std::endl;
1511 os <<
"starid_num_total " << comparison.numTotal << std::endl;
1514 if (comparison.numCorrect > 0 && comparison.numIncorrect == 0) {
1517 if (comparison.numIncorrect > 0) {
1518 numImagesIncorrect++;
1523 os <<
"starid_num_images_correct " << numImagesCorrect << std::endl;
1524 os <<
"starid_num_images_incorrect " << numImagesIncorrect << std::endl;
1527static void PrintAttitude(std::ostream &os,
const std::string &prefix,
const Attitude &attitude) {
1528 if (attitude.IsKnown()) {
1529 os << prefix <<
"attitude_known 1" << std::endl;
1532 os << prefix <<
"attitude_ra " <<
RadToDeg(spherical.ra) << std::endl;
1533 os << prefix <<
"attitude_de " <<
RadToDeg(spherical.de) << std::endl;
1534 os << prefix <<
"attitude_roll " <<
RadToDeg(spherical.roll) << std::endl;
1537 os << prefix <<
"attitude_i " << q.
i << std::endl;
1538 os << prefix <<
"attitude_j " << q.j << std::endl;
1539 os << prefix <<
"attitude_k " << q.k << std::endl;
1540 os << prefix <<
"attitude_real " << q.real << std::endl;
1543 os << prefix <<
"attitude_known 0" << std::endl;
1548static void PipelineComparatorPrintAttitude(std::ostream &os,
1550 const std::vector<PipelineOutput> &actual,
1552 assert(actual.size() == 1);
1553 assert(actual[0].attitude);
1554 PrintAttitude(os,
"", *actual[0].attitude);
1557static void PipelineComparatorPrintExpectedAttitude(std::ostream &os,
1559 const std::vector<PipelineOutput> &,
1561 assert(expected.size() == 1);
1562 assert(expected[0]->ExpectedAttitude());
1563 PrintAttitude(os,
"expected_", *expected[0]->ExpectedAttitude());
1567static void PipelineComparatorAttitude(std::ostream &os,
1569 const std::vector<PipelineOutput> &actual,
1577 decimal attitudeErrorSum = 0.0f;
1579 int numIncorrect = 0;
1581 for (
int i = 0; i < (int)expected.size(); i++) {
1582 if (actual[i].attitude->IsKnown()) {
1583 Quaternion expectedQuaternion = expected[i]->ExpectedAttitude()->GetQuaternion();
1584 Quaternion actualQuaternion = actual[i].attitude->GetQuaternion();
1586 assert(attitudeError >= 0);
1588 if (attitudeError <= angleThreshold) {
1589 attitudeErrorSum += attitudeError;
1597 decimal attitudeErrorMean =
DECIMAL(attitudeErrorSum) / numCorrect;
1599 decimal fractionIncorrect =
DECIMAL(numIncorrect) / expected.size();
1601 os <<
"attitude_error_mean " << attitudeErrorMean << std::endl;
1602 os <<
"attitude_availability " << fractionCorrect << std::endl;
1603 os <<
"attitude_error_rate " << fractionIncorrect << std::endl;
1606static void PrintTimeStats(std::ostream &os,
const std::string &prefix,
const std::vector<long long> ×) {
1607 assert(times.size() > 0);
1611 long long min = LONG_MAX;
1613 for (
int i = 0; i < (int)times.size(); i++) {
1614 assert(times[i] > 0);
1616 min = std::min(min, times[i]);
1617 max = std::max(max, times[i]);
1619 long average = sum / times.size();
1620 std::vector<long long> sortedTimes = times;
1621 std::sort(sortedTimes.begin(), sortedTimes.end());
1625 int ninetyFiveIndex = (int)std::ceil(0.95 * times.size()) - 1;
1626 assert(ninetyFiveIndex >= 0);
1627 long long ninetyFifthPercentile = sortedTimes[ninetyFiveIndex];
1629 os << prefix <<
"_average_ns " << average << std::endl;
1630 os << prefix <<
"_min_ns " << min << std::endl;
1631 os << prefix <<
"_max_ns " << max << std::endl;
1632 os << prefix <<
"_95%_ns " << ninetyFifthPercentile << std::endl;
1636static void PipelineComparatorPrintSpeed(std::ostream &os,
1638 const std::vector<PipelineOutput> &actual,
1640 std::vector<long long> centroidingTimes;
1641 std::vector<long long> starIdTimes;
1642 std::vector<long long> attitudeTimes;
1643 std::vector<long long> totalTimes;
1644 for (
int i = 0; i < (int)actual.size(); i++) {
1645 long long totalTime = 0;
1646 if (actual[i].centroidingTimeNs > 0) {
1647 centroidingTimes.push_back(actual[i].centroidingTimeNs);
1648 totalTime += actual[i].centroidingTimeNs;
1650 if (actual[i].starIdTimeNs > 0) {
1651 starIdTimes.push_back(actual[i].starIdTimeNs);
1652 totalTime += actual[i].starIdTimeNs;
1654 if (actual[i].attitudeEstimationTimeNs > 0) {
1655 attitudeTimes.push_back(actual[i].attitudeEstimationTimeNs);
1656 totalTime += actual[i].attitudeEstimationTimeNs;
1658 totalTimes.push_back(totalTime);
1660 if (centroidingTimes.size() > 0) {
1661 PrintTimeStats(os,
"centroiding", centroidingTimes);
1663 if (starIdTimes.size() > 0) {
1664 PrintTimeStats(os,
"starid", starIdTimes);
1666 if (attitudeTimes.size() > 0) {
1667 PrintTimeStats(os,
"attitude", attitudeTimes);
1669 if (centroidingTimes.size() > 0 || starIdTimes.size() > 0 || attitudeTimes.size() > 0) {
1670 PrintTimeStats(os,
"total", totalTimes);
1736 const std::vector<PipelineOutput> &actual,
1738 if (actual.size() == 0) {
1739 std::cerr <<
"ERROR: No output! Did you specify any input images? Try --png or --generate." << std::endl;
1743 assert(expected.size() == actual.size() && expected.size() > 0);
1747#define LOST_PIPELINE_COMPARE(precondition, errmsg, comparator, path, isBinary) do { \
1748 if (precondition) { \
1749 UserSpecifiedOutputStream pos(path, isBinary); \
1750 comparator(pos.Stream(), expected, actual, values); \
1752 std::cerr << "ERROR: Comparator not applicable: " << errmsg << std::endl; \
1757 if (values.plotRawInput !=
"") {
1759 "--plot-raw-input requires exactly 1 input image, but " + std::to_string(expected.size()) +
" many were provided.",
1760 PipelineComparatorPlotRawInput, values.plotRawInput,
true);
1763 if (values.plotInput !=
"") {
1764 LOST_PIPELINE_COMPARE(expected[0]->InputImage() && expected.size() == 1 && expected[0]->InputStars(),
1765 "--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.",
1766 PipelineComparatorPlotInput, values.plotInput,
true);
1768 if (values.plotExpected !=
"") {
1769 LOST_PIPELINE_COMPARE(expected[0]->InputImage() && expected.size() == 1 && expected[0]->ExpectedStars(),
1770 "--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.",
1771 PipelineComparatorPlotExpected, values.plotExpected,
true);
1773 if (values.plotOutput !=
"") {
1775 "--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.",
1776 PipelineComparatorPlotOutput, values.plotOutput,
true);
1778 if (values.printExpectedCentroids !=
"") {
1780 "--print-expected-centroids requires at least 1 input with expected centroids. " + std::to_string(expected.size()) +
" many input images were provided.",
1781 PipelineComparatorPrintExpectedCentroids, values.printExpectedCentroids,
false);
1783 if (values.printInputCentroids !=
"") {
1785 "--print-input-centroids requires at least 1 input with centroids. " + std::to_string(expected.size()) +
" many input images were provided.",
1786 PipelineComparatorPrintInputCentroids, values.printInputCentroids,
false);
1788 if (values.printActualCentroids !=
"") {
1790 "--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.",
1791 PipelineComparatorPrintActualCentroids, values.printActualCentroids,
false);
1793 if (values.plotCentroidIndices !=
"") {
1795 "--plot-centroid-indices requires exactly 1 input with image. " + std::to_string(expected.size()) +
" many inputs were provided.",
1798 if (values.compareCentroids !=
"") {
1799 LOST_PIPELINE_COMPARE(actual[0].stars && expected[0]->ExpectedStars() && values.centroidCompareThreshold,
1800 "--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.",
1801 PipelineComparatorCentroids, values.compareCentroids,
false);
1803 if (values.compareStarIds !=
"") {
1804 LOST_PIPELINE_COMPARE(expected[0]->ExpectedStarIds() && actual[0].starIds && expected[0]->ExpectedStars(),
1805 "--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.",
1806 PipelineComparatorStarIds, values.compareStarIds,
false);
1808 if (values.printAttitude !=
"") {
1810 "--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.",
1811 PipelineComparatorPrintAttitude, values.printAttitude,
false);
1813 if (values.printExpectedAttitude !=
"") {
1815 "--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.",
1816 PipelineComparatorPrintExpectedAttitude, values.printExpectedAttitude,
false);
1818 if (values.compareAttitudes !=
"") {
1819 LOST_PIPELINE_COMPARE(actual[0].attitude && expected[0]->ExpectedAttitude() && values.attitudeCompareThreshold,
1820 "--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.",
1821 PipelineComparatorAttitude, values.compareAttitudes,
false);
1823 if (values.printSpeed !=
"") {
1826 "--print-speed requires at least 1 output image. " + std::to_string(actual.size()) +
" many output images were provided.",
1827 PipelineComparatorPrintSpeed, values.printSpeed,
false);
1830#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.
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.
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.
The command line options passed when running a pipeline.
PipelineOutput Go(const PipelineInput &)
Run all stages of 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.
decimal SmallestAngle() const
Returns the smallest angle that can be used to represent 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 star idenification algorithm.
Records that a certain Star (detected in the image) corresponds to a certain CatalogStar.
A "centroid" detected in an image.
decimal radiusY
Approximate vertical radius of the bright area in pixels.
Star(decimal x, decimal y, decimal radiusX, decimal radiusY, int magnitude)
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.
static const int32_t kMagicValue
Magic value to use when storing inside a MultiDatabase.
Tetra star identification algorithm based on the one originally proposed by Brown & Stubis Creates pa...
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.
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...
std::vector< MultiDatabaseEntry > MultiDatabaseDescriptor
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.
void SerializeTetraDatabase(SerializeContext *ser, const Catalog &catalog, decimal maxFovDeg, const std::vector< uint16_t > &catalogPatternStarInds, int numEdgeRatioBins)
PipelineInputList GetPngPipelineInput(const PipelineOptions &values)
Create a PngPipelineInput using command line options.
std::vector< CatalogStar > BscParse(std::string tsvPath)
Parse the bright star catalog from the TSV file on disk.
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< uint16_t > TetraPreprocessCatalog(const Catalog &catalog, const decimal maxFovDeg, int numPatternStarsPerFov)
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.
std::ostream & operator<<(std::ostream &output, const Vec2 &vec)
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)
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