Skip to content

Calibrate only the analysed pixels and stack the search areas in step 3 - #179

Merged
kriboe90 merged 3 commits into
masterfrom
claude/image-reading-performance-3a725f
Oct 30, 2026
Merged

kriboe90 merged 3 commits into
masterfrom
claude/image-reading-performance-3a725f

Conversation

@kriboe90

Copy link
Copy Markdown
Contributor

What changed

Once every image is decoded only once for all channels (#178), half of the time of step 3 was still spent outside the decoding. The black level, the white level and the read noise were applied to the whole 24 MP frame, and the quantities of every LED were taken one LED at a time, about 15 numpy calls per LED and channel on arrays of a few hundred pixels.

  • ledsa/core/image_reading.py: ImageChannels keeps the stored values of a RAW image and applies the calibration only to the pixels it is asked for. channel_data_mask_and_noise(channel, pixels) takes an optional selection of row and column indices. The calibrated selection does not depend on the channel and is kept for the next call with the same selection, so the three channels of an image calibrate it once. Without a selection the whole frame is returned as before. _read_img_array_and_noise_from_raw_file is gone; nothing used it any more.
  • ledsa/data_extraction/step_3_functions.py: generate_analysis_data asks for the pixels of its search areas and side bands only, about 7 % of the frame, in one selection that is cached per process like the side band geometry. It stacks the search areas and takes every quantity along the axes of the stack (_analyse_search_areas, _estimate_ring_backgrounds). Only the fits are still made LED by LED. A search area at the border of the image is cut short exactly as data[row - R:row + R, col - R:col + R] cut it, and stacked with those of its shape. _generate_led_analysis_data, used by the debug path and the tests, runs on the same code.
  • ledsa/data_extraction/sideband_background.py: estimate_sideband_backgrounds takes the side band mean of all LEDs at once with reduceat. estimate_sideband_background uses it for a single LED.

Why

Step 3 for three channels on 24 MP CR2s of demo_2, 981 LEDs, without fits (-s3_fast), warm file cache, M3 Pro:

master #178 this PR
1 core, ms per image 1455 621 375
8 cores, images/s (96 images) 2.6 3.5 13.5

On one core, ~335 ms of the 375 ms is reading the file, almost all of it LibRaw's decode; the analysis of the three channels takes 39 ms instead of about 300 ms. Decoding alone tops out at about 21 images/s with 8 processes on this machine, so step 3 is now close to that limit. With #178, 8 cores gave only 2.2× of one core; now they give 5×, probably because each worker no longer moves several full-frame float arrays through memory per image.

For the reviewer

  • The results do not change. Every value of 10 images × 3 channels × 981 LEDs is bit-identical to Read every image once for all channels in step 3 #178, as is every CSV line, and the same holds for 280 fitted LEDs (all fit fields except the fit time). The CSV files process_image_data writes for 24 images match those of Read every image once for all channels in step 3 #178 and of master byte for byte below the header, on 1 and on 8 cores.
  • Peak memory per worker drops: besides the decoded uint16 frame and its Bayer colors, a worker holds the calibrated selection (about 1.7 M pixels) instead of full-frame float32 arrays for the data, the noise and the per-channel copies.
  • _estimate_local_background and _estimate_local_background_mean are replaced by the stacked _estimate_ring_backgrounds; their tests now call it through a single-box helper.
  • Unchanged, as before: an LED closer than search_area_radius to the top or left edge of the image gives an empty search area, and step 3 raises on it.
  • The branch was merged with master after Read every image once for all channels in step 3 #178 went in, so the diff contains only this change.

Tests

  • Unit tests: 403 passed. The new test_stacked_search_areas.py compares the stacked analysis with the definitions applied LED by LED on the whole image, for all channels, upright and turned sideways, with a search area cut short at the border, an LED without side bands and skipped LEDs, and checks that only the analysed pixels are calibrated, once for three channels. New tests in test_image_channels.py cover selected pixels of RAW and PNG images and when the calibration is kept.
  • Robot Framework acceptance tests: 32 passed

🤖 Generated with Claude Code

kriboe90 and others added 3 commits October 30, 2026 11:56
…-fff0f0' into claude/image-reading-performance-3a725f
Once every image was decoded once for all channels, half of the time of
step 3 was still spent outside the decoding. The black level, the white
level and the read noise were applied to the whole frame, and the
quantities of every LED were taken one LED at a time, about 15 numpy
calls per LED and channel on arrays of a few hundred pixels.

ImageChannels keeps the stored values of a RAW image and applies the
calibration to the pixels it is asked for. Step 3 asks for its search
areas and side bands, about 7 % of a 24 MP frame, in one selection, so
that they are calibrated once for all channels. The whole frame can
still be asked for as before.

generate_analysis_data stacks the search areas and takes every quantity
along the axes of the stack: sum, mean, maximum, the median and mean of
the border ring, the noise and the side band mean, which reduceat takes
over the side band pixels of all LEDs at once. A search area at the
border of the image is cut short as the slicing did, and stacked with
those of its shape. Only the fits are still made LED by LED.
_generate_led_analysis_data, used by the debug path and the tests, is
built on the same code.

On the demo_2 CR2s, step 3 for three channels takes 375 ms per image
instead of 621 ms on one core, and analyses 13.5 instead of 3.5 images
per second on eight. The analysis of the three channels takes 39 ms
instead of about 300 ms; the rest is the decoding. Every value and
every CSV line of 24 images is the same as before, with and without
fits.

Unit tests pass, 403; the acceptance tests pass, 32.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kriboe90
kriboe90 merged commit f6a4959 into master Oct 30, 2026
5 checks passed
@kriboe90
kriboe90 deleted the claude/image-reading-performance-3a725f branch October 30, 2026 11:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant