|
|
||
|---|---|---|
| .. | ||
| results-2026-09-30-tower121 | ||
| results-2026-10-01-tower111 | ||
| results-2026-10-01-tower121 | ||
| results-2026-10-02-tower121 | ||
| results-2026-10-04-tower111 | ||
| results-2026-10-08-tower111-throttle | ||
| results-common-tower111 | ||
| results-common-tower121 | ||
| README.md | ||
| bench.cpp | ||
| camera_bench.py | ||
| compare_8_vs_12.py | ||
| compare_examples.py | ||
| encode_bench.cpp | ||
| lossless_check.cpp | ||
| make_figures.py | ||
| make_synthetic.py | ||
| plot_common.py | ||
| plot_results.py | ||
| raw12_bench.cpp | ||
| run_camera_tests.sh | ||
| run_common_frame.sh | ||
| run_encode_tests.sh | ||
| run_raw12_camera.sh | ||
| run_raw12_encode.sh | ||
| summarize_encode.py | ||
README.md
camera-bench
Measures a tower's camera and CPU for the capture design: maximum and sustained frame rate per pixel format, losses while streaming, auto-exposure settle time, whether white balance applies to raw output, and the per-frame cost of copying, de-Bayering and JPEG XL encoding. The results of the first run, and what they mean, are in docs/camera-capture-measurements.md.
Not part of the build: CMake only compiles the tools it names.
| File | What |
|---|---|
camera_bench.py |
drives the camera through vmbpy; one JSON line per measurement |
bench.cpp |
CPU cost per frame, using the production src/camera/JpegXlEncoder.cpp unchanged |
lossless_check.cpp |
encodes at jxl_distance = 0 through the production encoder, decodes, and compares pixels |
encode_bench.cpp |
JPEG XL cost per input format, effort, threads and quality; saving throughput and pile-up; CPU temperature, clock, load and power |
make_synthetic.py |
a sharp, detailed stand-in frame (dead-leaves texture with sensor noise) in the camera's formats |
run_camera_tests.sh |
the camera sequence: streaming per format and link limit, under load, auto-exposure, white balance (~25 min) |
run_encode_tests.sh |
the compression, pile-up and heat sequence (~90 min) |
summarize_encode.py |
Markdown tables from a run_encode_tests.sh result folder |
raw12_bench.cpp |
the 12-bit pipeline: unpack, de-Bayer, encode and write timed per stage; quality against the frame's own lossless image; pile-up, heat; example files (2026-10-02) |
run_raw12_camera.sh / run_raw12_encode.sh |
the 12-bit camera sequence (~25 min) and processing sequence (~1.5 h) |
run_common_frame.sh |
the processing tests on one shared frame (common-frame/, git-ignored, rebuilt from 121's lossless 12-bit example), so towers compare exactly (~25–40 min) |
plot_common.py |
the shared-frame tables and charts (per-step time, size per quality setting, saves per second); laptop venv |
compare_examples.py |
scores every 12-bit example against the lossless 12-bit colour image (PSNR, SSIM); laptop venv |
compare_8_vs_12.py |
8-bit against 12-bit on a dark scene: noise, levels, JPEG XL error per brightness region, crop figures (2026-10-07); laptop venv |
make_figures.py |
example-image figures (crops, difference maps, 8 vs 12-bit shadow) from the example .jxl files; laptop venv |
plot_results.py |
every chart and table of docs/raw12-pipeline-measurements.md from the results folders; laptop venv |
results-*/ |
raw results of each run, one folder per tower and day |
examples-*/ |
full-size example .jxl files (git-ignored, 100+ MB per tower) |
Running it on a tower
The camera accepts one program at a time, so the tower program must be stopped, and the tower captures nothing until it is started again.
ssh ggs@<tower>
sudo systemctl stop fire-gimbal-control
cd ~/projects/fwt_2a/software/tools/camera-bench
PY="env GENICAM_GENTL64_PATH=/opt/VimbaX/cti $HOME/.venvs/vmbpy/bin/python camera_bench.py"
$PY info # current settings and ranges
$PY maxfps BayerRG8 250000000 # full res; also BayerRG12p, RGB8; also 450000000
$PY stream BayerRG8 250000000 14.75 60 # FPS = the max from maxfps; 60 s
$PY ae BayerRG8 250000000 14.75 dark 100 # dark|bright step, IntensityControllerRate
$PY wb BayerRG8 250000000 # also RGB8, as the reference
$PY save BayerRG8 250000000 3 frames # also BayerRG12p and RGB8: bench.cpp needs all three
Then the CPU benchmark, built from the repo so it uses the production encoder:
cd ~/projects/fwt_2a/software
g++ -O2 -std=c++17 -Iinclude tools/camera-bench/bench.cpp src/camera/JpegXlEncoder.cpp \
src/core/Logger.cpp -o /tmp/bench $(pkg-config --cflags --libs libjxl libjxl_threads opencv4) -pthread
cd tools/camera-bench
/tmp/bench all frames /tmp/bench-out # every measurement
/tmp/bench raw8true frames /tmp/bench-out # lossless raw 8-bit through a correct lossless path
/tmp/bench load frames /tmp/bench-out 62 & # encode in a loop, while running `$PY stream ...`
lossless_check.cpp and encode_bench.cpp build the same way. lossless_check runs from the
directory holding frames/.
The full sequences (2026-10-01)
Copy the programs into one working directory on the tower (for example ~/fgc-bench), with the
binaries bench and encode_bench built there. Then, with the tower program stopped:
nohup sh run_camera_tests.sh results > camera.log 2>&1 & # ~25 min; also saves frames/
python3 make_synthetic.py synth 5328 3040 # the camera's resolution; needs numpy + OpenCV
nohup sh run_encode_tests.sh results-enc > enc.log 2>&1 & # ~90 min
python3 summarize_encode.py results-enc
make_synthetic.py needs Python's numpy and OpenCV; where the tower lacks them (tower 111), generate
the frame elsewhere and copy it. run_encode_tests.sh reads package power through a root loop
(sudo -n), which it starts and stops itself. In run_camera_tests.sh, auto-exposure is capped at
20 ms (EXPOSURE_CAP_US), so long exposures cannot limit the frame rate being measured.
Finally, put the camera back on its profile, confirm frames flow, and restart the program:
$PY restore ~/projects/fwt_2a/software/config/camera_profiles/rgb/standard.xml
sudo systemctl start fire-gimbal-control
rm -rf frames /tmp/bench-out # full-resolution frames are 16-49 MB each
The script never saves a camera user set or writes an XML. The program reloads its profile at start anyway, so nothing a run changes survives it.
The 12-bit pipeline run (2026-10-02)
On the tower, with raw12_bench built like bench and the tower program stopped:
python3 make_synthetic.py synth <width> <height> # or copy one in
nohup sh run_raw12_camera.sh results-r12 > camera-r12.log 2>&1 &
nohup sh run_raw12_encode.sh results-r12 > encode-r12.log 2>&1 & # after the first ends
Then on the laptop, in a venv with matplotlib, numpy, imagecodecs and opencv-python-headless
(~/.venvs/fgc-plots), after copying results-r12/ and examples/jxl/ off the tower:
P=~/.venvs/fgc-plots/bin/python
$P compare_examples.py examples-<date>-<tower> results-<date>-<tower>/examples_compared.json
$P make_figures.py examples-<date>-<tower> ../../docs/images/raw12/examples/<tower> [dx,dy sx,sy]
$P plot_results.py ../../docs/images/raw12 121=results-2026-10-02-tower121 [111=...]
Things to know
- vmbpy 1.2.0 cannot stream under Python 3.14 as installed: its runtime type check raises
NameError: name 'Camera' is not defined.camera_bench.pybypasses that check in its own process. - Per-frame data:
ChunkEnableis writable only whileChunkModeActiveis off, so the script enables the items first and activates chunk mode last. - Colour order: the camera's
BayerRG8is OpenCV'sCOLOR_BayerBG2RGB. - The camera is the first real one found. Set
CAM_ID=DEV_...to choose one. - 12-bit JPEG XL from 16-bit words needs
JxlEncoderSetFrameBitDepth(JXL_BIT_DEPTH_FROM_CODESTREAM), or libjxl rescales the values and "lossless" is not. Both C++ programs set it. - The
aebright step is limited by the camera's limits: if gain is already atGainAutoMaxand exposure nearExposureAutoMax, it cannot force a full ×16. The result records the factor actually reached (forced_factor).