diff --git a/docs/contract.md b/docs/contract.md index 273fb17c..90f2087f 100644 --- a/docs/contract.md +++ b/docs/contract.md @@ -166,7 +166,7 @@ file's schema or statistical interpretation. | `channels_from_rgb_samples` | `true` when the C role is the RGB samples axis of images stored as RGB colour (§13), `false` otherwise; absent in manifests written before it. Optional key. | | `intensity_checks` | The limits of the intensity checks run on the table (§14): `IntensityCheckSettings.to_dict()` of the normalization brick (`library_version`, `outlier_limit`, `saturation_limit`, `min_images`); absent in manifests written before it. Optional key. Changing the limits recomputes nothing. | | `narrow_histogram_ranges` | The channel planes whose range check found the central range too narrow (§4), sorted by plane and channel: `relative_path`, `plane_id`, `channel` and the fields of the normalization brick's `RangeCheck` (`histogram_range`, `central_percentiles`, `central_bins`, `robust_quantiles`, `robust_limits`, `outside_robust_count`, `robust_central_bins`, `min_central_bins`). Empty when there are none; absent in manifests written before follow-up P6. Optional key: the manifest stays version 1. | -| Flags | `missing_as_singleton`, `convert_tiffs`, `read_in_blocks` and `channels_from_rgb_samples` are JSON booleans (`true`/`false`); any other value (`"false"`, `0`) makes the manifest malformed, never read as true or false, and `write_manifest` refuses to write one (`ValueError`), so a written manifest always reads back. | +| Flags | `missing_as_singleton`, `convert_tiffs`, `read_in_blocks`, `channel_names_trimmed` and `channels_from_rgb_samples` are JSON booleans (`true`/`false`); any other value (`"false"`, `0`) makes the manifest malformed, never read as true or false, and `write_manifest` refuses to write one (`ValueError`), so a written manifest always reads back. | | `versions` | Versions of `morphobricks-preprocessing`, `morphobricks-normalization` (which computes the histograms and writes the CSV; absent in manifests written before it), `numpy`, `tifffile` and Python. Information only: a different version never causes a rebuild. | Channel names and significant bits come from OME metadata. Significant bits are for diff --git a/src/morphobricks_preprocessing/manifest.py b/src/morphobricks_preprocessing/manifest.py index 00a3f8f6..96a41cca 100644 --- a/src/morphobricks_preprocessing/manifest.py +++ b/src/morphobricks_preprocessing/manifest.py @@ -544,7 +544,7 @@ def _file_dict(entry: FileEntry) -> dict: "ome_zarr_path": entry.ome_zarr_path, "read_in_blocks": _flag(entry.read_in_blocks, "read_in_blocks"), "position_id": entry.position_id, - "channel_names_trimmed": entry.channel_names_trimmed, + "channel_names_trimmed": _flag(entry.channel_names_trimmed, "channel_names_trimmed"), "roles": None if entry.roles is None else list(entry.roles), } @@ -568,7 +568,7 @@ def _file_entry(data: dict) -> FileEntry: ome_zarr_path=None if data.get("ome_zarr_path") is None else str(data["ome_zarr_path"]), read_in_blocks=_flag(data.get("read_in_blocks", False)), position_id=None if data.get("position_id") is None else str(data["position_id"]), - channel_names_trimmed=bool(data.get("channel_names_trimmed", False)), + channel_names_trimmed=_flag(data.get("channel_names_trimmed", False)), roles=_roles(data.get("roles"), str(data["axes"])), ) diff --git a/tests/test_manifest.py b/tests/test_manifest.py index 2445a950..7decf4b9 100644 --- a/tests/test_manifest.py +++ b/tests/test_manifest.py @@ -258,6 +258,7 @@ def test_bad_role_in_manifest_is_refused(): ("settings", "missing_as_singleton"), ("settings", "convert_tiffs"), ("file", "read_in_blocks"), + ("file", "channel_names_trimmed"), ("top", "channels_from_rgb_samples"), ], ) @@ -271,15 +272,22 @@ def test_flags_must_be_json_booleans(where, key, value): def _with_flag(key, value): - if key == "read_in_blocks": - return manifest(files=(entry(read_in_blocks=value),)) + if key in ("read_in_blocks", "channel_names_trimmed"): + return manifest(files=(entry(**{key: value}),)) if key == "channels_from_rgb_samples": return manifest(channels_from_rgb_samples=value) return manifest(settings=replace(RunSettings(65_536, 2**30, False, ()), **{key: value})) @pytest.mark.parametrize( - "key", ["missing_as_singleton", "convert_tiffs", "read_in_blocks", "channels_from_rgb_samples"] + "key", + [ + "missing_as_singleton", + "convert_tiffs", + "read_in_blocks", + "channel_names_trimmed", + "channels_from_rgb_samples", + ], ) @pytest.mark.parametrize("value", ["false", 0, 1, np.bool_(True)]) def test_the_writer_refuses_flags_that_are_not_booleans(tmp_path, key, value):