Add lazy tiled 2MASS catalog support
This commit is contained in:
1 parent
747f8eb695
commit
b0ca7c6df1
6 files changed
+404
-42
No files matched your search
@@ -33,6 +33,26 @@ as the sample processor. The downloader fails if an IRSA response reaches
|
|||||||
`--outrows`, so crowded fields cannot be silently truncated. It deletes raw
|
`--outrows`, so crowded fields cannot be silently truncated. It deletes raw
|
||||||
tile tables after successful cleaning unless `--keep-raw` is passed.
|
tile tables after successful cleaning unless `--keep-raw` is passed.
|
||||||
|
|
||||||
|
## Renderer use
|
||||||
|
|
||||||
|
Use the directory mode rather than `--catalog` for this partitioned dataset:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make ENABLE_PNG=1 SPACETIME=minkowski all
|
||||||
|
./build/minkowski_sky --all-sky-catalog assets/2mass/processed/all_sky \
|
||||||
|
--fov-deg 2 --look-ra-deg 180 --look-dec-deg -75 \
|
||||||
|
--exposure 1e15 \
|
||||||
|
--output output/imgs/2mass_all_sky_flat_2deg.png
|
||||||
|
```
|
||||||
|
|
||||||
|
The renderer lazily loads only tiles touched by source triangles in the frame.
|
||||||
|
It prefetches them serially, so OpenMP splat workers read immutable tile data.
|
||||||
|
Missing tiles are skipped until the downloader has produced them. Fully
|
||||||
|
contained tiles skip per-star source-triangle containment; partial tiles retain
|
||||||
|
that exact check. For 2MASS apparent-solid-angle amplitudes, start with
|
||||||
|
`--exposure 1e15`; use PNG (`make ENABLE_PNG=1`) for substantially smaller
|
||||||
|
single-frame output than binary PPM.
|
||||||
|
|
||||||
The two existing fields imply roughly 15--17 GiB of cleaned CSV for the full
|
The two existing fields imply roughly 15--17 GiB of cleaned CSV for the full
|
||||||
PSC. Their raw response rows imply 70--75 GiB if every source appeared once.
|
PSC. Their raw response rows imply 70--75 GiB if every source appeared once.
|
||||||
The cover grid uses substantially fewer cone areas than the old fixed-grid
|
The cover grid uses substantially fewer cone areas than the old fixed-grid
|
||||||
|
|||||||
+235
-2
@@ -1,6 +1,7 @@
|
|||||||
#include "catalog.h"
|
#include "catalog.h"
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
#include <limits.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -65,8 +66,11 @@ int catalog_load_csv(StarCatalog *catalog, const char *path)
|
|||||||
FILE *file = fopen(path, "r");
|
FILE *file = fopen(path, "r");
|
||||||
char line[256];
|
char line[256];
|
||||||
size_t capacity = 0;
|
size_t capacity = 0;
|
||||||
catalog->stars = NULL;
|
if (catalog == NULL) {
|
||||||
catalog->count = 0;
|
if (file != NULL) fclose(file);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*catalog = (StarCatalog){0};
|
||||||
if (file == NULL || fgets(line, sizeof line, file) == NULL) goto fail;
|
if (file == NULL || fgets(line, sizeof line, file) == NULL) goto fail;
|
||||||
|
|
||||||
while (fgets(line, sizeof line, file) != NULL) {
|
while (fgets(line, sizeof line, file) != NULL) {
|
||||||
@@ -99,9 +103,238 @@ fail:
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static size_t tile_index(int ra_index, int dec_index)
|
||||||
|
{
|
||||||
|
return (size_t)dec_index * CATALOG_ALL_SKY_RA_TILES + ra_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int load_tile(StarCatalog *catalog, int ra_index, int dec_index)
|
||||||
|
{
|
||||||
|
CatalogTile *tile = &catalog->tiles[tile_index(ra_index, dec_index)];
|
||||||
|
char path[PATH_MAX];
|
||||||
|
int written;
|
||||||
|
if (tile->state != 0)
|
||||||
|
return tile->state == 1 ? 0 : -1;
|
||||||
|
written = snprintf(path, sizeof path, "%s/tile_ra%03d_dec%03d.csv",
|
||||||
|
catalog->all_sky_root, ra_index, dec_index);
|
||||||
|
if (written < 0 || (size_t)written >= sizeof path) {
|
||||||
|
tile->state = -1;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
StarCatalog temporary = {0};
|
||||||
|
if (catalog_load_csv(&temporary, path)) {
|
||||||
|
tile->state = -1;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
tile->stars = temporary.stars;
|
||||||
|
tile->count = temporary.count;
|
||||||
|
tile->state = 1;
|
||||||
|
catalog->count += tile->count;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int catalog_load_all_sky(StarCatalog *catalog, const char *directory)
|
||||||
|
{
|
||||||
|
size_t tile_count = (size_t)CATALOG_ALL_SKY_RA_TILES *
|
||||||
|
CATALOG_ALL_SKY_DEC_TILES;
|
||||||
|
if (catalog == NULL || directory == NULL || directory[0] == '\0')
|
||||||
|
return -1;
|
||||||
|
*catalog = (StarCatalog){0};
|
||||||
|
catalog->all_sky_root = malloc(strlen(directory) + 1);
|
||||||
|
catalog->tiles = calloc(tile_count, sizeof *catalog->tiles);
|
||||||
|
if (catalog->all_sky_root == NULL || catalog->tiles == NULL) {
|
||||||
|
catalog_destroy(catalog);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
strcpy(catalog->all_sky_root, directory);
|
||||||
|
catalog->kind = STAR_CATALOG_ALL_SKY;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lon_lat_from_direction(const double direction[3], double *longitude,
|
||||||
|
double *latitude)
|
||||||
|
{
|
||||||
|
*longitude = atan2(direction[2], direction[0]) * 180.0 / PI;
|
||||||
|
if (*longitude < 0.0)
|
||||||
|
*longitude += 360.0;
|
||||||
|
*latitude = asin(fmax(-1.0, fmin(1.0, direction[1]))) * 180.0 / PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double dot(const double a[3], const double b[3])
|
||||||
|
{
|
||||||
|
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cross(const double a[3], const double b[3], double out[3])
|
||||||
|
{
|
||||||
|
out[0] = a[1] * b[2] - a[2] * b[1];
|
||||||
|
out[1] = a[2] * b[0] - a[0] * b[2];
|
||||||
|
out[2] = a[0] * b[1] - a[1] * b[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void direction_from_lon_lat(double longitude, double latitude,
|
||||||
|
double direction[3])
|
||||||
|
{
|
||||||
|
const double lon = longitude * PI / 180.0;
|
||||||
|
const double lat = latitude * PI / 180.0;
|
||||||
|
const double cos_lat = cos(lat);
|
||||||
|
direction[0] = cos_lat * cos(lon);
|
||||||
|
direction[1] = sin(lat);
|
||||||
|
direction[2] = cos_lat * sin(lon);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The minimum of a plane dot-product over a longitude/latitude rectangle is
|
||||||
|
* attained on a boundary or at its antipodal stationary point. This gives a
|
||||||
|
* conservative, analytic whole-tile containment test; it is not a corner-only
|
||||||
|
* approximation. */
|
||||||
|
static double tile_plane_minimum(const double normal[3], double lon_lo,
|
||||||
|
double lon_hi, double lat_lo, double lat_hi)
|
||||||
|
{
|
||||||
|
double values[32];
|
||||||
|
size_t count = 0;
|
||||||
|
const double phase = atan2(normal[2], normal[0]) * 180.0 / PI;
|
||||||
|
const double latitude_phase = atan2(normal[1], hypot(normal[0], normal[2])) * 180.0 / PI;
|
||||||
|
const double candidate_lon[] = {lon_lo, lon_hi, phase + 180.0, phase - 180.0};
|
||||||
|
const double candidate_lat[] = {lat_lo, lat_hi, latitude_phase + 180.0,
|
||||||
|
latitude_phase - 180.0};
|
||||||
|
for (size_t i = 0; i < sizeof candidate_lon / sizeof *candidate_lon; ++i)
|
||||||
|
for (size_t j = 0; j < 2; ++j) {
|
||||||
|
double lon = candidate_lon[i];
|
||||||
|
while (lon < lon_lo) lon += 360.0;
|
||||||
|
while (lon > lon_hi) lon -= 360.0;
|
||||||
|
if (lon >= lon_lo && lon <= lon_hi) {
|
||||||
|
double point[3];
|
||||||
|
direction_from_lon_lat(lon, j == 0 ? lat_lo : lat_hi, point);
|
||||||
|
values[count++] = dot(normal, point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < sizeof candidate_lon / sizeof *candidate_lon; ++i)
|
||||||
|
for (size_t j = 0; j < sizeof candidate_lat / sizeof *candidate_lat; ++j) {
|
||||||
|
double lon = candidate_lon[i];
|
||||||
|
while (lon < lon_lo) lon += 360.0;
|
||||||
|
while (lon > lon_hi) lon -= 360.0;
|
||||||
|
if (lon >= lon_lo && lon <= lon_hi &&
|
||||||
|
candidate_lat[j] >= lat_lo && candidate_lat[j] <= lat_hi) {
|
||||||
|
double point[3];
|
||||||
|
direction_from_lon_lat(lon, candidate_lat[j], point);
|
||||||
|
values[count++] = dot(normal, point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < 2; ++i)
|
||||||
|
for (size_t j = 0; j < sizeof candidate_lat / sizeof *candidate_lat; ++j)
|
||||||
|
if (candidate_lat[j] >= lat_lo && candidate_lat[j] <= lat_hi) {
|
||||||
|
double point[3];
|
||||||
|
direction_from_lon_lat(i == 0 ? lon_lo : lon_hi,
|
||||||
|
candidate_lat[j], point);
|
||||||
|
values[count++] = dot(normal, point);
|
||||||
|
}
|
||||||
|
double minimum = values[0];
|
||||||
|
for (size_t i = 1; i < count; ++i)
|
||||||
|
if (values[i] < minimum) minimum = values[i];
|
||||||
|
return minimum;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tile_is_fully_contained(const double direction[3][3], int ra_index,
|
||||||
|
int dec_index)
|
||||||
|
{
|
||||||
|
const double lon_lo = ra_index;
|
||||||
|
const double lon_hi = ra_index + 1.0;
|
||||||
|
const double lat_lo = dec_index - 90.0;
|
||||||
|
const double lat_hi = lat_lo + 1.0;
|
||||||
|
for (int edge = 0; edge < 3; ++edge) {
|
||||||
|
const double *left = direction[edge];
|
||||||
|
const double *right = direction[(edge + 1) % 3];
|
||||||
|
const double *opposite = direction[(edge + 2) % 3];
|
||||||
|
double normal[3];
|
||||||
|
cross(left, right, normal);
|
||||||
|
if (dot(normal, opposite) < 0.0)
|
||||||
|
for (int axis = 0; axis < 3; ++axis) normal[axis] = -normal[axis];
|
||||||
|
if (tile_plane_minimum(normal, lon_lo, lon_hi, lat_lo, lat_hi) < -1e-14)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int catalog_visit_source_triangle(StarCatalog *catalog,
|
||||||
|
const double direction[3][3],
|
||||||
|
int load_missing, CatalogTileVisitor visitor,
|
||||||
|
void *context)
|
||||||
|
{
|
||||||
|
if (catalog == NULL || direction == NULL || visitor == NULL)
|
||||||
|
return -1;
|
||||||
|
if (catalog->kind == STAR_CATALOG_MEMORY)
|
||||||
|
return visitor(catalog->stars, catalog->count, 0, context);
|
||||||
|
double longitude[3], latitude[3], unwrapped[3];
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
lon_lat_from_direction(direction[i], &longitude[i], &latitude[i]);
|
||||||
|
unwrapped[i] = longitude[i];
|
||||||
|
while (unwrapped[i] - longitude[0] > 180.0) unwrapped[i] -= 360.0;
|
||||||
|
while (unwrapped[i] - longitude[0] < -180.0) unwrapped[i] += 360.0;
|
||||||
|
}
|
||||||
|
double lon_min = unwrapped[0], lon_max = unwrapped[0];
|
||||||
|
double lat_min = latitude[0], lat_max = latitude[0];
|
||||||
|
for (int i = 1; i < 3; ++i) {
|
||||||
|
if (unwrapped[i] < lon_min) lon_min = unwrapped[i];
|
||||||
|
if (unwrapped[i] > lon_max) lon_max = unwrapped[i];
|
||||||
|
if (latitude[i] < lat_min) lat_min = latitude[i];
|
||||||
|
if (latitude[i] > lat_max) lat_max = latitude[i];
|
||||||
|
}
|
||||||
|
/* A triangle containing a pole covers every RA there. */
|
||||||
|
const double north[3] = {0.0, 1.0, 0.0};
|
||||||
|
const double south[3] = {0.0, -1.0, 0.0};
|
||||||
|
int all_ra = 0;
|
||||||
|
for (int pole = 0; pole < 2; ++pole) {
|
||||||
|
const double *point = pole == 0 ? north : south;
|
||||||
|
int inside = 1;
|
||||||
|
for (int edge = 0; edge < 3; ++edge) {
|
||||||
|
double normal[3];
|
||||||
|
cross(direction[edge], direction[(edge + 1) % 3], normal);
|
||||||
|
if (dot(normal, point) * dot(normal, direction[(edge + 2) % 3]) < -1e-14) inside = 0;
|
||||||
|
}
|
||||||
|
if (inside) all_ra = 1;
|
||||||
|
}
|
||||||
|
/* Source edges are great-circle arcs, so their RA/Dec extrema need not be
|
||||||
|
* vertices. This small guard band covers that curvature without pulling
|
||||||
|
* in an otherwise unrelated one-degree tile ring. */
|
||||||
|
lon_min -= 0.01;
|
||||||
|
lon_max += 0.01;
|
||||||
|
lat_min -= 0.01;
|
||||||
|
lat_max += 0.01;
|
||||||
|
if (lon_max - lon_min >= 360.0)
|
||||||
|
all_ra = 1;
|
||||||
|
const int dec_first = fmax(0, (int)floor(lat_min + 90.0));
|
||||||
|
const int dec_last = fmin(CATALOG_ALL_SKY_DEC_TILES - 1,
|
||||||
|
(int)floor(lat_max + 90.0));
|
||||||
|
const int ra_first = (int)floor(lon_min);
|
||||||
|
const int ra_last = (int)floor(lon_max);
|
||||||
|
for (int dec = dec_first; dec <= dec_last; ++dec)
|
||||||
|
for (int raw_ra = all_ra ? 0 : ra_first;
|
||||||
|
raw_ra <= (all_ra ? 359 : ra_last); ++raw_ra) {
|
||||||
|
const int ra = (raw_ra % 360 + 360) % 360;
|
||||||
|
CatalogTile *tile = &catalog->tiles[tile_index(ra, dec)];
|
||||||
|
if (load_missing && load_tile(catalog, ra, dec))
|
||||||
|
continue; /* Downloader has not finished this tile yet. */
|
||||||
|
if (tile->state != 1 ||
|
||||||
|
visitor(tile->stars, tile->count,
|
||||||
|
tile_is_fully_contained(direction, ra, dec), context))
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void catalog_destroy(StarCatalog *catalog)
|
void catalog_destroy(StarCatalog *catalog)
|
||||||
{
|
{
|
||||||
|
if (catalog->tiles != NULL)
|
||||||
|
for (size_t i = 0;
|
||||||
|
i < (size_t)CATALOG_ALL_SKY_RA_TILES * CATALOG_ALL_SKY_DEC_TILES;
|
||||||
|
++i)
|
||||||
|
free(catalog->tiles[i].stars);
|
||||||
free(catalog->stars);
|
free(catalog->stars);
|
||||||
|
free(catalog->tiles);
|
||||||
|
free(catalog->all_sky_root);
|
||||||
catalog->stars = NULL;
|
catalog->stars = NULL;
|
||||||
catalog->count = 0;
|
catalog->count = 0;
|
||||||
|
catalog->tiles = NULL;
|
||||||
|
catalog->all_sky_root = NULL;
|
||||||
|
catalog->kind = STAR_CATALOG_MEMORY;
|
||||||
}
|
}
|
||||||
@@ -9,11 +9,30 @@ typedef struct {
|
|||||||
double amplitude;
|
double amplitude;
|
||||||
} Star;
|
} Star;
|
||||||
|
|
||||||
|
enum { CATALOG_ALL_SKY_RA_TILES = 360, CATALOG_ALL_SKY_DEC_TILES = 180 };
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
Star *stars;
|
Star *stars;
|
||||||
size_t count;
|
size_t count;
|
||||||
|
int state; /* 0: not requested, 1: loaded, -1: absent or unreadable. */
|
||||||
|
} CatalogTile;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
STAR_CATALOG_MEMORY,
|
||||||
|
STAR_CATALOG_ALL_SKY
|
||||||
|
} StarCatalogKind;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Star *stars;
|
||||||
|
size_t count;
|
||||||
|
StarCatalogKind kind;
|
||||||
|
char *all_sky_root;
|
||||||
|
CatalogTile *tiles;
|
||||||
} StarCatalog;
|
} StarCatalog;
|
||||||
|
|
||||||
|
typedef int (*CatalogTileVisitor)(const Star *stars, size_t count,
|
||||||
|
int fully_contained, void *context);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Synthetic lensing fixture: stars lie on the union of 10-degree longitude
|
* Synthetic lensing fixture: stars lie on the union of 10-degree longitude
|
||||||
* and latitude lines, sampled every 2 degrees. The eight longitude/hemisphere
|
* and latitude lines, sampled every 2 degrees. The eight longitude/hemisphere
|
||||||
@@ -22,6 +41,15 @@ typedef struct {
|
|||||||
*/
|
*/
|
||||||
int catalog_write_octant_grid(const char *path);
|
int catalog_write_octant_grid(const char *path);
|
||||||
int catalog_load_csv(StarCatalog *catalog, const char *path);
|
int catalog_load_csv(StarCatalog *catalog, const char *path);
|
||||||
|
/* The directory contains tile_raRRR_decDDD.csv plus optional .done markers.
|
||||||
|
* Tiles are loaded only after a source triangle intersects them. */
|
||||||
|
int catalog_load_all_sky(StarCatalog *catalog, const char *directory);
|
||||||
|
/* Visit only 1-degree all-sky tiles that can intersect this source triangle.
|
||||||
|
* Call with load_missing=1 before parallel rendering, then 0 inside workers. */
|
||||||
|
int catalog_visit_source_triangle(StarCatalog *catalog,
|
||||||
|
const double direction[3][3],
|
||||||
|
int load_missing, CatalogTileVisitor visitor,
|
||||||
|
void *context);
|
||||||
void catalog_destroy(StarCatalog *catalog);
|
void catalog_destroy(StarCatalog *catalog);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
+106
-33
@@ -118,13 +118,22 @@ static double spherical_area(const double a[3], const double b[3],
|
|||||||
1.0 + dot(a, b) + dot(b, c) + dot(c, a));
|
1.0 + dot(a, b) + dot(b, c) + dot(c, a));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int spherical_barycentric_weights(const double point[3], const double a[3],
|
||||||
|
const double b[3], const double c[3],
|
||||||
|
double weights[3]) {
|
||||||
|
const double area = spherical_area(a, b, c);
|
||||||
|
if (area < 1e-14)
|
||||||
|
return -1;
|
||||||
|
weights[0] = spherical_area(point, b, c) / area;
|
||||||
|
weights[1] = spherical_area(point, c, a) / area;
|
||||||
|
weights[2] = spherical_area(point, a, b) / area;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int spherical_barycentric(const double point[3], const double a[3],
|
static int spherical_barycentric(const double point[3], const double a[3],
|
||||||
const double b[3], const double c[3],
|
const double b[3], const double c[3],
|
||||||
double weights[3]) {
|
double weights[3]) {
|
||||||
const double area = spherical_area(a, b, c);
|
|
||||||
double edge_cross[3];
|
double edge_cross[3];
|
||||||
if (area < 1e-14)
|
|
||||||
return -1;
|
|
||||||
const double *corners[3] = {a, b, c};
|
const double *corners[3] = {a, b, c};
|
||||||
for (int edge = 0; edge < 3; ++edge) {
|
for (int edge = 0; edge < 3; ++edge) {
|
||||||
const double *left = corners[edge];
|
const double *left = corners[edge];
|
||||||
@@ -138,10 +147,7 @@ static int spherical_barycentric(const double point[3], const double a[3],
|
|||||||
-1e-14 * dot(edge_cross, edge_cross))
|
-1e-14 * dot(edge_cross, edge_cross))
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
weights[0] = spherical_area(point, b, c) / area;
|
return spherical_barycentric_weights(point, a, b, c, weights);
|
||||||
weights[1] = spherical_area(point, c, a) / area;
|
|
||||||
weights[2] = spherical_area(point, a, b) / area;
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int usable_triangle(const FrameLensMesh *mesh,
|
static int usable_triangle(const FrameLensMesh *mesh,
|
||||||
@@ -171,8 +177,57 @@ static int owns_source_boundary(const LensTriangle *triangle,
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const LensVertex *vertex[3];
|
||||||
|
const LensTriangle *triangle;
|
||||||
|
double *hdr;
|
||||||
|
int width, height;
|
||||||
|
double exposure, magnification;
|
||||||
|
const PointSpreadFunction *psf;
|
||||||
|
size_t images;
|
||||||
|
} TriangleSplatContext;
|
||||||
|
|
||||||
|
static int splat_catalog_tile(const Star *stars, size_t count,
|
||||||
|
int fully_contained, void *opaque) {
|
||||||
|
TriangleSplatContext *context = opaque;
|
||||||
|
for (size_t s = 0; s < count; ++s) {
|
||||||
|
const Star *star = &stars[s];
|
||||||
|
double weights[3];
|
||||||
|
if (!fully_contained &&
|
||||||
|
spherical_barycentric(star->direction, context->vertex[0]->n_infinity,
|
||||||
|
context->vertex[1]->n_infinity,
|
||||||
|
context->vertex[2]->n_infinity, weights))
|
||||||
|
continue;
|
||||||
|
if (fully_contained) {
|
||||||
|
/* Only inverse-map weights remain: no per-star containment test. */
|
||||||
|
if (spherical_barycentric_weights(star->direction,
|
||||||
|
context->vertex[0]->n_infinity,
|
||||||
|
context->vertex[1]->n_infinity,
|
||||||
|
context->vertex[2]->n_infinity, weights))
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!owns_source_boundary(context->triangle, weights))
|
||||||
|
continue;
|
||||||
|
const double image_x = weights[0] * context->vertex[0]->image_x +
|
||||||
|
weights[1] * context->vertex[1]->image_x +
|
||||||
|
weights[2] * context->vertex[2]->image_x;
|
||||||
|
const double image_y = weights[0] * context->vertex[0]->image_y +
|
||||||
|
weights[1] * context->vertex[1]->image_y +
|
||||||
|
weights[2] * context->vertex[2]->image_y;
|
||||||
|
const double log_g = weights[0] * context->vertex[0]->log_frequency_ratio +
|
||||||
|
weights[1] * context->vertex[1]->log_frequency_ratio +
|
||||||
|
weights[2] * context->vertex[2]->log_frequency_ratio;
|
||||||
|
const LinearRgb color = blackbody_to_linear_rgb(star->temperature_K * exp(log_g));
|
||||||
|
splat_moffat(context->hdr, context->width, context->height, image_x, image_y,
|
||||||
|
color, context->exposure * star->amplitude * context->magnification,
|
||||||
|
context->psf);
|
||||||
|
++context->images;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static size_t splat_catalog_triangles(const FrameLensMesh *mesh,
|
static size_t splat_catalog_triangles(const FrameLensMesh *mesh,
|
||||||
const StarCatalog *catalog, double *hdr,
|
StarCatalog *catalog, double *hdr,
|
||||||
int width, int height, double exposure,
|
int width, int height, double exposure,
|
||||||
const PointSpreadFunction *psf,
|
const PointSpreadFunction *psf,
|
||||||
size_t first_triangle,
|
size_t first_triangle,
|
||||||
@@ -188,42 +243,60 @@ static size_t splat_catalog_triangles(const FrameLensMesh *mesh,
|
|||||||
spherical_area(vertex[0]->camera_direction, vertex[1]->camera_direction,
|
spherical_area(vertex[0]->camera_direction, vertex[1]->camera_direction,
|
||||||
vertex[2]->camera_direction);
|
vertex[2]->camera_direction);
|
||||||
const double magnification = image_area / source_area;
|
const double magnification = image_area / source_area;
|
||||||
for (size_t s = 0; s < catalog->count; ++s) {
|
const double direction[3][3] = {
|
||||||
const Star *star = &catalog->stars[s];
|
{vertex[0]->n_infinity[0], vertex[0]->n_infinity[1], vertex[0]->n_infinity[2]},
|
||||||
double weights[3];
|
{vertex[1]->n_infinity[0], vertex[1]->n_infinity[1], vertex[1]->n_infinity[2]},
|
||||||
if (spherical_barycentric(star->direction, vertex[0]->n_infinity,
|
{vertex[2]->n_infinity[0], vertex[2]->n_infinity[1], vertex[2]->n_infinity[2]}};
|
||||||
vertex[1]->n_infinity, vertex[2]->n_infinity,
|
TriangleSplatContext context = {.vertex = {vertex[0], vertex[1], vertex[2]},
|
||||||
weights))
|
.triangle = &mesh->triangles[t], .hdr = hdr,
|
||||||
continue;
|
.width = width, .height = height,
|
||||||
if (!owns_source_boundary(&mesh->triangles[t], weights))
|
.exposure = exposure, .magnification = magnification,
|
||||||
continue;
|
.psf = psf};
|
||||||
const double image_x = weights[0] * vertex[0]->image_x +
|
if (catalog_visit_source_triangle(catalog, direction, 0, splat_catalog_tile,
|
||||||
weights[1] * vertex[1]->image_x +
|
&context) == 0)
|
||||||
weights[2] * vertex[2]->image_x;
|
images += context.images;
|
||||||
const double image_y = weights[0] * vertex[0]->image_y +
|
|
||||||
weights[1] * vertex[1]->image_y +
|
|
||||||
weights[2] * vertex[2]->image_y;
|
|
||||||
const double log_g = weights[0] * vertex[0]->log_frequency_ratio +
|
|
||||||
weights[1] * vertex[1]->log_frequency_ratio +
|
|
||||||
weights[2] * vertex[2]->log_frequency_ratio;
|
|
||||||
const LinearRgb color =
|
|
||||||
blackbody_to_linear_rgb(star->temperature_K * exp(log_g));
|
|
||||||
splat_moffat(hdr, width, height, image_x, image_y, color,
|
|
||||||
exposure * star->amplitude * magnification, psf);
|
|
||||||
++images;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return images;
|
return images;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int prefetch_catalog_tile(const Star *stars, size_t count,
|
||||||
|
int fully_contained, void *context) {
|
||||||
|
(void)stars;
|
||||||
|
(void)count;
|
||||||
|
(void)fully_contained;
|
||||||
|
(void)context;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prefetch_catalog_for_mesh(const FrameLensMesh *mesh,
|
||||||
|
StarCatalog *catalog) {
|
||||||
|
if (catalog->kind != STAR_CATALOG_ALL_SKY)
|
||||||
|
return;
|
||||||
|
for (size_t t = 0; t < mesh->triangle_count; ++t) {
|
||||||
|
const LensVertex *vertex[3];
|
||||||
|
if (!usable_triangle(mesh, &mesh->triangles[t], vertex))
|
||||||
|
continue;
|
||||||
|
const double direction[3][3] = {
|
||||||
|
{vertex[0]->n_infinity[0], vertex[0]->n_infinity[1], vertex[0]->n_infinity[2]},
|
||||||
|
{vertex[1]->n_infinity[0], vertex[1]->n_infinity[1], vertex[1]->n_infinity[2]},
|
||||||
|
{vertex[2]->n_infinity[0], vertex[2]->n_infinity[1], vertex[2]->n_infinity[2]}};
|
||||||
|
(void)catalog_visit_source_triangle(catalog, direction, 1,
|
||||||
|
prefetch_catalog_tile, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
size_t frame_splat_catalog(const FrameLensMesh *mesh,
|
size_t frame_splat_catalog(const FrameLensMesh *mesh,
|
||||||
const StarCatalog *catalog, double *hdr, int width,
|
StarCatalog *catalog, double *hdr, int width,
|
||||||
int height, double exposure,
|
int height, double exposure,
|
||||||
const PointSpreadFunction *psf) {
|
const PointSpreadFunction *psf) {
|
||||||
if (mesh == NULL || catalog == NULL || hdr == NULL || exposure <= 0.0 ||
|
if (mesh == NULL || catalog == NULL || hdr == NULL || exposure <= 0.0 ||
|
||||||
psf == NULL || width <= 0 || height <= 0)
|
psf == NULL || width <= 0 || height <= 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
/* Tile I/O is deliberately serial and complete before OpenMP workers start.
|
||||||
|
* The parallel splat pass then reads an immutable tile cache. */
|
||||||
|
prefetch_catalog_for_mesh(mesh, catalog);
|
||||||
|
|
||||||
const size_t pixel_count = (size_t)width * height * 3;
|
const size_t pixel_count = (size_t)width * height * 3;
|
||||||
if (pixel_count > SIZE_MAX / sizeof(double) ||
|
if (pixel_count > SIZE_MAX / sizeof(double) ||
|
||||||
pixel_count * sizeof(double) > FRAME_SPLAT_MAX_PRIVATE_HDR_BYTES / 2)
|
pixel_count * sizeof(double) > FRAME_SPLAT_MAX_PRIVATE_HDR_BYTES / 2)
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@ int frame_lens_mesh_trace(FrameLensMesh *mesh, const SpacetimeSource *spacetime,
|
|||||||
/* Each locally invertible escaped triangle contributes one image per contained
|
/* Each locally invertible escaped triangle contributes one image per contained
|
||||||
* star. */
|
* star. */
|
||||||
size_t frame_splat_catalog(const FrameLensMesh *mesh,
|
size_t frame_splat_catalog(const FrameLensMesh *mesh,
|
||||||
const StarCatalog *catalog, double *hdr, int width,
|
StarCatalog *catalog, double *hdr, int width,
|
||||||
int height, double exposure,
|
int height, double exposure,
|
||||||
const PointSpreadFunction *psf);
|
const PointSpreadFunction *psf);
|
||||||
void frame_draw_mesh(const FrameLensMesh *mesh, double *hdr, int width,
|
void frame_draw_mesh(const FrameLensMesh *mesh, double *hdr, int width,
|
||||||
|
|||||||
+14
-6
@@ -22,6 +22,7 @@ typedef struct {
|
|||||||
double observer_inward_speed;
|
double observer_inward_speed;
|
||||||
PointSpreadFunction psf;
|
PointSpreadFunction psf;
|
||||||
const char *catalog_path;
|
const char *catalog_path;
|
||||||
|
const char *all_sky_catalog_path;
|
||||||
const char *output_path;
|
const char *output_path;
|
||||||
const char *observer_track_path;
|
const char *observer_track_path;
|
||||||
const char *frames_dir;
|
const char *frames_dir;
|
||||||
@@ -112,6 +113,8 @@ static int parse_args(int argc, char **argv, Settings *s,
|
|||||||
for (int i = 1; i < argc; ++i) {
|
for (int i = 1; i < argc; ++i) {
|
||||||
if (!strcmp(argv[i], "--catalog") && i + 1 < argc)
|
if (!strcmp(argv[i], "--catalog") && i + 1 < argc)
|
||||||
s->catalog_path = argv[++i];
|
s->catalog_path = argv[++i];
|
||||||
|
else if (!strcmp(argv[i], "--all-sky-catalog") && i + 1 < argc)
|
||||||
|
s->all_sky_catalog_path = argv[++i];
|
||||||
else if (!strcmp(argv[i], "--output") && i + 1 < argc)
|
else if (!strcmp(argv[i], "--output") && i + 1 < argc)
|
||||||
s->output_path = argv[++i];
|
s->output_path = argv[++i];
|
||||||
else if (!strcmp(argv[i], "--width") && i + 1 < argc &&
|
else if (!strcmp(argv[i], "--width") && i + 1 < argc &&
|
||||||
@@ -185,7 +188,7 @@ static int default_observer(const Settings *s, ObserverState *observer) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static int render_observer_frame(const Settings *s, const StarCatalog *catalog,
|
static int render_observer_frame(const Settings *s, StarCatalog *catalog,
|
||||||
const SpacetimeSource *spacetime,
|
const SpacetimeSource *spacetime,
|
||||||
const ObserverState *observer,
|
const ObserverState *observer,
|
||||||
const char *output_path) {
|
const char *output_path) {
|
||||||
@@ -214,7 +217,7 @@ static int render_observer_frame(const Settings *s, const StarCatalog *catalog,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int render_frame(const Settings *s, const StarCatalog *catalog,
|
static int render_frame(const Settings *s, StarCatalog *catalog,
|
||||||
const SpacetimeSource *spacetime) {
|
const SpacetimeSource *spacetime) {
|
||||||
ObserverState observer;
|
ObserverState observer;
|
||||||
return default_observer(s, &observer) ? -1
|
return default_observer(s, &observer) ? -1
|
||||||
@@ -234,7 +237,7 @@ static int frame_output_path(char path[PATH_MAX], const Settings *s,
|
|||||||
return written < 0 || written >= PATH_MAX ? -1 : 0;
|
return written < 0 || written >= PATH_MAX ? -1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int render_movie(const Settings *s, const StarCatalog *catalog,
|
static int render_movie(const Settings *s, StarCatalog *catalog,
|
||||||
const SpacetimeSource *spacetime) {
|
const SpacetimeSource *spacetime) {
|
||||||
ObserverTrack track = {0};
|
ObserverTrack track = {0};
|
||||||
Movie movie = {0};
|
Movie movie = {0};
|
||||||
@@ -323,7 +326,7 @@ int main(int argc, char **argv) {
|
|||||||
const char *write_path;
|
const char *write_path;
|
||||||
if (parse_args(argc, argv, &settings, &write_path)) {
|
if (parse_args(argc, argv, &settings, &write_path)) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Usage: %s [--catalog PATH] [--output PATH] [--width N] [--height "
|
"Usage: %s [--catalog PATH | --all-sky-catalog DIR] [--output PATH] [--width N] [--height "
|
||||||
"N] [--fov-deg D] [--look-ra-deg D] [--look-dec-deg D] "
|
"N] [--fov-deg D] [--look-ra-deg D] [--look-dec-deg D] "
|
||||||
"[--exposure E] [--observer-inward-speed V] "
|
"[--exposure E] [--observer-inward-speed V] "
|
||||||
"[--psf-fwhm-pixels N] [--psf-moffat-beta N] "
|
"[--psf-fwhm-pixels N] [--psf-moffat-beta N] "
|
||||||
@@ -341,8 +344,13 @@ int main(int argc, char **argv) {
|
|||||||
return write_minkowski_accel_track(&settings) == 0
|
return write_minkowski_accel_track(&settings) == 0
|
||||||
? 0
|
? 0
|
||||||
: (perror(settings.write_minkowski_accel_track_path), 1);
|
: (perror(settings.write_minkowski_accel_track_path), 1);
|
||||||
StarCatalog catalog;
|
StarCatalog catalog = {0};
|
||||||
if (catalog_load_csv(&catalog, settings.catalog_path)) {
|
if (settings.all_sky_catalog_path != NULL) {
|
||||||
|
if (catalog_load_all_sky(&catalog, settings.all_sky_catalog_path)) {
|
||||||
|
perror(settings.all_sky_catalog_path);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else if (catalog_load_csv(&catalog, settings.catalog_path)) {
|
||||||
if (catalog_write_octant_grid(settings.catalog_path) ||
|
if (catalog_write_octant_grid(settings.catalog_path) ||
|
||||||
catalog_load_csv(&catalog, settings.catalog_path)) {
|
catalog_load_csv(&catalog, settings.catalog_path)) {
|
||||||
perror(settings.catalog_path);
|
perror(settings.catalog_path);
|
||||||
|
|||||||
Reference in new issue
Block a user