Skip to content

Z projection 1/6: the pure arithmetic (part of #176) - #211

Merged
guijacquemet merged 2 commits into
mainfrom
claude/z-projection-core
Nov 8, 2026
Merged

guijacquemet merged 2 commits into
mainfrom
claude/z-projection-core

Conversation

@guijacquemet

Copy link
Copy Markdown
Contributor

What changed

First PR of the plan for #176 (recorded Z=1 projection copy), following the design docs/restart/designs/pp176-z-projection-design-2026-11-07.md (MorphoFeature, branch claude/restart-doc) and the owner's answers of 2026-11-07 (extend convert, no default method, every image goes through the method, masks never projected, one optional --z-range).

New pure module src/morphobricks_preprocessing/projection.py (no I/O, layer pure), plus tests. Nothing calls it yet; no command or record changes.

  • Projection(method, z_range): frozen, checked in __post_init__ (ValueError for a bad method or range before anything is read). resolve(size) gives the slices used and refuses a range reaching past the image.
  • projection_dtype(dtype, method, slices): the output type from the input type, method and slice count only (never the data). Max keeps the type; integer sums are uint32/int32 (8/16-bit inputs) or float64 holding the exact integer (32-bit inputs); means and float sums are float64. 64-bit integers, bool, float16 and complex are refused as cannot_convert_pixel_type. A sum or mean whose worst case (type extreme x slices) could not be exact is refused as projection_cannot_be_exact, never wrapped or clipped. The returned rule carries dtype_rule, rounding, significant_bits_dropped and the working memory (working_nbytes), for the record and the memory limit in later PRs.
  • PlaneAccumulator: slices added in ascending z into one running plane; a slice of another type or shape is refused, never cast. NaN propagates (no nan* functions), infinities are kept, a finite sum that overflows to infinity is projection_overflow, and inf + -inf is projection_undefined.
  • Projection and ProjectionRefused are exported from the package; AGENTS.md repo map and CHANGELOG updated.

Two corrections to the design's bounds

The design lists int8 sum as exact for Z <= 16 909 320 and int16 for Z <= 65 538. Those only check the positive side; the negative extreme (-128 x Z, -32768 x Z) reaches the int32 minimum first. The code computes both sides: int8 Z <= 16 777 216 (2^24), int16 Z <= 65 536; int32 into float64 is Z <= 2^22 (the design said 2^21, which is the uint32 bound). A test pins every bound and the first refused slice count, and checks the extremes at the bound.

One implementation choice: integer sums to an integer type accumulate in that output type (the bound makes that exact) rather than in a 64-bit accumulator, so the running plane is smaller; values are identical.

Regression test

The end-to-end regression (test_project_max_makes_a_z_stack_analysable) needs the writer and lands in PR 3 of the series. This PR's regression for the defect that made a projection impossible to do exactly is tests/test_projection.py::test_uint16_sum_does_not_wrap (65535 x 3 slices is 196605 in uint32; a uint16 sum gives 65533), together with the bound and table tests.

  • On main (old code): the whole module fails at collection, ModuleNotFoundError: No module named 'morphobricks_preprocessing.projection' (1 error, 0 tests run).
  • On this branch: 87 passed (pytest tests/test_projection.py -W error).

Checks

python tools/check.py on this branch: Ruff, formatting and code rules clean; 4251 passed, 4 skipped, 2 warnings. The same command on origin/main (ad55dd4) gave 4164 passed, 4 skipped, 2 warnings, so the difference is exactly the 87 new tests. The 4 skips are the pre-existing ones (case-insensitive file system x3, file modes x1).

Series: 1 pure module (this PR), 2 record v3, 3 plan and write, 4 CLI, 5 run side, 6 notebook card. PRs 2 and onward are stacked on this one.


Generated by Claude Code

Projection (max/mean/sum, optional slice range), projection_dtype (output type and
exactness bound from input type, method and slice count; refused rather than wrapped)
and PlaneAccumulator (one running plane, z ascending). No I/O, no command yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Scma6W3LC11s3qjXHZuGDm
@guijacquemet
guijacquemet merged commit 68b2a4b into main Nov 8, 2026
7 checks passed
@guijacquemet
guijacquemet deleted the claude/z-projection-core branch November 8, 2026 00:59
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