Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ src/morphobricks_preprocessing/
validation.py ValidationIssue (severity, code, message, paths, action), error()/warning() builders
axes.py AxisMapping (role per dimension), prefill, check_mapping, review_axes (one mapping
applied to every file, missing axes as singletons only when confirmed),
check_declared_axes (a brick's declared axes against the header, contract §16)
check_declared_axes and declared_axes_transposed (deprecated; removed
before the v0.4.0 release)
axis_check.py the axis check (contract §2): header_axis_issue, axis_disagreements,
likely_axis_cause, check_header_axes; AXIS_ERROR_CODES, AXIS_CHECK_CODES
planes.py plane-by-plane reader (iter_planes, read_plane, read_sample_plane), table rows as
XYC planes whose channels are read one at a time (iter_xyc_planes, XycPlane),
memory estimate,
Expand All @@ -107,6 +110,8 @@ src/morphobricks_preprocessing/
experiment's folders, mixed; OME-Zarr label images), header-only
checks (2D, image YX, integer/bool), MaskInventory (to_manifest), read_mask,
read_mask_file (a mask by its path), check_labels (int32 labels, contract §16)
mask_checks.py mask checks with the confirmed roles: mask_size_issues (Y/X by role),
mask_issues (a run's mask issues; images with axis errors skipped)
mask_scan.py the optional mask step (Q32): piece_counts (scipy.ndimage), pair_counts,
scan_masks -> MaskReport (kinds, relations, direct-parent hierarchy, evidence),
write_mask_report (mask_relationships.json); DEFAULT_THRESHOLDS,
Expand All @@ -119,6 +124,7 @@ src/morphobricks_preprocessing/
block by block),
ConversionSettings (chunk size, compression, pyramid levels), estimate_conversion
notebook_style.py notebook look and feel: cards, status cards, tiles, tables, ConfirmToggle, Browse dialogs
notebook_axes.py the notebook's axis-check card and the marks under the role dropdowns
notebook_ui.py PreprocessingNotebook: the four notebook steps (needs the `notebook` extra);
folder_step/axis_step take title, purpose, intro and show_next (and
show_output and show_design for step 1) so other bricks can embed them
Expand Down Expand Up @@ -225,6 +231,17 @@ functions the tests use.
- X/Y/Z/C/T keep their role;
- `S` (RGB samples) becomes C when there is no other C;
- everything else (`I`, `Q`, …) is left unassigned: **never guess** depth vs time.
- **Axis check (v0.4.0, plan §12 question 45):** every TIFF's confirmed roles must agree
with the axes its header names (`axes.header_axis_issue`, position by position, on
`metadata.named_axes`). Otherwise one of three errors naming the files with their header
and confirmed axes: the errors `axes_yx_swapped` and `axes_contradict_header` (with the
likely cause; such an image is **never read**, not transposed, not under a contradicted
declaration; fix: correct the axes, or named-axis copies) and the warning
`axes_not_named` (a plain stack, read with the confirmed roles, `axes_from:
"confirmed"`).
`AxisReview.axis_error_files` lists them; mask sizes and planes are checked by the
confirmed roles and skip those images (`run.mask_issues`). OME-Zarr images never have
these errors.
- **Position-like axes** (`R` tile, `M` mosaic, `J` column, `K` row) cannot get a role. If
they hold more than one plane: `unsupported_axis` error.
- **Missing axes:** a file lacking some of the mapping's axes (e.g. `ZYX` next to `ZCYX`) is
Expand Down
11 changes: 6 additions & 5 deletions docs/contract.md

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions docs/plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -1035,3 +1035,27 @@ Raised during review; to be decided before the milestones that need them:
1D array keeps the header axes `X` (`header_axes`, `check_declared_axes`), so the folder
check suggests a mapping without Y and the run refuses it with `missing_xy_axis`;
reading it as one row too would change the shared header-axes rule (§16).*
45. How are the axes checked, now that every brick reads preprocessing's run (MorphoBricks
axes redesign, design note and audit of 1 November 2026)? Until now a confirmed
mapping was never compared with the axes the header names: a Y/X swap was read
transposed and a contradiction (header `ZYX` confirmed `TYX`) read under the
declaration, with no trace but the manifest's `roles`.
*Decided (1 November 2026, owner's final design after the audit): preprocessing is the
only place axes are checked. `review_axes` compares each TIFF's confirmed roles with
its header (`header_axis_issue`, built from the declared-axes rule). Roles that swap
the named Y and X (`axes_yx_swapped`) or otherwise contradict them
(`axes_contradict_header`) are errors naming the files with their header and
confirmed axes and the likely cause (tifffile's `CYX` default, an ImageJ slice stack),
with two fixes: correct the axes, or convert (named-axis copies). Nothing is read
transposed or under a contradicted declaration. A plain stack whose metadata does not
name its axes is read with the roles the user confirms, with the warning
`axes_not_named` recommending named copies, recorded per file as `axes_from:
"confirmed"`. The notebook shows the three codes as one card and marks each role that
disagrees with the header before confirmation; when the axes are correct, the notebook
and the CLI say so. Masks are compared with their image's Y and X by the confirmed
roles (they were compared with the header's letters; the role-based comparison still
matters for plain stacks, whose positional Y and X need not be the roles), and not at
all for an image with an axis error. `check_declared_axes` and
`declared_axes_transposed` are deprecated, to be removed before the v0.4.0 release
(design v0.4 §6, PP-2). Breaking for v0.4.0: experiments run with roles that swap or
contradict the header no longer run as they are.*
27 changes: 25 additions & 2 deletions docs/user_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ that measure objects; the optional `masks` command (§5) also tells how they rel
## 3. Supported images

- **TIFF files:** `.tif` and `.tiff` (any case), including OME-TIFF and ImageJ hyperstacks.
The axis roles must agree with the axes their metadata names (§4 step 2); a file whose
axes you know better than its header is saved as a named-axis copy first. A plain stack
of pages without axis metadata is read with the roles you confirm (a warning).
- **OME-Zarr images:** OME-NGFF 0.4 and 0.5 (needs the `zarr` extra; 0.5 needs Python 3.11
or newer). Their axes (time, channel, z, y, x) come from their metadata. Only the
full-resolution level of a single image is read; a folder holding several images (a
Expand Down Expand Up @@ -238,8 +241,18 @@ results or comparing another grouping.
channels, a message says so and step 3 stays locked. Each picture should be a normal image of your sample;
stripes or a smear mean X or Y is on the wrong dimension. Brightness is stretched for
display only.
- The roles are checked against the axes each image's metadata names, in one **Axis
check** card. A role that disagrees with the header is marked in amber under its
dropdown; the card gives the error (`axes_yx_swapped` or `axes_contradict_header`,
§8) naming the images with their header axes, your roles and the likely cause. Such
images are never read: correct the axes, or save named-axis copies with your roles and
run preprocessing on the copies. An axis the metadata does not name (a plain stack of
pages) takes the role you confirm: the card warns (`axes_not_named`) and the run
records the roles as confirmed by you.
- If the preview looks right, press **Yes, the preview looks right**, then click
**Confirm axis roles**. Changing a role afterwards asks you to check the preview again.
**Confirm axis roles**. When the roles agree with every header, a green card says
**Axes checked: they are correct for every image**. Changing a role afterwards asks
you to check the preview again.
The confirmed roles are locked until you click **Change axis roles**.
- If some images lack a dimension the others have (often a single channel), a yellow box
lists them; press **Yes, they have a single plane there** if that is the case.
Expand Down Expand Up @@ -375,6 +388,13 @@ morphobricks-preprocessing run /data/exp1 --axes ZCYX
`T`, or `-` for a length-1 dimension without a role. Roles are never taken from metadata
without being stated.

Both commands check the axes (§4, step 2): `check` with the suggested roles, which are the
headers' own, so only the `axes_not_named` warning of plain stacks can appear there; `run`
with the roles given in `--axes`. They print **Axes checked: every image names its axes in
its metadata, and the roles agree with them** when all is well (and name the plain stacks
whose roles are recorded as confirmed), or the errors naming the images, the likely cause
and the two fixes, and `run` stops (exit status 1).

`--image-type fluorescence` or `--image-type rgb` gives the image type (§4, step 2).
Without it, `run` uses the type suggested from the image headers and prints it, with why it
was suggested and how to choose the other one; `check` prints the suggestion too. `rgb`
Expand Down Expand Up @@ -555,7 +575,10 @@ continues.
| `inconsistent_dimensions`, `inconsistent_mapping` | Error | Images have their dimensions in a different order. Export them the same way or process separately. |
| `missing_axes` | Error | Some images lack a dimension. Confirm they have one plane along it (step 2), or export them again. |
| `ambiguous_missing_axes` | Error | It is unclear which dimension an image lacks. Export it again. |
| `declared_axes_mismatch` | Error (other bricks) | The axes a brick was told for an image or mask contradict the axes in its metadata (for example a depth stack declared as channels). Declare the axes the metadata gives, or export the image again with the right axes. |
| `axes_not_named` | Warning | The metadata of some images does not name their axes (a plain stack of pages, `IYX` or `QYX`), so which dimension is depth, time or channel comes only from the roles you confirm. The images are read with them, and the roles are recorded as confirmed by you. Check the roles with the preview. Before segmenting these images in other tools, consider saving named-axis copies (OME-TIFF), so every tool reads the same axes. |
| `axes_yx_swapped` | Error | The roles you confirmed give the Y and X the image header names the other way round, so the images are not read (never transposed). The message names the likely cause. Two fixes: correct the axes (if the header is right, choose its roles), or convert (save named-axis copies with your roles, then run preprocessing on the copies). |
| `axes_contradict_header` | Error | The roles you confirmed contradict the axes the image header names (for example the header says channels, C, and you chose time, T). The images are not read under a contradicted declaration. The message names the likely cause, often the tool that saved the files: tifffile labels a 3-D stack saved with `imagej=True` or `ome=True` as `CYX`, and an ImageJ stack of slices reads as depth even for a time-lapse. The same two fixes as `axes_yx_swapped`. |
| `declared_axes_mismatch` | Error (other bricks; deprecated, removed in v0.4.0) | The axes a brick was told for an image or mask contradict the axes in its metadata (for example a depth stack declared as channels). Declare the axes the metadata gives, or export the image again with the right axes. |
| `missing_xy_axis`, `duplicate_axis_role` | Error | X/Y missing or a role used twice. Correct the axis roles. |
| `unassigned_axis` | Error | A dimension with several planes has no role. Choose Z, C or T. |
| `unsupported_axis` | Error | A position or tile dimension. Export each position separately. |
Expand Down
11 changes: 10 additions & 1 deletion notebooks/MorphoBricks_Preprocessing.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -292,11 +292,14 @@
"Run the cell below, then:\n",
"\n",
"1. Check the suggested role in each box. The line under each box says what the choice means.\n",
" An amber line marks a role that disagrees with the axes the image header names: such images\n",
" are not read. An axis the metadata does not name (a plain stack) takes the role you confirm.\n",
"2. Click **Show preview** and move the sliders. Each picture should look like a normal image of your\n",
" sample. Stripes or a smeared picture mean X or Y is on the wrong dimension.\n",
"3. If the preview looks right, press **Yes, the preview looks right** in the yellow box under the\n",
" picture. Changing a role afterwards asks you to check the preview again.\n",
"4. Press **Confirm axis roles**. The roles are locked until you press **Change axis roles**.\n",
"4. Press **Confirm axis roles**. A green card says **Axes checked** when the roles agree with every\n",
" image header. The roles are locked until you press **Change axis roles**.\n",
"5. Under **Image type**, check the suggestion: **Fluorescence** (each channel is a separate stain\n",
" or marker) or **RGB** (colour images, such as brightfield or histology stains). RGB is suggested\n",
" when the images are stored as RGB colour. If the suggestion is right, press\n",
Expand Down Expand Up @@ -467,6 +470,12 @@
"\n",
"**\"No role is chosen for the axis\"**: a dimension with several planes needs Z, C or T in step 2.\n",
"\n",
"**\"… swap the Y and X their header names\"** or **\"… contradict their header\"**: the images\n",
"are not read; the message names the likely cause. Correct the axes in step 2 (choose the\n",
"header's roles if it is right), or save named-axis copies with your roles and run preprocessing on\n",
"the copies. **\"… do not name their axes in their metadata\"** is a warning: check the roles with\n",
"the preview; they are recorded as confirmed by you.\n",
"\n",
"**\"The images have different numbers of channels\"**: each row of the table holds every channel of\n",
"a plane, so all images need the same channels. Process the others as a separate experiment, or\n",
"exclude them; if a dimension got the role C by mistake, correct it in step 2.\n",
Expand Down
15 changes: 15 additions & 0 deletions src/morphobricks_preprocessing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@
reference_axes,
review_axes,
)
from .axis_check import (
AXIS_CHECK_CODES,
AXIS_ERROR_CODES,
axis_disagreements,
header_axis_issue,
likely_axis_cause,
)
from .dataset import (
CONSISTENCY_CODES,
BalanceCell,
Expand Down Expand Up @@ -102,6 +109,7 @@
source_fingerprint,
write_manifest,
)
from .mask_checks import mask_issues, mask_size_issues
from .mask_scan import (
DEFAULT_THRESHOLDS,
MASK_REPORT_FILENAME,
Expand Down Expand Up @@ -208,6 +216,8 @@ def __dir__() -> list[str]:
__version__ = "0.0.0+unknown"

__all__ = [
"AXIS_CHECK_CODES",
"AXIS_ERROR_CODES",
"CONSISTENCY_CODES",
"DEFAULT_MEMORY_LIMIT",
"DEFAULT_THRESHOLDS",
Expand Down Expand Up @@ -289,6 +299,7 @@ def __dir__() -> list[str]:
"ValidationIssue",
"XycPlane",
"ZarrUnavailableError",
"axis_disagreements",
"axis_length_ranges",
"balance_table",
"biological_repeats",
Expand Down Expand Up @@ -324,6 +335,7 @@ def __dir__() -> list[str]:
"group_summaries",
"has_errors",
"header_axes",
"header_axis_issue",
"image_type_issues",
"inspect_experiment",
"inspect_image",
Expand All @@ -333,11 +345,14 @@ def __dir__() -> list[str]:
"is_zarr_store",
"iter_planes",
"iter_xyc_planes",
"likely_axis_cause",
"load_design",
"make_plane_id",
"images_named_as_masks",
"mask_issues",
"mask_name_issues",
"mask_plane_issues",
"mask_size_issues",
"mask_thresholds",
"min_max",
"named_axes",
Expand Down
Loading
Loading