Repository navigation
Input support for MorphoRelate M3: single folders, physical sizes per axis (TIFF and OME-Zarr), embeddable notebook steps - #20
Merged
Conversation
In the MorphoBricks convention (CellPatcher's trees, MorphoRelate's data
laid out as <condition>/single/<images>) a level written "single" means
"this level exists with one member". Folder discovery refused any folder
named single (reserved_folder_name), so such experiments could not be
read. A folder named single in lower case is now that level's value
"single" when it is the only folder in its place (system folders aside):
<condition>/single/<images> gives biological_repeat and technical_repeat
"single", the same labels as <condition>/<images>.
The reservation came with the initial plan and had no recorded reason
beyond keeping "single" for missing levels. What it protects is that
"single" keeps one meaning, so two cases stay errors under the same code:
another spelling ("Single" would be a label different from the "single"
written for missing levels), and a single folder beside other folders
(its level would have several members, one of them called single).
Decision recorded as plan section 12 question 21; contract section 1 and
8, the user guide, AGENTS.md and the notebook's label help updated. The
CSV, the manifest and their versions are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TP44aBCGzyksTpPS2FFmQ
read_physical_sizes applied the X unit to Y and Z: an OME-TIFF with
PhysicalSizeXUnit="nm" and PhysicalSizeYUnit="µm" reported Y in
nanometres, and a Z size without PhysicalSizeZUnit took X's unit instead
of the OME default (micrometres). PhysicalSizes now has x_unit, y_unit
and z_unit, and unit(role) returns each axis's own unit: OME
PhysicalSize{X,Y,Z}Unit (micrometres when left out), ImageJ unit, yunit
and zunit (X's unit when left out, as ImageJ writes them). space_unit
stays the unit of X, and sizes built with space_unit alone keep it for
every space axis, so existing callers see no change. OME-Zarr copies now
write each axis's own unit.
inspect_image / TiffMetadata gains the field physical_sizes, read from
the header that is already open, so callers such as MorphoRelate's
`inspect` need not open each file twice. Existing fields are unchanged;
the new field is last and defaults to empty sizes.
PhysicalSizes and read_physical_sizes move to the new module
physical_sizes (zarr_input needs them next without importing
zarr_export); they stay importable from zarr_export and the package.
Decision recorded as plan section 12 question 22; contract section 10
and the AGENTS.md map updated. The CSV, the manifest and their versions
are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TP44aBCGzyksTpPS2FFmQ
open_ngff read the axes of an OME-Zarr image but never its OME-NGFF coordinateTransformations or axis units, so the physical size of its pixels was not available. MorphoRelate milestone M3 reads OME-Zarr input and needs them as it gets them for TIFF files. NgffImage gains physical_sizes, in the PhysicalSizes form read_physical_sizes returns for TIFF files: the scale of level 0's coordinateTransformations, multiplied by the multiscales entry's own scale when it has one (OME-NGFF applies them in turn; translations do not change sizes), for X, Y, Z and T, with each axis's unit as written. OME-NGFF 0.4 and 0.5 are read alike. inspect_image records them for OME-Zarr images as for TIFF files, and label images opened with open_ngff_labels carry their own. Nothing is guessed: without a level-0 scale, with a scale stored in a separate file (path) or not one number per axis, every size is None; an axis without a unit has the unit None and a non-positive scale is None. A scale without a unit is kept with the unit None, since only the caller can tell a calibration from a placeholder (this brick's own copies write 1 without a unit when the size is unknown). The test helper write_ngff gains scale, units, dataset_transforms and multiscale_transforms; its default output is unchanged. Decision recorded as plan section 12 question 23; contract section 2 and AGENTS.md updated. Information only: the CSV, the manifest and their versions are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017TP44aBCGzyksTpPS2FFmQ
PreprocessingNotebook.folder_step and axis_step had fixed titles, purpose sentences and intro boxes that speak of this brick's histograms; step 1 always showed the card for this brick's output folder, and the "What next?" boxes send the user to this notebook's steps (step 2's to compute the histograms). MorphoRelate milestone M3's "your data" notebook reuses the folder check and the axis review, so it needs its own words. Both steps take an optional title and purpose, a keyword intro (HTML; an empty string leaves the box out) and show_next (False hides the "What next?" box); folder_step also takes show_output (False leaves out the "Output folder (optional)" card, and the check then uses the default output folder, as with the box left empty). The defaults give the pages as before, and this notebook still calls them without arguments; the widgets, checks and gating are unchanged. Tested headless (pages built, driven by clicks); the embedded pages were rendered with embed_minimal_html but not looked at in a browser or a real JupyterLab. Decision recorded as plan section 12 question 24; AGENTS.md updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017TP44aBCGzyksTpPS2FFmQ
The repository has no changelog file: changes are recorded as plan section 12 decisions with the milestone status in plan section 10 and AGENTS.md section 11. Both now list questions 21 to 24 as unreleased work for MorphoRelate milestone M3. No version bump or tag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017TP44aBCGzyksTpPS2FFmQ
Physical sizes are information only, but open_ngff computed them without a guard, and a scale value too large for a float (an integer such as 10**400) raised OverflowError, so preflight reported the image as unreadable_ome_zarr; on main it was fine. Such a value now gives an unknown size for its axis, and any error while reading the sizes is logged and gives empty sizes, as for TIFF files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017TP44aBCGzyksTpPS2FFmQ
PhysicalSizes filled x_unit, y_unit and z_unit from space_unit in
__post_init__. dataclasses.replace then raised (space_unit no longer
matched the filled x_unit) or kept the old unit (space_unit=None), a
pickle written before per-axis units loaded with x_unit None so unit("X")
gave None, and PhysicalSizes(z=1, space_unit="micrometer",
y_unit="nanometer").unit("Z") gave None.
The per-axis units are now stored as given, None meaning space_unit, and
unit(role) resolves them. Equality and hashing compare the sizes,
space_unit and the resolved units, so a size given with space_unit alone
equals the same size spelled out per axis. The readers set space_unit to
the unit of X as before, or None when a space axis with a size has no
unit (it would otherwise fall back to X's unit). The ValueError for
differing space_unit and x_unit is gone: an axis's own unit wins.
Plan section 12 question 22 updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TP44aBCGzyksTpPS2FFmQ
OME-Zarr copies written before each axis had its own unit carry X's unit on Y and Z, and reruns kept them, so one output folder could mix the old and the new units. A converted TIFF whose copy's axis units (read from the copy's .zattrs only) differ from those its header gives now counts as changed and is converted again through the usual staged publication; its rows do not change. A copy whose metadata cannot be read is written again rather than trusted. Copies with matching units are kept as before. Contract section 10, plan section 12 question 22 and the user guide updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017TP44aBCGzyksTpPS2FFmQ
OME-Zarr axis units were kept exactly as written, so "µm", "um" or
"micron" reached callers that expect UDUNITS-2 names (MorphoRelate
rejects them as "not a length unit"). They now map through the same
tables as TIFF units ("micrometer", "second", ...); other units are kept
as written, and spellings of two letters or fewer are matched exactly
("Mm" is not "mm").
The space-unit table gains the angstrom (OME's "Å", the angstrom sign
and "angstrom"). ImageJ writes the micro sign as the text µ in the
TIFF description and tifffile returns it as written, so unit, yunit,
zunit and tunit of "µm" gave no unit; the escapes are now decoded.
Contract section 2 and plan section 12 question 23 updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TP44aBCGzyksTpPS2FFmQ
A multiscales entry whose coordinateTransformations is not a list (a dict, say) was skipped, so the level-0 scale was used as if the image had no multiscale-level transformations. It now gives no sizes, as the docs say for a scale that cannot be used; the same holds at the dataset level. _number turned True into a size of 1: tifffile reads ImageJ's "spacing=true" as True. Booleans are no longer sizes (OME-NGFF scales already rejected them; a test now says so). Also adds a test that the sizes come from level 0 of a multi-level pyramid (it fails if the last dataset is read instead). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017TP44aBCGzyksTpPS2FFmQ
folder_step(show_output=False) left the output folder card out of the page, so a notebook started with an output folder inside the experiment reported output_inside_experiment with no box to fix it. The card is now always on the page and only hidden; the check shows it again when it finds that error, and hides it once the folder is fixed. The default page is unchanged. Also rewords plan section 2, which still called single "the reserved value", and updates plan section 12 question 24 and AGENTS.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017TP44aBCGzyksTpPS2FFmQ
guijacquemet
marked this pull request as ready for review
October 27, 2026 06:27
Bring in follow-ups P2 (dataset consistency warnings) and P3 (experiment design) while keeping this branch's decisions: - physical_sizes.py, with a unit per axis, is the one home of PhysicalSizes. main's pixel_sizes.py is removed; its micrometers() helper for the folder check's pixel-size comparison moves to PhysicalSizes and uses each axis's own unit (angstrom included). - TiffMetadata and NgffImage carry physical_sizes as this branch reads them; main's NgffImage.scale/units are dropped. A unitless OME-Zarr scale is kept with the unit None, so it cannot be compared and is reported as missing, as before. - Step 1 keeps main's consistency panel and this branch's hidden output card, which comes back for output_inside_experiment. - The embedded-step tests answer the experiment-design questions, since embedded steps keep the same gating. - The plan keeps main's questions 21-22 (P2, P3); this branch's questions become 23-26, with every reference renumbered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
I merged
Locally on Windows (Python 3.12, zarr 3.4.1, with the notebook extras), 🤖 Generated with Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
@IvanHCenalmor, could you review this? Please merge it yourself if you're happy with it; we won't merge it. Once it is merged, could you tag a release? MorphoRelate will pin to that tag, and MorphoRelate 1.0 needs one.
Every finding from an independent adversarial review is fixed below. Each fix has a test that fails without it.
Why
MorphoRelate's milestone M3 ("Easy input":
morphorelate init,inspectreading headers, a "your data" notebook, OME-Zarr input) makes this package a core dependency, pinned by commit until it has a tag (owner, 2026-10-26). It reuses this package's discovery, header reading, physical sizes, OME-Zarr reading and notebook steps.Changes
singleis read as that level's valuesingle(the MorphoBricks one-member level, as in<condition>/single/<images>), when it is the only folder in its place. Two cases stayreserved_folder_name:SingleorSINGLE;singlefolder beside other folders.PhysicalSizesandread_physical_sizesmoved here and stay importable fromzarr_exportand the package):x_unit,y_unit,z_unit. None meansspace_unit;unit(role)resolves it.dataclasses.replaceand old pickles stay consistent.PhysicalSizeYUnitandPhysicalSizeZUnitare honoured.yunitandzunitare honoured, including the escaped micro signµ.inspect_image/TiffMetadatagainsphysical_sizes, for TIFF and OME-Zarr images, read from the header that is already open. Existing fields are unchanged.NgffImage.physical_sizesis the NGFF level-0coordinateTransformationsscale times the multiscale-level scale (0.4 and 0.5). Axis units are normalised to UDUNITS-2 names.folder_stepandaxis_steptake optionaltitle,purpose,introandshow_next.folder_steptakesshow_output: the output card is hidden, and comes back when the check reportsoutput_inside_experiment.Behaviour changes for review
PhysicalSizeXUnit="nm"used to report Y (and Z) in nanometres. They now default to micrometres, as the schema says: a silent 1000× change in Y and Z for such files. It is deliberate and documented (plan §12 Q22). Z behaves the same way when only X and Y carry units.singlefolder that is the only folder in its place is no longer an error.PhysicalSizes(space_unit=..., x_unit=...)with differing values no longer raises:x_unitwins.Decisions
Recorded as plan §12 questions 21–24. Contract §1, §2, §8 and §10, the user guide and AGENTS.md are updated. The CSV, the manifest and their versions are unchanged.
Notes for review
Singlebesidesinglegives two errors,reserved_folder_nameandcase_conflict.singleblocks the run.singleis not handled.singlewas reserved by the initial plan, with no recorded reason. The two cases that still give an error keepsingleunambiguous.embed_minimal_html. They were not checked in a browser or JupyterLab.Tests (head
cf4ef21)ruff check .: all checks passed.ruff format --check .: 55 files already formatted.🤖 Generated with Claude Code
https://claude.ai/code/session_017TP44aBCGzyksTpPS2FFmQ