Skip to content

PP-1: the axis check (swaps and contradictions are errors; plain stacks confirmed) - #64

Merged
guijacquemet merged 5 commits into
mainfrom
claude/axes-errors
Nov 1, 2026
Merged

guijacquemet merged 5 commits into
mainfrom
claude/axes-errors

Conversation

@guijacquemet

@guijacquemet guijacquemet commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

PP-1 of the v0.4.0 preprocessing plan (preprocessing-design-v0.4.md, revision 4, frozen; §3 "Axes" and §8). It covers the axis check only. The removals, manifest v2, per-file confirmation, masks, modes and conversion come in PP-2 onward.

Summary

  • axis_check.py (new module, pure): header_axis_issue, axis_disagreements, likely_axis_cause and check_header_axes compare each TIFF's confirmed roles with the axes its header names (named_axes), position by position.
    • axes_yx_swapped and axes_contradict_header are errors. Each error names every file with its header axes and confirmed roles, plus the likely cause. The causes it can name are:

      • tifffile's CYX default for 3-D stacks saved with imagej=True/ome=True;
      • an ImageJ slice stack read as Z;
      • rows and columns exchanged.

      Each error offers two fixes: correct the axes, or save named-axis copies (OME-TIFF) with your roles. Such an image is never read, either transposed or under a contradicted declaration.

    • axes_not_named is a warning. A plain stack (IYX, QYX) is read with the confirmed roles and recorded as AxisReview.axes_from[path] == "confirmed"; every other image is "header". The warning recommends named copies before segmenting.

    • These are not errors: S→C, length-1 axes without a role, and OME-Zarr images (they take their NGFF roles).

    • AXIS_ERROR_CODES holds the two errors and AXIS_CHECK_CODES holds all three.

  • axes.py: review_axes runs the check. AxisReview gains axes_from, axis_errors, axis_check, axis_error_files, confirmed_files and only_axis_errors. POSITION_AXES moved to axis_check.
  • mask_checks.py (new module): mask_size_issues compares a mask's Y/X with its image's Y/X by the confirmed roles, not the header letters (this matters for plain stacks). mask_issues gathers a run's mask issues and skips images with an axis error. masks.mask_plane_issues(skip=) supports that skip. find_masks compares sizes only when the header names Y and X.
  • notebook_axes.py (new module): shows the three codes as one "Axis check" card. Before confirmation, each role that disagrees with the header is marked, and an unnamed axis is marked "recorded as yours". On success the notebook says "Axes checked: they are correct for every image". Confirmation is disabled while there are errors.
  • CLI/report: check and run print axis_check_summary ("Axes checked: …") or the axis-check issues. check exits 1 on axis errors.
  • Deprecated: check_declared_axes and declared_axes_transposed raise a DeprecationWarning. They are removed before the v0.4.0 release (PP-2, design §6).
  • Docs: contract §2/§8/§15/§16, user guide, plan §12 Q45, AGENTS.md and the notebook text.

Scope note: audit-2 B-1 is not fixed here

Roles are still applied by arrangement: review_axes and file_mapping_for give every file with the same axes the one experiment mapping. So a confirmation for a plain-stack arrangement covers every file of that arrangement, including files added later. axes_from is kept only in AxisReview and is not yet written to the run manifest (still version 1). Both are PP-4: manifest v2 with per-file roles/axes_from, plus per-file plain-stack confirmation.

Decisions

  • If a file has both a contradiction and a swap, the contradiction is reported. A named axis longer than 1 without a role stays unassigned_axis.
  • A plain stack may still get roles that put X before Y in storage, as in v0.3.4. Its positional Y/X are not named, so this is the user's confirmation, not a contradiction.

Notes for review

  • Breaking: runs whose roles swap or contradict the header no longer run.
  • The notebook preview still renders a plane under the roles being edited, swapped ones included. This is how a wrong role is spotted, and nothing it shows is written. Notebook cards are revisited in PP-10.
  • I tested the notebook only headless, not in JupyterLab.
  • The history includes a first version in which plain stacks were errors. Later commits revised it without force-pushing.

Review (independent agent)

Two review commits:

  • 5f9b015: the axis errors named a morphobricks-preprocessing convert command that does not exist yet (PP-11), and the contract cited a non-existent §17. The plain-stack warning, the notebook and the user guide claimed the run manifest records axes_from, but manifest v1 has no such field. The messages now name only what exists, and the tests assert both points.
  • b284034: the deprecation text promised removal in v0.5.0. Spec revision 4 removes the functions in v0.4.0 (PP-2).

§9 metrics:

  • New modules:
    • axis_check.py is 300 lines, mask_checks.py 102 and notebook_axes.py 99.
    • The longest function is axis_check_issues, with 45 non-blank lines and complexity 8.
    • All functions are annotated, with at most 4 parameters. Ruff C901/PLR0913/PLR0915/ANN is clean.
  • Already over the limits on main, slightly grown here (for the PP-0 baseline):
    • axes.py: 777 → 867 lines; review_axes body 97 → 108 lines (complexity 14).
    • cli._check: 46 → 52 lines; notebook_ui._toggle_confirmation: 25 → 48 lines.

Tests

  • New file tests/test_axis_check.py with 60 tests. Against old main it fails at import. With the new names stubbed:
    • 57 fail;
    • 3 pass: test_the_codes, which passes only because of the stub, and 2 controls of unchanged behaviour.
  • Full gate as CI (ruff check ., ruff format --check ., pytest with .[dev,notebook,zarr]) at b284034:
    • Python 3.13 with zarr 3: 1557 passed, 1 skipped (running as root ignores file modes).
    • Python 3.11 with zarr 2.18: 1539 passed, 19 skipped (the same root skip plus 18 OME-NGFF 0.5 tests that need zarr 3).

🤖 Generated with Claude Code

https://claude.ai/code/session_01WX6rdMV1r5jUPUR94kxV5a

…radict_header

review_axes now compares every TIFF's confirmed roles with the axes its own
header names (header_axis_issue / axis_disagreements on named_axes). A plain
stack whose metadata does not name an axis longer than 1, roles that swap the
named Y and X, and roles that otherwise contradict the header are errors that
name every file with its header and confirmed axes and recommend saving the
images as OME-Zarr. Nothing is read transposed or under a contradicted
declaration; OME-Zarr images take their NGFF roles and never fail.

Masks are compared with their image's Y and X by the confirmed roles
(mask_size_issues; find_masks compared with the header's letters), and the
mask size and plane checks skip images with an axis error (B1).

The notebook marks each role that disagrees with the header before
confirmation and shows "Axes checked" on success; check and run print the
same. check_declared_axes and declared_axes_transposed are deprecated
(removed in v0.5.0). Contract §2/§8/§15/§16, user guide, plan §12 Q45.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WX6rdMV1r5jUPUR94kxV5a
…rors

Revised design after the audit (owner decision of 1 November 2026):

- axes_not_named is a warning, not an error. A plain stack is read with the
  roles the user confirms, and AxisReview.axes_from records each file as
  "confirmed" (plain stack) or "header". The warning recommends named-axis
  copies before segmenting in other tools.
- axes_yx_swapped and axes_contradict_header stay errors. They now name the
  likely cause (likely_axis_cause: tifffile's CYX default for 3-D stacks
  saved with imagej=True or ome=True, an ImageJ slice stack read as Z, or
  rows and columns exchanged) and give two fixes: correct the axes, or
  convert to named-axis copies.
- The notebook shows the three codes as one "Axis check" card. A role on an
  unnamed axis is marked as "recorded as yours" rather than as an error.
  The success message stays and names the confirmed plain stacks.
- AXIS_ERROR_CODES now holds the two errors; AXIS_CHECK_CODES holds all
  three.
- B1 is kept: mask sizes are compared by the confirmed roles, which still
  matters for plain stacks.

Docs: contract §2/§8, user guide, plan §12 Q45, AGENTS.md, notebook text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WX6rdMV1r5jUPUR94kxV5a
…o modules

The code quality rules for v0.4 are focused modules, no growth of oversized
files, and NumPy docstrings on every function. So:
- axis_check.py: the axis-check codes, header_axis_issue,
  axis_disagreements, likely_axis_cause, axis_check_issues and
  check_header_axes (axes.review_axes calls it).
- notebook_axes.py: axis_check_html and header_marks.
- mask_checks.py: mask_size_issues and mask_issues (moved from masks.py and
  run.py).

No behaviour change. The names stay exported from the package.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WX6rdMV1r5jUPUR94kxV5a
@guijacquemet guijacquemet changed the title Axes redesign P1: axis problems are errors (axes_not_named, axes_yx_swapped, axes_contradict_header) Axes redesign P1: the axis check (swaps and contradictions are errors; plain stacks confirmed) Nov 1, 2026
The axis errors told users to run `morphobricks-preprocessing convert`, a
command that does not exist yet (conversion is design v0.4 §5, PP-11), and
the contract cited a §17 that does not exist. The plain-stack warning, the
notebook and the user guide said the run manifest records the roles as
confirmed, but manifest version 1 has no axes_from field (manifest v2 is
PP-4). The messages now name only what exists; tests check both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WX6rdMV1r5jUPUR94kxV5a
check_declared_axes and declared_axes_transposed are removed in v0.4.0
(design v0.4 revision 4, §6 and PP-2), so the deprecation warning, the
docstrings, AGENTS.md, the contract, the user guide and plan Q45 no longer
promise them until v0.5.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WX6rdMV1r5jUPUR94kxV5a
guijacquemet pushed a commit that referenced this pull request Nov 1, 2026
PP-0 now merges after #64 (PP-1). Its baseline covers the combined code, so
main stays green whichever PR is reviewed first. It adds layers for #64's
modules: axis_check and mask_checks are pure, notebook_axes is ui.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WX6rdMV1r5jUPUR94kxV5a
@guijacquemet guijacquemet changed the title Axes redesign P1: the axis check (swaps and contradictions are errors; plain stacks confirmed) PP-1: the axis check (swaps and contradictions are errors; plain stacks confirmed) Nov 1, 2026
@guijacquemet
guijacquemet marked this pull request as ready for review November 1, 2026 11:59
@guijacquemet
guijacquemet merged commit d7a538e into main Nov 1, 2026
7 checks passed

Copy link
Copy Markdown
Contributor Author

Independent review against design v0.4 revision 4 (§3 Axes, §9), merged as d7a538e (merge commit).

Fixes made during review:

  • 5f9b015: the axis errors pointed to a morphobricks-preprocessing convert command that doesn't exist yet (PP-11), and the contract cited a non-existent §17. Several places also said the run manifest records axes_from, but manifest v1 has no such field.
  • b284034: the deprecation text promised removal in v0.5.0. Revision 4 removes these functions in v0.4.0 (PP-2).

Not fixed here, left to PP-4: audit-2 B-1. Roles are still applied by arrangement, so one plain-stack confirmation covers every file of that arrangement, and axes_from is not yet written to the manifest.

The gate at b284034:

  • Python 3.13: 1557 passed, 1 skipped (root file modes).
  • Python 3.11 with zarr 2.18: 1539 passed, 19 skipped (root, plus 18 OME-NGFF 0.5 tests that need zarr 3).
  • CI is green on all 7 jobs.

Generated by Claude Code

guijacquemet pushed a commit that referenced this pull request Nov 1, 2026
The baseline is regenerated on the new main: the review commit 5f9b015 adds
a line to axes.py. PP-0 creates the baseline, so it records main as it is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WX6rdMV1r5jUPUR94kxV5a
guijacquemet pushed a commit that referenced this pull request Nov 1, 2026
Conflicts in the docs, axes.py and the header-axes tests were resolved hunk
by hunk with this PR's removals. The review commits' other edits are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WX6rdMV1r5jUPUR94kxV5a
@guijacquemet
guijacquemet deleted the claude/axes-errors branch November 14, 2026 08:20
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