Skip to content

fix: PMT crashes in a single-channel Simulation (float() on a (1, 1, 1, 1) exposure) - #159

Merged
tlambert03 merged 1 commit into
tlambert03:mainfrom
federico-carrara:fc/fix/PMT_per_channel
Oct 13, 2026
Merged

tlambert03 merged 1 commit into
tlambert03:mainfrom
federico-carrara:fc/fix/PMT_per_channel

Conversation

@federico-carrara

Copy link
Copy Markdown
Contributor

Description

Simulation.digital_image() crashes with a PMT detector when the simulation has a single channel:

import numpy as np
import microsim.schema as ms
from microsim.schema.detectors import lib

truth = np.ones((1, 64, 64))
sim = ms.Simulation(
    truth_space=ms.ShapeScaleSpace(shape=truth.shape, scale=(0.1, 0.1, 0.1)),
    sample=ms.Sample(labels=[ms.FluorophoreDistribution.from_array(truth)]),
    modality=ms.Identity(),
    detector=lib.PMT_GAASP,  # default channels: [FITC]
    exposure_ms=0.01,
)
sim.digital_image()
# TypeError: only 0-dimensional arrays can be converted to Python scalars

The exposure time is a single number, but by the time it reaches the PMT it is a 4-d array.

Call chain:

  1. Simulation.digital_image(): called without exposure_ms, it builds one exposure per channel as a DataArray over c (shape (1,)) and passes it to detector.render() → _Detector.simulate().
  2. _Detector.simulate(): exposure_s = exposure_ms / 1000 * n_avg (still (1,)), then _per_channel(exposure_s, ndim) from _detector.py adds trailing axes so that it broadcasts against the (C, Z, Y, X) image: shape (1, 1, 1, 1). This goes to _after_multiplication() as time_s.
  3. PMT._after_multiplication(): passes time_s (saturation, if max_output_current_ua is set) or dwell_s (amplifier bandwidth, if time_constant_us > 0) as dt to the _per_channel in _point.py. That is a different helper with the same name.
  4. _per_channel() in _point.py: dt.size == 1 is true, so it calls float(dt) on the (1, 1, 1, 1) array. float() on an array with ndim > 0 has been deprecated since NumPy 1.25, and it raises in NumPy 2.4 (tested with 2.4.6).

Why the tests didn't catch it: the crash needs all four of these at once:

  • exposures passed per channel. A float exposure_ms is never reshaped in step 2, and every test in test_point_detectors.py calls render() with a float.
  • exactly one channel. With C > 1 the else branch runs, and it flattens dt with dt.ravel().
  • a PMT with saturation or a bandwidth limit. Both PMT presets set max_output_current_ua. HyD and the cameras don't use this helper.
  • a NumPy version that raises. Older versions only emit a DeprecationWarning.

Changes

  • _per_channel() in _point.py: float(dt) → float(dt.item()). .item() returns the single value whatever the number of axes.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Oct 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.15%. Comparing base (0aea364) to head (f176506).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #159   +/-   ##
=======================================
  Coverage   88.15%   88.15%           
=======================================
  Files          48       48           
  Lines        3292     3292           
=======================================
  Hits         2902     2902           
  Misses        390      390           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tlambert03 tlambert03 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch thanks!

@tlambert03
tlambert03 merged commit 8362e0f into tlambert03:main Oct 13, 2026
20 checks passed
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.

2 participants