Skip to content

v0.4.0rc5: tie-break gives no parent, ImageJ unit without resolution, plan Q66/Q73/Q74, changelog - #143

Merged
guijacquemet merged 5 commits into
mainfrom
claude/rc5
Nov 3, 2026
Merged

guijacquemet merged 5 commits into
mainfrom
claude/rc5

Conversation

@guijacquemet

@guijacquemet guijacquemet commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The owner's decisions of 3 November 2026, and version 0.4.0rc5.

Masks: a tie between parents gives no parent (plan §12 Q74)

  • mask_scan.pair_counts: before, np.lexsort((-shared, kid)) was stable, so when two or more parents shared a child's largest overlap exactly, the child went to the parent with the lowest label index. That was the only tie path: the same_objects counts come from a maximum matching, whose size does not depend on label numbering (Q67), and the hierarchy is sorted by name.
  • New pure module mask_pairs.py with best_parents (gives each child's largest overlap and whether it is tied), plus one_to_one_matches and match_counts, moved from mask_scan so that it stays under 600 lines. mask_scan still re-exports one_to_one_matches. The layer is declared pure.
  • A tied child has no parent and is never inside. If the tied share would make it inside (at least inside_pixel_share), it is counted in the new evidence ambiguous_parent. A tie below that share is split_between_parents, as before (it gets no parent either way). Every child is counted in exactly one of inside, ambiguous_parent, split_between_parents or outside_parents.
  • MASK_REPORT_VERSION 2 -> 3.
  • mask_text: new mask_ambiguous_lines. mask_report_summary (the masks command) has an "Objects given no parent" section that covers every pair, including pairs whose relation is none. The relationship table has a new last column. The notebook's Describe the masks card shows the lines.

TIFF calibration (physical_sizes, tiff_size_values)

  • An ImageJ TIFF with a unit but neither XResolution nor YResolution now warns invalid_physical_calibration with the reason "X, Y: the unit 'um' is given but neither the XResolution nor the YResolution tag is (a unit without a resolution)". Before, it read as uncalibrated with no reason.
  • A TIFF with no pages (tifffile opens it and only logs) gives "the TIFF file has no pages (no image)". Before, it read as uncalibrated with no reason.
  • A file with neither a unit nor resolution tags is still uncalibrated.

Docs

Version: 0.4.0rc5 in pyproject.toml, the notebook (current_version, RELEASE_TAG and the Step 0 callout), notebook_latest_versions.yaml, the README and the user guide. Historical rc4 mentions are kept: the rc4 manifest fixture and its test, the contract's "rc4 keys", and the plan's rc4 entries.

Consumers of the tie-break fields (read-only grep, 3 November 2026)

  • CellPatcher: checked at HEAD 438e6d4 (branch claude/fix-audit2) and at origin/main 79adc3a.
  • MorphoFeature: checked at HEAD 9e66ca6 (branch claude/request-check-hook) and at origin/main 142c448.
  • Searched for: inside, parents_with_*, split_between_parents, outside_parents, inside_parent_larger, ambiguous_parent, mask_relationships, pair_counts, scan_masks, MaskReport.
  • Result: no code reads any of them.
    • The only hits are a pytest parameter name inside in CellPatcher tests/test_notebook_session.py.
    • In MorphoFeature, prose in docs/contracts/provider-api.md uses "inside" for region masks, and D79 in the decision ledger says "the mask scan lives in preprocessing".

Decisions

  • Owner, 3 November 2026: the tie-break, the ImageJ unit without resolution, Q66 confirmed, and Q73's follow-up.
  • Within those decisions, my choices: a tie below inside_pixel_share stays split_between_parents, not ambiguous_parent, because it gets no parent under either rule. The no-pages case is reported as invalid rather than raised.

Notes for review

  • child_pixels_with_parent_label compares label values, as designed (it is the grouped_by evidence). So the permutation test applies one label map to both masks; independent maps would change that count legitimately.
  • The new tests were checked against the old behaviour: with best_parents patched to report no ties (the old lowest-index choice), 7 of the 9 tie tests fail. Against the old source, the 3 new TIFF tests fail.
  • ruff check --ignore-noqa gives 60 findings, the same 60 as origin/main, all existing noqa'd entries. tools/code_rules.py and its baseline are unchanged (SHA cc04a420).
  • Not verified: the notebook in a real JupyterLab or Colab (tested headless only).

Tests

Local, Python 3.13, .[dev,notebook,zarr] (zarr 3): 2197 passed, 4 skipped, 0 failed.

  • The 4 skips are platform skips: 3 for "this file system tells names apart by case", 1 for "permissions that ignore file modes".
  • ruff check ., ruff format --check . and python tools/check.py --rules all pass.
  • CI: see the checks.

🤖 Generated with Claude Code

https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
pair_counts took the first parent in label order when two or more parents
shared a child's largest overlap exactly, so inside and parents_with_N_children
depended on the label numbering. A tie now gives no parent; when the tied share
would make the child inside it is counted as ambiguous_parent. The report is
version 3; masks, the notebook card and the relationship table show it. The
overlap helpers move to the pure module mask_pairs (mask_scan stays under 600
lines). Tests: the tie, three tied parents, a tie below the largest overlap,
random masks under permuted labels, best_parents against a reference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
…, as invalid

An ImageJ TIFF with a unit but neither XResolution nor YResolution read as
uncalibrated with no reason; it is now invalid_physical_calibration, "a unit
without a resolution" (owner, 3 November 2026). A TIFF file without pages,
which tifffile opens with only a log message, gives the reason "no pages".
A file with neither unit nor resolution is still uncalibrated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
Q66 confirmed by the owner (3 November 2026); Q73's follow-up decided: neither a
stated reshape nor a reorder in v0.4, kept in issue #144; Q74 records the
tie-break and the consumers checked. The changelog gains a section for rc5 (PRs
#139, #140, #142, #143) with migration notes: mounts refused, position_id
required in v2, micrometre conversion within 1 ULP, --verify-content, and the
tie-break. The migration guide gets the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
@guijacquemet
guijacquemet marked this pull request as ready for review November 3, 2026 14:09
… on its own

Review of #143. In check and run a TIFF without pages is the error unreadable_tiff (its
header has no series), as before rc5; only read_physical_sizes gives it the "no pages"
reason. The CHANGELOG, contract, migration guide and user guide said it was
invalid_physical_calibration. They also said every tied child is ambiguous_parent; only a
tie whose share reaches inside_pixel_share is, and a smaller one stays split, as the
contract and plan Q74 state.

Tests: the folder check of a TIFF without pages; every count but
child_pixels_with_parent_label is unchanged when the child and parent masks are
relabelled independently (three inside shares, ties present).

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

Copy link
Copy Markdown
Contributor Author

Independent adversarial review of the rc5 PR, starting at head 83ce987. Verdict: ready to merge after one documentation fix (e5e6a74).

Tie-break (Q74). It holds up.

  • Each child is counted exactly once, in one of inside, ambiguous_parent, split or outside. The tests check this on random masks.
  • Ties are found by comparing integer pixel counts (shared == most), so the test is exact. A 50 vs 49 near-tie stays inside.
  • I relabelled the parent and child masks independently, with inside shares of 0.5, 0.34, 0.25 and 0.2, over 2,000 random cases containing 1,306 ambiguous children. Every count was unchanged except child_pixels_with_parent_label, which is defined by comparing label numbers. That test is now in the suite (test_the_counts_are_the_same_when_each_mask_is_numbered_on_its_own).
  • A three-way tie is split at the default share and ambiguous_parent at 1/3.
  • The same_objects matching code moved verbatim to mask_pairs, and the re-export in mask_scan is a single import.
  • A tie below inside_pixel_share staying split_between_parents is consistent with the owner's rule. Such a child already had no parent, because no parent is assigned below the share, and its counts never depended on the numbering. ambiguous_parent means "would be inside, but which parent is undetermined". Moving those children would change split for cases the decision did not cover. At the default of 0.5, ambiguous means exactly half in each of two parents.
  • Mutation testing: 17 mutants, 16 killed. The survivor (first versus last tied host) is equivalent, because a tied host is never used.

ImageJ / no pages.

  • Both cases are warnings. A file with neither a unit nor a resolution stays uncalibrated with no reason.
  • check, run and RunInputs agree for the unit-without-resolution case. I checked this end to end: one warning, and the image is recorded as uncalibrated.
  • Fixed (documentation, important for the release): in check and run, a TIFF without pages is the error unreadable_tiff, as before. Its header has no series, so the new reason is reached only through read_physical_sizes. The CHANGELOG, contract, migration guide and user-guide table said it was invalid_physical_calibration. They now say where each one is reported, and a test pins the folder-check behaviour. The CHANGELOG and user guide also said every tie is ambiguous_parent; they now state the share condition.

Release.

§9.

  • The touched files are within the limits (at most 570 lines).
  • check.py --rules passes, and the code rules baseline file is unchanged. tools/code_rules.py is unchanged (cc04a420).
  • ruff check --ignore-noqa is clean on every touched source file.

Gate on e5e6a74:

  • ruff check and ruff format: clean.
  • pytest with .[dev,notebook,zarr]: 2201 passed, 4 skipped (three because this file system is case-sensitive, one because the tests run as root), 0 failed.
  • CI: 7/7.

Generated by Claude Code

@guijacquemet
guijacquemet merged commit 1993f2b into main Nov 3, 2026
7 checks passed
@guijacquemet
guijacquemet deleted the claude/rc5 branch November 3, 2026 14:29
@guijacquemet
guijacquemet restored the claude/rc5 branch November 3, 2026 14:29
@guijacquemet
guijacquemet deleted the claude/rc5 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