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
6 changes: 5 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,9 @@ src/morphobricks_preprocessing/
notebook_viewers.py which roles each step reads an image with
notebook_scan_axes.py the quick scan's "Axes of this image": roles proposed from the header,
changed right there, the proposal the Axes step starts from
notebook_zarr_copy.py the first screen's "Also save a copy as OME-Zarr" and the Axes step's
card that writes it once the roles are confirmed; ome_zarr_copy.py
the writers (OME-NGFF 0.4, written directly) and the copy's name
notebook_projection.py the Axes step's card for Z-stacks: choose the projection, write the
projected copies into the output folder (convert_experiment), open
them as the experiment; the greyed 3-D option
Expand Down Expand Up @@ -1098,7 +1101,8 @@ jupyter lab notebooks/MorphoBricks_Preprocessing.ipynb
[docs/archive/agents-status-history-v0.1-v0.3.md](docs/archive/agents-status-history-v0.1-v0.3.md).
- **Unreleased:** none yet; later changes go in a new "Unreleased" section of the changelog.
- **Scope:** 2-D per plane (a Z-stack is refused as `z_stack_not_supported`); TIFF and OME-Zarr
input read directly (TIFF to OME-Zarr conversion and the `_moved.py` re-exports were removed);
input read directly (the v0.3 TIFF to OME-Zarr export and the `_moved.py` re-exports were
removed; since v0.5.2 `convert_experiment(store="ome-zarr")` writes recorded OME-Zarr copies);
optional masks, the mask step and the notebook's **Describe the masks**; named-axis copies
(`convert`, notebook card); calibration; resumable, crash-consistent runs.
- **Command line:** `check`, `run`, `masks`, `calibrate`, `describe`, `convert` (`check`,
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,17 @@ experiment-v1 §4.5, §4.5a-§4.5c, §7, §8; ADR 0002 resolver contract version
shown greyed out with "3-D analysis is not enabled yet"; `Capabilities.volume_support` and
`A_VOLUME_NOT_SUPPORTED` are unchanged. Copies written earlier are offered when the stacks
are opened again.
- **Also save a copy as OME-Zarr.** An option on the first screen (unticked; it writes
nothing). Once the axis roles are confirmed, the Axes step offers **Write the OME-Zarr copy
and continue with it**: `convert_experiment(store="ome-zarr")` writes every image that is
read as `<position>.ome.zarr` (OME-NGFF 0.4, written directly: uncompressed, one chunk per
plane, the same bytes for the same source; each `omero` channel has the `color` and
`window` OME-NGFF 0.4 requires, white and the pixel type's whole range) into `<output>/ome_zarr`, with the conversion
record, and the copies are opened as the experiment. With Z-stacks the projected copies are
written as OME-Zarr. The record's entry of such a copy has the copy's path as
`relative_path` and the new key `source_relative_path`; records without OME-Zarr copies are
byte for byte as before (record version 3 is kept). New refusal: `copy_names_collide`.
`plan_conversion` takes `projection`, `masks_folder` and `store` as keywords.
- **Notebook file, cell 3:** on Google Colab it calls
`google.colab.output.no_vertical_scroll()`, so the app is not shown in an output frame that
scrolls on its own. Nothing changes elsewhere.
Expand Down
1 change: 1 addition & 0 deletions docs/contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,7 @@ exclude=, missing_as_singleton=, memory_limit=, progress=)` (`conversion`), the
| Repeat | A copy already in the tree is reused when its source (fingerprint and axes), roles, copy axes, pixel sizes, unknown sizes (roles and reasons), channel names, significant bits, the copy's fingerprint and the copy's SHA-256 (read whole) are as recorded; a new image is added; when every copy is reused the record is kept as it was (same SHA-256). Anything else (a source changed, other roles, a copy changed, an image no longer in the experiment, another experiment's copy tree, a file at a new image's place that the record does not list) is `copy_differs`: nothing is overwritten. A conversion record that cannot be read, or of another version than 3, is `conversion_record_unreadable`. |
| Conversion record | `.morphobricks-conversion.json` in the copy tree's root (a dot-file, so discovery skips it), record version 3: `source_root`, `mapping`, `projection` (null, or `{method, z_range}`: the method and slice range of a tree of Z-projected copies, see "Projected copies" below), `missing_as_singleton`, `written_at`, `versions`, and per image `relative_path`, `action` (`converted` or `copied`), `reason` (the axis code, `excluded`, or null), `source` and `copy` fingerprints (`size`, `mtime_ns`), `copy_sha256` (the copy's SHA-256, computed in one pass after it is written; an OME-Zarr copy's is the SHA-256 of its files' relative paths and SHA-256s in path order, its `labels` group left out as for fingerprints: `content_hash`), `source_axes`, `roles`, `copy_axes`, `pixel_sizes` (by role, as §9), `unknown_sizes` (role: why), `channel_names` (as the copy holds them; `[]` for none), `significant_bits` (or null) and `projection` (null, or the projected copy's provenance, below). Sources keep the fingerprint rule (design v0.4 §1); the copies' SHA-256 is the owner's decision of 2 November 2026 (plan §12 question 59 (16)). Version 2 (PP-11c) added `copy_sha256`, `channel_names` and `significant_bits`; version 3 (issue #176) added the record-level `projection` and the entry's `projection`, and the action `projected`. Only version 3 is read (v0.5.1, SWEEP-PP: no backward compatibility, Spec ADR 0002 AR10): a record of any other version, 1 and 2 included, is refused everywhere it is read (`convert`, the run's `review_conversion`, `RunInputs`), never converted, with a message to convert the originals again into a new folder. `read_conversion_record(folder)` reads it with its SHA-256. |
| Projected copies (issue #176, record version 3) | A tree made with a Z projection holds, for every image, a 2-D copy of its Z-stack made with the record-level `projection` `{method, z_range}` (`max`, `mean` or `sum`; there is no default method; `z_range` is `[start, stop]`, zero-based, stop excluded, the same for every image, or null for every slice). An entry has the action `projected` and a `projection` object with exactly: `source_relative_path`, `source_content_sha256` (the source's content as read while writing), `source_z_axis` (`{letter, index}` of the header's Z axis, or null for an image without one, which is one slice), `source_z_size`, `z_range` (the slices actually used), `method`, `source_dtype`, `output_dtype`, `dtype_rule`, `rounding` (`none`, `float64, one division` or `float64 addition, z ascending`), `nan_policy` (`propagate`), `dropped_sizes` (`{"Z": {value, unit, source}}`, or `{"Z": null}` when the source gave no Z size: the Z size is never written to the copy) and `significant_bits_dropped` (a reason, or null: kept for `max`, dropped for `sum` and `mean`). Excluded images stay plain copies (`copied`, reason `excluded`). The output type depends only on the input type, the method and the slice count (never on the data): `max` keeps the type; `sum` of `uint8`/`uint16` is `uint32`, of `int8`/`int16` is `int32`, of `uint32`/`int32` is `float64` holding the exact integer, of floats is `float64`; `mean` is `float64`; 64-bit integers are refused (`cannot_convert_pixel_type`). A sum or mean whose worst case (the type's extreme times the slices) could not be held exactly is refused (`projection_cannot_be_exact`), never wrapped or clipped. A NaN makes its pixel NaN, infinities are kept. The reader refuses an entry whose output type or rounding is not the table's for its method, a projected entry in a record without a projection (and the reverse), and a method or range that is not the record's. The reuse key holds the projection (everything of it except the content SHA-256, which the write computes), so another method or range into the same tree is `copy_differs`. Masks are never projected: `convert_experiment(..., projection=Projection(...), masks_folder=)` scans the masks folder from the headers only and refuses, before writing anything, masks of the images to project that have depth slices (`projection_masks_3d`) or more than Y and X with axes that are not named (`projection_masks_axes_unknown`); 2-D masks are left alone. The other refusals, all before anything is written: `nothing_to_project` (no image has a Z role), `projection_z_range_outside`, `projection_cannot_be_exact`, `cannot_convert_pixel_type`, `projection_over_memory_limit` (one input plane, the running plane and, when it is another type, the output plane must fit `memory_limit`) and `projection_zarr_not_supported` (OME-Zarr images are not projected yet). The copy's OME-XML holds an `Image/Description` with the method, slices, source, source SHA-256 and types, and a UUID derived from it, so one projection of one source is the same bytes; the source's content SHA-256 is taken before and after the projection and the source fingerprint before and after, and a source that changed is refused. A sum that overflows to infinity (`projection_overflow`) or adds an infinity to its opposite (`projection_undefined`) stops the conversion and is never written. Excluded images are copied byte for byte. |
| OME-Zarr copies (v0.5.2, record version 3) | `convert_experiment(..., store="ome-zarr")` (the notebook's **Also save a copy as OME-Zarr**; default `"ome-tiff"`) writes every image that is read as an OME-Zarr image instead of an OME-TIFF: also the images whose headers name their axes as confirmed, which are otherwise copied byte for byte. Excluded images and OME-Zarr images are copied byte for byte as before. With `projection` the projected copies are OME-Zarr. A copy is named after its image with `.ome.zarr` in place of the TIFF extension, in the same folder, so its position ID is the same; two images that would get one copy name are refused (`copy_names_collide`). In the record the entry's `relative_path` is the copy's path and `source_relative_path` (a key written only for such an entry, so every other record is byte for byte as before) is the source's path relative to `source_root`; `significant_bits` is null (OME-NGFF 0.4 has no field for it). The store is OME-NGFF 0.4 (Zarr version 2), written directly: one array `0`, uncompressed, little-endian, one chunk per plane (the whole Y and X), metadata as sorted JSON, so one copy of one source is the same bytes. Each plane is the plane read under the confirmed roles, bit for bit; the axes are the roles (`t`, `c`, `z`, `y`, `x`) with the units of the copy's sizes, the level-0 `scale` holds the sizes and 1 for an unknown one, which is named under the multiscale's `metadata.unknown_scale` and so read as unknown; channel names are under `omero.channels[].label`, each channel with the `color` (`FFFFFF`) and `window` (the pixel type's whole range as `min`, `max`, `start`, `end`) that OME-NGFF 0.4 requires, display only and never read from the pixels; a projected copy's description is under `morphobricks.description`. Reading the copies needs the `zarr` extra, as for any OME-Zarr image. Plan, lock, `.partial` writes, reuse, the copy's SHA-256 and the checks of the run on the copies are those of the other copies. |
| The run on the copies | The user runs preprocessing on the copy tree with the same output folder (`--output`): the recorded experiment root becomes the copy tree and the report says so (`experiment_root_changed`, and `conversion` with the record, its SHA-256 and `source_root`; the text says "named-axis copies of ..."); every row is recomputed (the row key includes the read path, §9); the design, image type and calibration are kept (same relative paths). The manifest records `conversion_record_sha256`, and for projected copies the `projection` summary (§9); the report says "max projections of slices 1:4 (2 images) of <source>" (and `conversion.projection` in `discovery_report.json`). Masks are paired as usual (the image has no Z now, so 2-D masks fit; a mask with depth slices stays `mask_planes_mismatch` and stops the run), and the run warns `masks_on_projected_images`, naming the method, for masks paired with projections, in the report and the manifest's `projection.warnings`. The run and `check` warn when a source changed or went missing after the conversion (`conversion_source_changed`) and refuse a copy that changed (its fingerprint or its SHA-256: each copy is read whole, so `check` and `run` on a copy tree cost one more read of the copies), went missing or was added since (`copy_changed_after_conversion`: an error since PP-12, owner decision of 2 November 2026; the run stops after the header checks, before any pixel is read, writing only its report, since `RunInputs` would refuse the run; the message says to convert the originals again into a new folder); an unreadable record stops the run (`conversion_record_unreadable`). `check` and `run` also read the OME `Image/Description` header (first page's description tag only, no pixels) of every analysed TIFF the record does not list as `projected`: a description that begins "MorphoBricks Z projection:" in a tree with no record, or whose record does not list the image as projected, is refused as `projection_record_missing` (owner decision of 8 November 2026; plain images are unaffected). `RunInputs` reads the copies like any images, and refuses a run whose conversion record changed or went missing since (`conversion_record_sha256`), is of version 1, or whose recorded images' copies no longer have the recorded fingerprint and SHA-256 (§9.1). |
| Notebook (PP-11b) | Step 2's **Make named-axis copies (optional)** card, under the axis check, shown only when it reports images with `axes_yx_swapped`, `axes_contradict_header`, `axes_x_before_y` or `axes_not_named` for the roles being edited (design v0.4 §5: offered only for files with errors), and not with `folder_step(show_output=False)`. The roles are the user's: those confirmed in step 2, or the roles shown once the preview is checked and the card's own question is answered; any change of a role or of the missing-axes answer takes it back. A required folder for the copies (Browse…, or pasted) checked with the command's rule (`copy_folder_issues`); `preview_conversion` (`conversion_plan`, headers only, writes nothing) gives what will be converted and copied byte for byte (the command's `plan_conversion`), the space needed and the free space; then `convert_experiment` with a progress bar, under the copy tree's lock. Refusals use the existing cards (the lock card with **Remove the lock of a stopped run**, the rule of `--remove-stale-lock`). After the copies: the record's path, and **Check the copies in step 1**, which sets the experiment folder to the copy tree, keeps the output folder and checks again; the card says that the missing-axes answer must be given again and lists the copies whose relabelled sizes are unknown (`ConversionResult.unknown_sizes`), to state under **Pixel sizes**. |

Expand Down
12 changes: 12 additions & 0 deletions docs/user_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,17 @@ lists each one (folder, path, fingerprint, SHA-256, slices used). **Keep the 3-D
When you open the stacks again with the same output folder, the copies written earlier are
offered (**Continue with the copies written earlier**).

**A copy as OME-Zarr.** The Folders card of step 1 has **Also save a copy as OME-Zarr (in the
output folder)**. Ticking it writes nothing. Once the axis roles are confirmed, the Axes step
shows **OME-Zarr copy** with **Write the OME-Zarr copy and continue with it**: every image is
written plane for plane, with the confirmed roles, to `<output>/ome_zarr` (one
`<image name>.ome.zarr` per image, OME-NGFF 0.4, uncompressed), with a conversion record, and
the copies are opened as the experiment (scanned and checked again; you confirm their axes and
go on). Your images are only read. With Z-stacks the projected copies are written as OME-Zarr
instead (`<output>/projected_max_zarr`). Pixel sizes you stated for the whole experiment or a
folder keep applying; a size stated for a single image names the image file, so state it again
for the copy if needed (the check says so: `calibration_target_not_found`).

Folders may have different numbers of channels (for example three stains everywhere and a
fourth only in `Biological_repeat_2`): on **Channels** you give each label once and tag each
folder's channels; the same label means the same stain in every folder. A stain a folder does
Expand Down Expand Up @@ -983,6 +994,7 @@ continues.
| `copy_folder_overlaps_experiment` | Error | `convert`: the folder for the copies is the experiment folder, inside it, or holds it. Choose a new folder outside the experiment. |
| `copy_folder_overlaps_masks` | Error | `convert`: the folder for the copies is the masks folder, inside it, or holds it; copies there would be read as masks. Choose a folder outside the masks folder. |
| `copy_folder_not_a_folder` | Error | `convert`: the path given with `--to` is a file. Choose a folder. |
| `copy_names_collide` | Error | An OME-Zarr copy: two images of one folder differ only by their extension, so their copies would have the same name. Nothing is written. Rename or exclude one of them. |
| `copy_folder_not_empty` | Error | `convert`: the folder for the copies holds files and no conversion record. Nothing is overwritten; choose a new or empty folder. |
| `copy_differs` | Error | `convert`: images in the folder of copies differ from the copies this conversion would write (an original changed, other roles, a copy changed, an image no longer in the experiment), or the folder holds copies of another experiment. Nothing is overwritten; convert into a new folder, or remove those copies first. |
| `cannot_convert_pixel_type` | Error | `convert`: images of 64-bit integers cannot be saved as OME-TIFF, and pixel values are never changed to fit. Save them again with named axes in a 32-bit type if their values fit, or correct the roles. |
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ baseline = "tools/code_rules_baseline.json"
"morphobricks_preprocessing.notebook_quick_scan" = "ui"
"morphobricks_preprocessing.notebook_viewer" = "ui"
"morphobricks_preprocessing.notebook_projection" = "ui"
"morphobricks_preprocessing.notebook_zarr_copy" = "ui"
"morphobricks_preprocessing.notebook_scan_axes" = "ui"
"morphobricks_preprocessing.notebook_viewers" = "ui"
"morphobricks_preprocessing.image_view" = "io"
Expand Down Expand Up @@ -224,6 +225,7 @@ baseline = "tools/code_rules_baseline.json"
"morphobricks_preprocessing.projection_plan" = "orchestration"
"morphobricks_preprocessing.planned_copy" = "pure"
"morphobricks_preprocessing.ome_tiff_copy" = "io"
"morphobricks_preprocessing.ome_zarr_copy" = "io"
"morphobricks_preprocessing.discovery_report" = "pure"
"morphobricks_preprocessing.dataset" = "pure"
"morphobricks_preprocessing.design" = "io"
Expand Down
Loading
Loading