Output: default to PNG with PPM fallback

This commit is contained in:
wyj committed 2026-08-27 04:25:45 -04:00
1 parent 83dc05fd52
commit b0fdf1faca
7 files changed
+47 -26

No files matched your search

+5 -2
View File
@@ -2,12 +2,15 @@ CC ?= cc
CFLAGS ?= -std=c11 -O2 -Wall -Wextra -Wpedantic CFLAGS ?= -std=c11 -O2 -Wall -Wextra -Wpedantic
OPENMP_FLAGS ?= -fopenmp OPENMP_FLAGS ?= -fopenmp
LDLIBS ?= -lm LDLIBS ?= -lm
ENABLE_PNG ?= 0 ENABLE_PNG ?= 1
SPACETIME ?= minkowski SPACETIME ?= minkowski
ifeq ($(ENABLE_PNG),1) ifeq ($(ENABLE_PNG),1)
CPPFLAGS += -DENABLE_PNG CPPFLAGS += -DENABLE_PNG
LDLIBS += -lpng LDLIBS += -lpng
IMAGE_EXT := png
else
IMAGE_EXT := ppm
endif endif
COMMON_SOURCES := $(filter-out src/main.c src/spacetime_minkowski.c src/spacetime_schwarzschild.c,$(wildcard src/*.c)) COMMON_SOURCES := $(filter-out src/main.c src/spacetime_minkowski.c src/spacetime_schwarzschild.c,$(wildcard src/*.c))
@@ -40,7 +43,7 @@ build:
run: $(TARGET) run: $(TARGET)
mkdir -p output/imgs mkdir -p output/imgs
./$(TARGET) --catalog assets/sky_grid_5deg.csv --output output/imgs/$(SPACETIME)_sky.ppm ./$(TARGET) --catalog assets/sky_grid_5deg.csv --output output/imgs/$(SPACETIME)_sky.$(IMAGE_EXT)
minkowski: minkowski:
$(MAKE) SPACETIME=minkowski all $(MAKE) SPACETIME=minkowski all
+12 -10
View File
@@ -21,8 +21,7 @@ which equalizes their CIE/linear-sRGB luminance under the renderer's blackbody
integration. Longitude boundaries belong to the sector to their east and the integration. Longitude boundaries belong to the sector to their east and the
equator to the northern hemisphere, so boundary stars have a deterministic equator to the northern hemisphere, so boundary stars have a deterministic
color. color.
The output is a binary PPM at `output/imgs/minkowski_sky.ppm`; it can be inspected by The default output is PNG at `output/imgs/minkowski_sky.png`.
most image viewers or converted to PNG with ImageMagick.
## Movie PNG sequence (Phase A) ## Movie PNG sequence (Phase A)
@@ -33,7 +32,7 @@ Minkowski benchmark—two coordinate seconds at 30 fps, accelerating from rest
to about `0.95c`—then render its numbered PNG frames: to about `0.95c`—then render its numbered PNG frames:
```sh ```sh
make clean && make ENABLE_PNG=1 make clean && make
mkdir -p output/imgs mkdir -p output/imgs
./build/minkowski_sky --write-minkowski-accel-track output/minkowski_accel_2s.csv \ ./build/minkowski_sky --write-minkowski-accel-track output/minkowski_accel_2s.csv \
--duration 2 --fps 30 --proper-acceleration 1.52 --duration 2 --fps 30 --proper-acceleration 1.52
@@ -55,15 +54,18 @@ synthetic test catalog uses global default exposure `1e-3`; the accelerated
benchmark explicitly uses `1e-5` because its physical Doppler blue shift benchmark explicitly uses `1e-5` because its physical Doppler blue shift
otherwise clips the later frames. otherwise clips the later frames.
PNG output is optional so the default build has no `libpng` dependency. Build PNG is the default output and the default build links `libpng`:
with `make ENABLE_PNG=1`, then select it with a `.png` output path:
```sh ```sh
make clean && make ENABLE_PNG=1 make clean && make
mkdir -p output/imgs mkdir -p output/imgs
./build/minkowski_sky --output output/imgs/minkowski_sky.png ./build/minkowski_sky --output output/imgs/minkowski_sky.png
``` ```
If `libpng` is unavailable, rebuild with `make clean && make ENABLE_PNG=0`.
That intentionally selects the binary-PPM fallback, whose default path is
`output/imgs/minkowski_sky.ppm`; pass a `.ppm` path for explicit output.
Run the flat-spacetime geodesic regression with: Run the flat-spacetime geodesic regression with:
```sh ```sh
@@ -79,7 +81,7 @@ Kerr--Schild coordinates (regular at the horizon), then render the test catalog
to PNG: to PNG:
```sh ```sh
make clean && make SPACETIME=schwarzschild ENABLE_PNG=1 make clean && make SPACETIME=schwarzschild
mkdir -p output/imgs mkdir -p output/imgs
./build/schwarzschild_sky --catalog assets/sky_grid_5deg.csv \ ./build/schwarzschild_sky --catalog assets/sky_grid_5deg.csv \
--width 640 --height 360 --coarse-cell-pixels 8 --fov-deg 60 \ --width 640 --height 360 --coarse-cell-pixels 8 --fov-deg 60 \
@@ -113,13 +115,13 @@ Useful options:
```sh ```sh
./build/minkowski_sky --width 1920 --height 1080 --fov-deg 30 \ ./build/minkowski_sky --width 1920 --height 1080 --fov-deg 30 \
--catalog assets/sky_grid_5deg.csv --output output/imgs/frame.ppm --catalog assets/sky_grid_5deg.csv --output output/imgs/frame.png
./build/minkowski_sky --catalog assets/2mass/processed/2mass_psc_m31_0p5deg_stars.csv \ ./build/minkowski_sky --catalog assets/2mass/processed/2mass_psc_m31_0p5deg_stars.csv \
--look-ra-deg 10.6847083 --look-dec-deg 41.26875 --fov-deg 1.8 \ --look-ra-deg 10.6847083 --look-dec-deg 41.26875 --fov-deg 1.8 \
--exposure 1e15 --output output/imgs/2mass_m31.ppm --exposure 1e15 --output output/imgs/2mass_m31.png
./build/minkowski_sky --catalog assets/2mass/processed/2mass_psc_m44_1p0deg_stars.csv \ ./build/minkowski_sky --catalog assets/2mass/processed/2mass_psc_m44_1p0deg_stars.csv \
--look-ra-deg 129.99165 --look-dec-deg 19.54139 --fov-deg 2.0 \ --look-ra-deg 129.99165 --look-dec-deg 19.54139 --fov-deg 2.0 \
--exposure 1e15 --width 1920 --height 1920 --output output/imgs/2mass_m44.ppm --exposure 1e15 --width 1920 --height 1920 --output output/imgs/2mass_m44.png
./build/minkowski_sky --write-catalog assets/sky_grid_5deg.csv ./build/minkowski_sky --write-catalog assets/sky_grid_5deg.csv
``` ```
+4 -3
View File
@@ -38,7 +38,7 @@ tile tables after successful cleaning unless `--keep-raw` is passed.
Use the directory mode rather than `--catalog` for this partitioned dataset: Use the directory mode rather than `--catalog` for this partitioned dataset:
```sh ```sh
make ENABLE_PNG=1 SPACETIME=minkowski all make SPACETIME=minkowski all
./build/minkowski_sky --all-sky-catalog assets/2mass/processed/all_sky \ ./build/minkowski_sky --all-sky-catalog assets/2mass/processed/all_sky \
--fov-deg 2 --look-ra-deg 180 --look-dec-deg -75 \ --fov-deg 2 --look-ra-deg 180 --look-dec-deg -75 \
--exposure 1e15 \ --exposure 1e15 \
@@ -87,8 +87,9 @@ with the rotating observer fixture:
Missing tiles are skipped until the downloader has produced them. Fully Missing tiles are skipped until the downloader has produced them. Fully
contained tiles skip per-star source-triangle containment; partial tiles retain contained tiles skip per-star source-triangle containment; partial tiles retain
that exact check. For 2MASS apparent-solid-angle amplitudes, start with that exact check. For 2MASS apparent-solid-angle amplitudes, start with
`--exposure 1e15`; use PNG (`make ENABLE_PNG=1`) for substantially smaller `--exposure 1e15`; PNG is the default output format. If libpng is unavailable,
single-frame output than binary PPM. use the PPM fallback with `make clean && make ENABLE_PNG=0` and a `.ppm`
output path.
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.
+13 -3
View File
@@ -96,6 +96,11 @@ static int parse_moffat_beta(const char *text, double *value) {
static int parse_args(int argc, char **argv, Settings *s, static int parse_args(int argc, char **argv, Settings *s,
const char **write_path) { const char **write_path) {
#ifdef ENABLE_PNG
const char *default_output_path = "output/imgs/minkowski_sky.png";
#else
const char *default_output_path = "output/imgs/minkowski_sky.ppm";
#endif
*s = (Settings){.width = 1280, *s = (Settings){.width = 1280,
.height = 720, .height = 720,
.coarse_cell_pixels = 16, .coarse_cell_pixels = 16,
@@ -106,7 +111,7 @@ static int parse_args(int argc, char **argv, Settings *s,
.observer_radius = 30.0, .observer_radius = 30.0,
.psf = {2.7, 4.5}, .psf = {2.7, 4.5},
.catalog_path = "assets/sky_grid_5deg.csv", .catalog_path = "assets/sky_grid_5deg.csv",
.output_path = "output/imgs/minkowski_sky.ppm", .output_path = default_output_path,
.frames_prefix = "frame", .frames_prefix = "frame",
.movie_duration = 2.0, .movie_duration = 2.0,
.movie_fps = 30.0, .movie_fps = 30.0,
@@ -246,13 +251,18 @@ static int render_frame(const Settings *s, StarCatalog *catalog,
static int frame_output_path(char path[PATH_MAX], const Settings *s, static int frame_output_path(char path[PATH_MAX], const Settings *s,
size_t frame_id) { size_t frame_id) {
#ifdef ENABLE_PNG
const char *extension = "png";
#else
const char *extension = "ppm";
#endif
struct stat st; struct stat st;
if (s->frames_dir == NULL || s->frames_prefix == NULL || if (s->frames_dir == NULL || s->frames_prefix == NULL ||
strchr(s->frames_prefix, '/') != NULL || stat(s->frames_dir, &st) || strchr(s->frames_prefix, '/') != NULL || stat(s->frames_dir, &st) ||
!S_ISDIR(st.st_mode)) !S_ISDIR(st.st_mode))
return -1; return -1;
const int written = snprintf(path, PATH_MAX, "%s/%s_%06zu.png", s->frames_dir, const int written = snprintf(path, PATH_MAX, "%s/%s_%06zu.%s", s->frames_dir,
s->frames_prefix, frame_id); s->frames_prefix, frame_id, extension);
return written < 0 || written >= PATH_MAX ? -1 : 0; return written < 0 || written >= PATH_MAX ? -1 : 0;
} }
+10 -5
View File
@@ -120,6 +120,7 @@ static unsigned char tonemap_channel(double hdr_value)
return (unsigned char)lround(255.0 * clamp(display, 0.0, 1.0)); return (unsigned char)lround(255.0 * clamp(display, 0.0, 1.0));
} }
#ifndef ENABLE_PNG
static int write_tonemapped_ppm(const char *path, const double *hdr, int width, static int write_tonemapped_ppm(const char *path, const double *hdr, int width,
int height) int height)
{ {
@@ -132,6 +133,7 @@ static int write_tonemapped_ppm(const char *path, const double *hdr, int width,
} }
return fclose(file) == 0 ? 0 : -1; return fclose(file) == 0 ? 0 : -1;
} }
#endif
#ifdef ENABLE_PNG #ifdef ENABLE_PNG
static int write_tonemapped_png(const char *path, const double *hdr, int width, static int write_tonemapped_png(const char *path, const double *hdr, int width,
@@ -171,13 +173,16 @@ done:
int write_tonemapped_image(const char *path, const double *hdr, int width, int height) int write_tonemapped_image(const char *path, const double *hdr, int width, int height)
{ {
const size_t path_length = strlen(path); const size_t path_length = strlen(path);
if (path_length >= 4 && strcmp(path + path_length - 4, ".png") == 0) {
#ifdef ENABLE_PNG #ifdef ENABLE_PNG
if (path_length >= 4 && strcmp(path + path_length - 4, ".png") == 0)
return write_tonemapped_png(path, hdr, width, height); return write_tonemapped_png(path, hdr, width, height);
fputs("PNG output is enabled; use a .png output path.\n", stderr);
return -1;
#else #else
fputs("PNG output is disabled; rebuild with make ENABLE_PNG=1.\n", stderr); if (path_length >= 4 && strcmp(path + path_length - 4, ".ppm") == 0)
return -1; return write_tonemapped_ppm(path, hdr, width, height);
fputs("PNG output is unavailable; use a .ppm output path or rebuild with libpng.\n",
stderr);
return -1;
#endif #endif
}
return write_tonemapped_ppm(path, hdr, width, height);
} }
+1 -1
View File
@@ -13,7 +13,7 @@ LinearRgb blackbody_to_linear_rgb(double temperature_K);
void splat_moffat(double *hdr, int width, int height, double x, double y, void splat_moffat(double *hdr, int width, int height, double x, double y,
LinearRgb color, double flux, LinearRgb color, double flux,
const PointSpreadFunction *psf); const PointSpreadFunction *psf);
/* Writes PPM by default; a .png path requires a build with ENABLE_PNG=1. */ /* Writes PNG when built with libpng; non-libpng builds use PPM fallback. */
int write_tonemapped_image(const char *path, const double *hdr, int width, int write_tonemapped_image(const char *path, const double *hdr, int width,
int height); int height);
+2 -2
View File
@@ -41,8 +41,8 @@ SampleRequest -> RayPool (SoA, inactive / active / terminated)
1. 新增 `observer_track.h/.c`,将目前的 `ObserverState` 提升为可插值的 `ObserverTrack`:每个 sample 保存 `t_coordinate`、`tau`、`x[3]` 和完整 tetrad。提供加载、严格单调时间校验、区间查找、插值和销毁函数。原有固定 observer 构造函数保留,作为只含一个 sample 的兼容快捷方式。 1. 新增 `observer_track.h/.c`,将目前的 `ObserverState` 提升为可插值的 `ObserverTrack`:每个 sample 保存 `t_coordinate`、`tau`、`x[3]` 和完整 tetrad。提供加载、严格单调时间校验、区间查找、插值和销毁函数。原有固定 observer 构造函数保留,作为只含一个 sample 的兼容快捷方式。
2. 第一版轨迹输入采用人可审阅的 21 列 CSV,字段固定为 `t,tau,x,y,z,e0t,e0x,...,e3z`;不在 renderer 内从“速度曲线”临时猜 tetrad。loader 做完整性、有限数与严格单调时间检查;tetrad 的 metric 正交归一由轨迹生成器和 backend-aware regression 验证。另提供生成器工具/模式为测试写出 CSV。这样 NR 生产轨迹、解析测试轨迹和以后离线 trajectory generator 使用完全相同的 renderer 输入。 2. 第一版轨迹输入采用人可审阅的 21 列 CSV,字段固定为 `t,tau,x,y,z,e0t,e0x,...,e3z`;不在 renderer 内从“速度曲线”临时猜 tetrad。loader 做完整性、有限数与严格单调时间检查;tetrad 的 metric 正交归一由轨迹生成器和 backend-aware regression 验证。另提供生成器工具/模式为测试写出 CSV。这样 NR 生产轨迹、解析测试轨迹和以后离线 trajectory generator 使用完全相同的 renderer 输入。
3. 加入 `movie.h/.c`,定义 `MovieSettings`、`MovieFrame`(`frame_id`、`t_camera`、插值得到的 observer state、mesh、HDR image)与 `Movie`。用 `start-time`、`duration`、`fps` 生成 frame 时间表;先拒绝轨迹覆盖范围外的时间。 3. 加入 `movie.h/.c`,定义 `MovieSettings`、`MovieFrame`(`frame_id`、`t_camera`、插值得到的 observer state、mesh、HDR image)与 `Movie`。用 `start-time`、`duration`、`fps` 生成 frame 时间表;先拒绝轨迹覆盖范围外的时间。
4. 将 `render_frame()` 中与单帧无关的工作拆成“初始化 mesh / 安装 endpoint / splat HDR / 写出”四个明确步骤。保持现有单帧命令和 PPM/PNG 行为可用;movie 模式只是调用同一套 frame rendering 与 optics。 4. 将 `render_frame()` 中与单帧无关的工作拆成“初始化 mesh / 安装 endpoint / splat HDR / 写出”四个明确步骤。默认构建写 PNG;未启用 libpng 时单帧和 movie 序列统一退回 PPM。movie 模式只是调用同一套 frame rendering 与 optics。
5. 新增 `output.h/.c`,从 `optics` 移出文件命名和写图逻辑。PNG 仍复用当前 libpng 路径,movie PNG 命名固定为 `<output-dir>/<prefix>_%06u.png`,例如 `output/imgs/minkowski_accel_000042.png`。在创建前验证目录、prefix 与帧号范围,写入失败应指出 frame id 和路径。 5. 新增 `output.h/.c`,从 `optics` 移出文件命名和写图逻辑。默认 movie PNG 仍复用当前 libpng 路径,命名固定为 `<output-dir>/<prefix>_%06u.png`,例如 `output/imgs/minkowski_accel_000042.png`;无 libpng 的 PPM fallback 只替换扩展名。创建前验证目录、prefix 与帧号范围,写入失败应指出 frame id 和路径。
验收:原有 `make test` 和两种单帧 backend 的输出保持不变;新测试验证 CSV 拒绝缺列、非有限数与非单调时间,frame 时间表精确包含首帧且不越界,PNG 序号无冲突。此阶段不要求 FFmpeg,也不改变 ray tracing 的调度方式。 验收:原有 `make test` 和两种单帧 backend 的输出保持不变;新测试验证 CSV 拒绝缺列、非有限数与非单调时间,frame 时间表精确包含首帧且不越界,PNG 序号无冲突。此阶段不要求 FFmpeg,也不改变 ray tracing 的调度方式。