From 13b98cd877d75fdc7b0a793f0244c87210669de1 Mon Sep 17 00:00:00 2001 From: parallelArchitect <236280545+parallelArchitect@users.noreply.github.com> Date: Sun, 21 Jun 2026 01:23:16 -0400 Subject: [PATCH 1/5] fix: convert em_rates to backend array in Identity.render (#124) Identity.render() declared **kwargs instead of an explicit xp parameter, so xp was never in scope to convert em_rates to the active backend. truth is converted via xp in the ground_truth() path; em_rates from filtered_emission_rates() was not, causing a TypeError when multiplying a backend array (cupy) against an unconverted numpy array. Fix mirrors the working solution from the issue reporter's fork: em_rates.copy(data=xp.asarray(...)) preserves the xarray wrapper so .coords access below still works. Verified against both cupy and numpy backends, using synthetic MatsLines data and real COSEM segmentation data (jrc_hela-3, er-mem_pred). --- src/microsim/schema/modality/_simple_psf.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/microsim/schema/modality/_simple_psf.py b/src/microsim/schema/modality/_simple_psf.py index 16a15ca..16a6f55 100644 --- a/src/microsim/schema/modality/_simple_psf.py +++ b/src/microsim/schema/modality/_simple_psf.py @@ -209,6 +209,7 @@ def render( truth: xrDataArray, # (F, Z, Y, X) em_rates: xrDataArray, # (C, F, W) *args: Any, + xp: NumpyAPI, **kwargs: Any, ) -> xrDataArray: """Render a 3D image of the truth for F fluorophores, in C channels. @@ -217,7 +218,9 @@ def render( already convolved with the PSF. Therefore, we simply compute the emission flux for each fluorophore and each channel. """ - em_image = em_rates.sum(Axis.W) * truth + em_rates = em_rates.sum(Axis.W) + em_rates = em_rates.copy(data=xp.asarray(em_rates.data)) + em_image = em_rates * truth return DataArray( em_image, dims=[Axis.C, Axis.F, Axis.Z, Axis.Y, Axis.X], From 82415608fa7b8c820cb8d14dbbf01c7472e8284a Mon Sep 17 00:00:00 2001 From: parallelArchitect <236280545+parallelArchitect@users.noreply.github.com> Date: Sun, 21 Jun 2026 01:46:27 -0400 Subject: [PATCH 2/5] fix: match base class render() signature, satisfy mypy xp as a keyword-only param via **kwargs violated Liskov substitution against _PSFModality.render()'s signature. Matches base class exactly: truth, em_rates, objective_lens, settings, xp as named params. Re-verified GPU (cupy) and CPU (numpy) backends after the signature change. --- src/microsim/schema/modality/_simple_psf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/microsim/schema/modality/_simple_psf.py b/src/microsim/schema/modality/_simple_psf.py index 16a6f55..f60a2fd 100644 --- a/src/microsim/schema/modality/_simple_psf.py +++ b/src/microsim/schema/modality/_simple_psf.py @@ -208,9 +208,9 @@ def render( self, truth: xrDataArray, # (F, Z, Y, X) em_rates: xrDataArray, # (C, F, W) - *args: Any, + objective_lens: ObjectiveLens, + settings: Settings, xp: NumpyAPI, - **kwargs: Any, ) -> xrDataArray: """Render a 3D image of the truth for F fluorophores, in C channels. From 805a107d69a665f06f64c5ac28376c37d7b920cf Mon Sep 17 00:00:00 2001 From: parallelArchitect <236280545+parallelArchitect@users.noreply.github.com> Date: Sun, 21 Jun 2026 02:22:20 -0400 Subject: [PATCH 3/5] test: add regression test for #124 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Covers Identity.render() backend conversion across numpy and cupy (parametrized via the existing np_backend fixture). Scoped narrowly to optical_image() — the boundary of what #124 actually touches — to avoid an unrelated, pre-existing failure in the detector simulation step that surfaces when running the full pipeline. --- tests/test_simulation.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_simulation.py b/tests/test_simulation.py index bf9f267..2c54616 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -166,3 +166,17 @@ def test_simulation_write_from_ground_truth(tmp_path: Path) -> None: ) sim.run() assert (tmp_path / "output.zarr").exists() + + +def test_identity_modality_backend_conversion(np_backend: ms.BackendName) -> None: + """Regression test for #124: em_rates must be converted to the active + backend before use in Identity.render(), same as truth already is.""" + sim = ms.Simulation( + truth_space=ms.ShapeScaleSpace(shape=(8, 32, 32), scale=(0.2, 0.1, 0.1)), + output_space={"downscale": 1}, + sample=ms.Sample(labels=[MATSLINES]), + modality=ms.Identity(), + settings=ms.Settings(np_backend=np_backend), + ) + result = sim.optical_image() + assert result.shape[1:] == sim.truth_space.shape From 793728ceacc8101f0227450fce9809ff29aa60cb Mon Sep 17 00:00:00 2001 From: parallelArchitect <236280545+parallelArchitect@users.noreply.github.com> Date: Sun, 21 Jun 2026 04:15:10 -0400 Subject: [PATCH 4/5] docs: add DEV_NOTES.md with GPU/dependency setup issues --- DEV_NOTES.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 DEV_NOTES.md diff --git a/DEV_NOTES.md b/DEV_NOTES.md new file mode 100644 index 0000000..ad4d7c0 --- /dev/null +++ b/DEV_NOTES.md @@ -0,0 +1,39 @@ +# Development Notes + +Setup friction encountered on this fork during GPU development — kept +here separately so the main README stays clean. Not universal microsim +guidance, just what was hit on this machine. + +## GPU backend (cupy) setup + +1. **numpy version conflict** — microsim's metadata pins `numpy<2.0`, but + `cupy-cuda12x` requires `numpy>=2.0`. In practice, numpy 2.4.6 worked + fine at runtime with this version of microsim despite the metadata + warning — if `pip` flags the conflict, it's likely safe to ignore, + but verify by running a real simulation before trusting it. + +2. **CUDA headers not found** (`RuntimeError: Failed to find CUDA headers`) + — if your system has multiple CUDA toolkit installs (common after + driver updates over time), cupy's JIT compiler may not find headers + even with `CUDA_PATH` set correctly. Installing the toolkit extra + directly into the venv sidesteps the problem: +```bash + pip install "cupy-cuda12x[ctk]" +``` + +3. **zarr / numcodecs mismatch** (`ImportError: cannot import name + 'cbuffer_sizes' from 'numcodecs.blosc'`) — zarr 2.17.x expects an + older numcodecs API: +```bash + pip install "numcodecs<0.13" +``` + +4. **COSEM data fetching** — loading real segmentation data (e.g. the + `cosem` example) requires the optional extra: +```bash + pip install "microsim[cosem]" +``` + +None of these are bugs in microsim itself, except where already filed +as upstream PRs (#140, #141) — mostly environment/dependency friction +from multiple existing CUDA/Python installs on a dev machine. From 29f2640de9765a8a02d394734a267755864a493d Mon Sep 17 00:00:00 2001 From: parallelArchitect <236280545+parallelArchitect@users.noreply.github.com> Date: Sun, 21 Jun 2026 04:33:19 -0400 Subject: [PATCH 5/5] docs: link DEV_NOTES.md from GPU support section --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 8bf10c2..5b68f64 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,9 @@ These are not installed by default, see the or [cupy](https://docs.cupy.dev/en/stable/install.html) installation instructions, paying attention to your GPU requirements. Support for torch is planned. +See [DEV_NOTES.md](DEV_NOTES.md) for real setup issues hit on this fork +(numpy/cupy version conflicts, CUDA header errors, zarr/numcodecs mismatch). + ## Usage Construct and run a