patchworks 1.3.4__tar.gz → 1.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. {patchworks-1.3.4 → patchworks-1.4.0}/PKG-INFO +1 -1
  2. {patchworks-1.3.4 → patchworks-1.4.0}/docs/examples/dog.md +3 -0
  3. {patchworks-1.3.4 → patchworks-1.4.0}/src/patchworks/plugins/ome_zarr.py +149 -8
  4. {patchworks-1.3.4 → patchworks-1.4.0}/tests/test_ome_zarr.py +29 -0
  5. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/config/config.yaml +7 -1
  6. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/scripts/convert.py +1 -0
  7. {patchworks-1.3.4 → patchworks-1.4.0}/.github/workflows/docs.yml +0 -0
  8. {patchworks-1.3.4 → patchworks-1.4.0}/.github/workflows/lint.yml +0 -0
  9. {patchworks-1.3.4 → patchworks-1.4.0}/.github/workflows/release.yml +0 -0
  10. {patchworks-1.3.4 → patchworks-1.4.0}/.gitignore +0 -0
  11. {patchworks-1.3.4 → patchworks-1.4.0}/.markdownlint-cli2.yaml +0 -0
  12. {patchworks-1.3.4 → patchworks-1.4.0}/LICENSE +0 -0
  13. {patchworks-1.3.4 → patchworks-1.4.0}/README.md +0 -0
  14. {patchworks-1.3.4 → patchworks-1.4.0}/cliff.toml +0 -0
  15. {patchworks-1.3.4 → patchworks-1.4.0}/docs/api/chunks.md +0 -0
  16. {patchworks-1.3.4 → patchworks-1.4.0}/docs/api/cluster.md +0 -0
  17. {patchworks-1.3.4 → patchworks-1.4.0}/docs/api/io.md +0 -0
  18. {patchworks-1.3.4 → patchworks-1.4.0}/docs/api/merge_tile_labels.md +0 -0
  19. {patchworks-1.3.4 → patchworks-1.4.0}/docs/api/plugins/cellpose.md +0 -0
  20. {patchworks-1.3.4 → patchworks-1.4.0}/docs/api/plugins/dog.md +0 -0
  21. {patchworks-1.3.4 → patchworks-1.4.0}/docs/api/plugins/napari.md +0 -0
  22. {patchworks-1.3.4 → patchworks-1.4.0}/docs/api/plugins/ome_zarr.md +0 -0
  23. {patchworks-1.3.4 → patchworks-1.4.0}/docs/api/postprocess.md +0 -0
  24. {patchworks-1.3.4 → patchworks-1.4.0}/docs/api/relabel.md +0 -0
  25. {patchworks-1.3.4 → patchworks-1.4.0}/docs/api/tile_process.md +0 -0
  26. {patchworks-1.3.4 → patchworks-1.4.0}/docs/assets/logo.png +0 -0
  27. {patchworks-1.3.4 → patchworks-1.4.0}/docs/examples/cellpose_2d.md +0 -0
  28. {patchworks-1.3.4 → patchworks-1.4.0}/docs/examples/cellpose_2d.py +0 -0
  29. {patchworks-1.3.4 → patchworks-1.4.0}/docs/examples/cellpose_3d.md +0 -0
  30. {patchworks-1.3.4 → patchworks-1.4.0}/docs/examples/cellpose_3d.py +0 -0
  31. {patchworks-1.3.4 → patchworks-1.4.0}/docs/examples/custom.md +0 -0
  32. {patchworks-1.3.4 → patchworks-1.4.0}/docs/examples/custom_method.py +0 -0
  33. {patchworks-1.3.4 → patchworks-1.4.0}/docs/examples/dog.py +0 -0
  34. {patchworks-1.3.4 → patchworks-1.4.0}/docs/examples/standalone_merge.md +0 -0
  35. {patchworks-1.3.4 → patchworks-1.4.0}/docs/examples/stardist.md +0 -0
  36. {patchworks-1.3.4 → patchworks-1.4.0}/docs/examples/stardist_2d.py +0 -0
  37. {patchworks-1.3.4 → patchworks-1.4.0}/docs/getting_started.md +0 -0
  38. {patchworks-1.3.4 → patchworks-1.4.0}/docs/guide/custom_segmentation.md +0 -0
  39. {patchworks-1.3.4 → patchworks-1.4.0}/docs/guide/gpu_distributed.md +0 -0
  40. {patchworks-1.3.4 → patchworks-1.4.0}/docs/guide/label_relations.md +0 -0
  41. {patchworks-1.3.4 → patchworks-1.4.0}/docs/guide/measurements.md +0 -0
  42. {patchworks-1.3.4 → patchworks-1.4.0}/docs/guide/merging.md +0 -0
  43. {patchworks-1.3.4 → patchworks-1.4.0}/docs/guide/ome_zarr_napari.md +0 -0
  44. {patchworks-1.3.4 → patchworks-1.4.0}/docs/guide/performance.md +0 -0
  45. {patchworks-1.3.4 → patchworks-1.4.0}/docs/guide/pitfalls.md +0 -0
  46. {patchworks-1.3.4 → patchworks-1.4.0}/docs/guide/skip_empty.md +0 -0
  47. {patchworks-1.3.4 → patchworks-1.4.0}/docs/guide/snakemake.md +0 -0
  48. {patchworks-1.3.4 → patchworks-1.4.0}/docs/guide/tiling.md +0 -0
  49. {patchworks-1.3.4 → patchworks-1.4.0}/docs/index.md +0 -0
  50. {patchworks-1.3.4 → patchworks-1.4.0}/mkdocs.yml +0 -0
  51. {patchworks-1.3.4 → patchworks-1.4.0}/pyproject.toml +0 -0
  52. {patchworks-1.3.4 → patchworks-1.4.0}/src/patchworks/__init__.py +0 -0
  53. {patchworks-1.3.4 → patchworks-1.4.0}/src/patchworks/_chunks.py +0 -0
  54. {patchworks-1.3.4 → patchworks-1.4.0}/src/patchworks/_cluster.py +0 -0
  55. {patchworks-1.3.4 → patchworks-1.4.0}/src/patchworks/_core.py +0 -0
  56. {patchworks-1.3.4 → patchworks-1.4.0}/src/patchworks/_distributed.py +0 -0
  57. {patchworks-1.3.4 → patchworks-1.4.0}/src/patchworks/_io.py +0 -0
  58. {patchworks-1.3.4 → patchworks-1.4.0}/src/patchworks/_merge.py +0 -0
  59. {patchworks-1.3.4 → patchworks-1.4.0}/src/patchworks/_postprocess.py +0 -0
  60. {patchworks-1.3.4 → patchworks-1.4.0}/src/patchworks/_relabel.py +0 -0
  61. {patchworks-1.3.4 → patchworks-1.4.0}/src/patchworks/_relations.py +0 -0
  62. {patchworks-1.3.4 → patchworks-1.4.0}/src/patchworks/plugins/__init__.py +0 -0
  63. {patchworks-1.3.4 → patchworks-1.4.0}/src/patchworks/plugins/cellpose.py +0 -0
  64. {patchworks-1.3.4 → patchworks-1.4.0}/src/patchworks/plugins/dog.py +0 -0
  65. {patchworks-1.3.4 → patchworks-1.4.0}/src/patchworks/plugins/napari.py +0 -0
  66. {patchworks-1.3.4 → patchworks-1.4.0}/tests/test_core.py +0 -0
  67. {patchworks-1.3.4 → patchworks-1.4.0}/tests/test_distributed.py +0 -0
  68. {patchworks-1.3.4 → patchworks-1.4.0}/tests/test_dog.py +0 -0
  69. {patchworks-1.3.4 → patchworks-1.4.0}/tests/test_napari.py +0 -0
  70. {patchworks-1.3.4 → patchworks-1.4.0}/tests/test_postprocess.py +0 -0
  71. {patchworks-1.3.4 → patchworks-1.4.0}/tests/test_relations.py +0 -0
  72. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/README.md +0 -0
  73. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/Snakefile +0 -0
  74. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/config/config_cilia.yaml +0 -0
  75. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/config/config_cyto.yaml +0 -0
  76. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/config/config_nuclei.yaml +0 -0
  77. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/config/multi.yaml +0 -0
  78. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/pixi.toml +0 -0
  79. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/profile/slurm/config.yaml +0 -0
  80. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/rules/common.smk +0 -0
  81. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/rules/convert.smk +0 -0
  82. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/rules/merge.smk +0 -0
  83. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/rules/segment.smk +0 -0
  84. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/scripts/_pw.py +0 -0
  85. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/scripts/fetch_model.py +0 -0
  86. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/scripts/merge.py +0 -0
  87. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/scripts/prepare_tiles.py +0 -0
  88. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/scripts/run_multi.py +0 -0
  89. {patchworks-1.3.4 → patchworks-1.4.0}/workflow/scripts/segment_tile.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patchworks
3
- Version: 1.3.4
3
+ Version: 1.4.0
4
4
  Summary: Tiled processing of arbitrarily large images with globally consistent labels
5
5
  Project-URL: Homepage, https://github.com/imcf/patchworks
6
6
  Project-URL: Issues, https://github.com/imcf/patchworks/issues
@@ -6,6 +6,9 @@ label the connected components. CPU (scipy) by default, GPU (cupy) optional.
6
6
  Optionally deconvolve each tile first with
7
7
  [pycudadecon](https://github.com/tlambert03/pycudadecon).
8
8
 
9
+ > Cilia DoG + deconvolution approach courtesy of
10
+ > [angelo-angonezi](https://github.com/angelo-angonezi).
11
+
9
12
  ## Installation
10
13
 
11
14
  `dog_label_fn` itself only needs patchworks' core deps (scipy). The
@@ -9,14 +9,18 @@ Input handling, in order:
9
9
  * a ``.zarr`` path → read through :func:`patchworks.load_ome_zarr`;
10
10
  * a ``.ims`` (Imaris) path → read lazily with ``imaris-ims-file-reader``
11
11
  (HDF5, no JVM); install with ``pip install "patchworks[imaris]"``;
12
+ * with ``sequence_pattern=`` set, a glob over a folder of single-plane TIFFs
13
+ → each file becomes one chunk of a lazy dask array via
14
+ :func:`tifffile.TiffSequence`, no data duplicated;
12
15
  * any other path (CZI, LIF, ND2, OME-TIFF, …) → opened lazily with
13
16
  `bioio <https://github.com/bioio-devs/bioio>`_.
14
17
 
15
18
  Pixel calibration (physical voxel size) is read from the input — bioio's
16
- ``physical_pixel_sizes``, the Imaris resolution metadata, or an existing
17
- OME-ZARR's scale transform and written into the NGFF ``coordinate
18
- Transformations`` so the µm/pixel sizing is preserved. Pass ``pixel_size=`` to
19
- override or to supply it for bare arrays.
19
+ ``physical_pixel_sizes``, the Imaris resolution metadata, an existing
20
+ OME-ZARR's scale transform, or (for a TIFF sequence) the first file's own
21
+ ImageJ/resolution tags and written into the NGFF ``coordinateTransformations``
22
+ so the µm/pixel sizing is preserved. Pass ``pixel_size=`` to override or to
23
+ supply it for bare arrays.
20
24
 
21
25
  Downsampling uses strided (nearest-neighbour) subsampling — the correct,
22
26
  label-preserving choice — and only on **X and Y**; ``z`` (and channel/time)
@@ -620,6 +624,116 @@ def _open_bioio(path: str, scene: int) -> tuple[da.Array, str, PixelSize]:
620
624
  return arr, axes, pixel_size
621
625
 
622
626
 
627
+ _UNIT_TO_UM = {
628
+ "micron": 1.0,
629
+ "um": 1.0,
630
+ "µm": 1.0,
631
+ "mm": 1000.0,
632
+ "cm": 10000.0,
633
+ "centimeter": 10000.0,
634
+ "inch": 25400.0,
635
+ }
636
+ # TIFF ResolutionUnit tag values (RESUNIT): 2 == INCH, 3 == CENTIMETER.
637
+ _RESUNIT_TO_UM = {2: 25400.0, 3: 10000.0}
638
+
639
+
640
+ def _tiff_pixel_size(path: str) -> PixelSize:
641
+ """Read physical voxel size from a TIFF file's own metadata.
642
+
643
+ Z comes from ImageJ metadata (``spacing`` + ``unit``), if present. X/Y
644
+ come from the page's ``XResolution``/``YResolution`` tags (pixels per
645
+ unit); the unit itself is taken from the ``ResolutionUnit`` tag when set
646
+ (plain TIFFs), or falls back to ImageJ metadata's ``unit`` — ImageJ
647
+ itself writes ``ResolutionUnit=NONE`` and keeps the unit as a string in
648
+ its metadata instead. Unrecognized units are ignored (treated as
649
+ uncalibrated).
650
+
651
+ Parameters
652
+ ----------
653
+ path : str
654
+ Path of one TIFF file in the sequence.
655
+
656
+ Returns
657
+ -------
658
+ dict
659
+ ``{axis: micrometers}`` for whichever axes could be determined
660
+ (empty if none).
661
+ """
662
+ import tifffile
663
+
664
+ pixel_size: PixelSize = {}
665
+ with tifffile.TiffFile(path) as tif:
666
+ ij = tif.imagej_metadata or {}
667
+ spacing = ij.get("spacing")
668
+ if spacing:
669
+ factor = _UNIT_TO_UM.get(str(ij.get("unit", "micron")).lower(), 1.0)
670
+ pixel_size["z"] = float(spacing) * factor
671
+
672
+ page = tif.pages[0]
673
+ unit_tag = page.tags.get("ResolutionUnit")
674
+ um_per_unit = _RESUNIT_TO_UM.get(
675
+ unit_tag.value if unit_tag else None
676
+ ) or _UNIT_TO_UM.get(str(ij.get("unit", "")).lower())
677
+ if um_per_unit is not None:
678
+ for axis, tag_name in (("y", "YResolution"), ("x", "XResolution")):
679
+ tag = page.tags.get(tag_name)
680
+ if tag and tag.value[0]:
681
+ num, den = tag.value
682
+ pixel_size[axis] = um_per_unit / (num / den)
683
+ return pixel_size
684
+
685
+
686
+ def _open_tiff_sequence(
687
+ pattern: str, sequence_pattern: str
688
+ ) -> tuple[da.Array, str, PixelSize]:
689
+ """Open a folder of single-plane TIFFs as one lazy dask array.
690
+
691
+ Each file becomes exactly one chunk, decoded on access — no data is
692
+ duplicated or eagerly loaded. See
693
+ :func:`tifffile.TiffSequence`/``ZarrFileSequenceStore`` (the same
694
+ mechanism used by Cellpose's own distributed pipeline,
695
+ ``cellpose.contrib.distributed_segmentation.wrap_folder_of_tiffs``).
696
+
697
+ Parameters
698
+ ----------
699
+ pattern : str
700
+ Glob pattern matching all the files, e.g. ``"folder/*.tif"``.
701
+ sequence_pattern : str
702
+ Regular expression parsing each file name into axis labels and
703
+ indices, e.g. ``r"_T(?P<T>\\d+)_Z(?P<Z>\\d+)_C(?P<C>\\d+)_V\\d+"``.
704
+ Named groups become axis labels directly; axis order follows the
705
+ order groups appear in the pattern.
706
+
707
+ Returns
708
+ -------
709
+ tuple
710
+ ``(array, axes, pixel_size)`` — a lazy dask array, its axes string
711
+ (parsed axes + trailing ``"yx"``) and a ``{axis: micrometers}``
712
+ calibration dict read from the first file.
713
+ """
714
+ try:
715
+ import tifffile
716
+ except ImportError as exc:
717
+ raise ImportError(
718
+ "reading a folder of TIFFs requires tifffile. Install it with:\n"
719
+ " pip install tifffile\n"
720
+ "(already pulled in by patchworks[bioio] via bioio-tifffile)."
721
+ ) from exc
722
+
723
+ ts = tifffile.TiffSequence(pattern, pattern=sequence_pattern)
724
+ arr = da.from_zarr(zarr.open(store=ts.aszarr()))
725
+ axes = ts.axes.lower() + "yx"
726
+ pixel_size = _tiff_pixel_size(ts[0])
727
+ logger.info(
728
+ "tifffile sequence opened %s files as %s %s cal=%s",
729
+ len(ts),
730
+ axes,
731
+ arr.shape,
732
+ pixel_size,
733
+ )
734
+ return arr, axes, pixel_size
735
+
736
+
623
737
  def _open_imaris(path: str, level: int = 0) -> tuple[da.Array, str, PixelSize]:
624
738
  """Open one Imaris ``.ims`` resolution level lazily.
625
739
 
@@ -769,12 +883,14 @@ def _to_dask(
769
883
  source: Union[da.Array, np.ndarray, str, Path],
770
884
  axes: Union[str, None],
771
885
  scene: int,
886
+ sequence_pattern: Union[str, None] = None,
772
887
  ) -> tuple[da.Array, str, PixelSize]:
773
888
  """Resolve *source* into a lazy ``(array, axes, pixel_size)`` triple.
774
889
 
775
890
  Dispatches by type: dask/NumPy arrays pass through; ``.zarr`` paths use the
776
- OME-ZARR loader; ``.ims`` paths use the Imaris reader; anything else uses
777
- bioio.
891
+ OME-ZARR loader; ``.ims`` paths use the Imaris reader; a *sequence_pattern*
892
+ treats *source* as a glob over a folder of single-plane TIFFs; anything
893
+ else uses bioio.
778
894
 
779
895
  Parameters
780
896
  ----------
@@ -784,6 +900,10 @@ def _to_dask(
784
900
  Explicit axes, or ``None`` to infer them.
785
901
  scene : int
786
902
  Scene index for bioio inputs.
903
+ sequence_pattern : str or None
904
+ When given, *source* is a glob pattern over a folder of single-plane
905
+ TIFFs and this is the filename-parsing regex; see
906
+ :func:`_open_tiff_sequence`.
787
907
 
788
908
  Returns
789
909
  -------
@@ -796,6 +916,9 @@ def _to_dask(
796
916
  return da.asarray(source), axes or _default_axes(source.ndim), {}
797
917
 
798
918
  path = str(source)
919
+ if sequence_pattern is not None:
920
+ arr, detected, ps = _open_tiff_sequence(path, sequence_pattern)
921
+ return arr, axes or detected, ps
799
922
  if path.endswith(".zarr"):
800
923
  arr = load_ome_zarr(source, channel=None)
801
924
  ax = axes or _default_axes(arr.ndim)
@@ -815,6 +938,7 @@ def to_ome_zarr(
815
938
  axes: Union[str, None] = None,
816
939
  pixel_size: Union[PixelSize, tuple, None] = None,
817
940
  scene: int = 0,
941
+ sequence_pattern: Union[str, None] = None,
818
942
  n_levels: int = 5,
819
943
  downscale: int = 2,
820
944
  chunks: Union[tuple[int, ...], None] = None,
@@ -826,7 +950,8 @@ def to_ome_zarr(
826
950
  """Write *source* as a pyramidal, calibrated OME-ZARR store.
827
951
 
828
952
  *source* may be a dask/NumPy array, a ``.zarr`` store, an Imaris ``.ims``
829
- file, or any image format readable by bioio (CZI, LIF, ND2, OME-TIFF, …).
953
+ file, any image format readable by bioio (CZI, LIF, ND2, OME-TIFF, …), or
954
+ (with *sequence_pattern* set) a glob over a folder of single-plane TIFFs.
830
955
  File inputs are read lazily; the pyramid is built level-by-level from disk
831
956
  with bounded chunks, so the full volume never needs to fit in RAM. Only
832
957
  ``x``/``y`` are downsampled; ``z`` (and channel/time) stay full-resolution.
@@ -847,6 +972,15 @@ def to_ome_zarr(
847
972
  arrays.
848
973
  scene : int, optional
849
974
  Scene index for multi-scene bioio files.
975
+ sequence_pattern : str, optional
976
+ When given, *source* is treated as a glob pattern over a folder of
977
+ single-plane TIFFs (e.g. ``"folder/*.tif"``) instead of a single
978
+ file, and this is the regex parsing each file name into axis labels
979
+ and indices via named groups, e.g.
980
+ ``r"_T(?P<T>\\d+)_Z(?P<Z>\\d+)_C(?P<C>\\d+)_V\\d+"``. Each file
981
+ becomes exactly one chunk, read lazily on access (no data
982
+ duplicated) — see :func:`tifffile.TiffSequence`. Axis order follows
983
+ the order named groups appear in the pattern.
850
984
  n_levels : int, optional
851
985
  Maximum number of pyramid levels including full resolution.
852
986
  downscale : int, optional
@@ -881,6 +1015,13 @@ def to_ome_zarr(
881
1015
  >>> from patchworks.plugins.ome_zarr import to_ome_zarr
882
1016
  >>> to_ome_zarr("scan.ims", "scan.zarr", n_levels=4)
883
1017
  'scan.zarr'
1018
+ >>> to_ome_zarr(
1019
+ ... "ZT18_Male4_Left/*.tif",
1020
+ ... "ZT18_Male4_Left.zarr",
1021
+ ... sequence_pattern=r"_T(?P<T>\\d+)_Z(?P<Z>\\d+)_C(?P<C>\\d+)_V\\d+",
1022
+ ... shard=True,
1023
+ ... ) # doctest: +SKIP
1024
+ 'ZT18_Male4_Left.zarr'
884
1025
  """
885
1026
  if downscale < 2:
886
1027
  raise ValueError("downscale must be >= 2")
@@ -908,7 +1049,7 @@ def to_ome_zarr(
908
1049
  exc,
909
1050
  )
910
1051
 
911
- arr, axes, detected = _to_dask(source, axes, scene)
1052
+ arr, axes, detected = _to_dask(source, axes, scene, sequence_pattern)
912
1053
  if len(axes) != arr.ndim:
913
1054
  raise ValueError(
914
1055
  f"axes {axes!r} has {len(axes)} entries but array is {arr.ndim}-D"
@@ -79,6 +79,35 @@ def test_non_spatial_axis_not_downsampled(tmp_path):
79
79
  assert load_ome_zarr(out, channel=None, level=1).shape == (3, 8, 8)
80
80
 
81
81
 
82
+ def test_tiff_sequence_conversion(tmp_path):
83
+ """A folder of single-plane TIFFs is wrapped lazily and converted."""
84
+ tifffile = pytest.importorskip("tifffile")
85
+ n_z, n_c, size = 3, 2, 8
86
+ for z in range(n_z):
87
+ for c in range(n_c):
88
+ img = np.full((size, size), z * 10 + c, dtype="uint16")
89
+ tifffile.imwrite(
90
+ tmp_path / f"sample_Z{z:03d}_C{c}_V0.tif",
91
+ img,
92
+ resolution=(20000.0, 20000.0),
93
+ resolutionunit="CENTIMETER",
94
+ )
95
+
96
+ out = tmp_path / "out.zarr"
97
+ to_ome_zarr(
98
+ str(tmp_path / "*.tif"),
99
+ out,
100
+ sequence_pattern=r"_Z(?P<Z>\d+)_C(?P<C>\d+)_V\d+",
101
+ n_levels=1,
102
+ )
103
+
104
+ result = np.asarray(load_ome_zarr(out, channel=None))
105
+ assert result.shape == (n_z, n_c, size, size)
106
+ # each plane's constant value encodes its (z, c) position.
107
+ assert (result[:, :, 0, 0] == [[z * 10 + c for c in range(n_c)] for z in range(n_z)]).all()
108
+ assert _level_scale(out, 0) == pytest.approx([1.0, 1.0, 0.5, 0.5])
109
+
110
+
82
111
  def test_axes_length_mismatch(tmp_path):
83
112
  with pytest.raises(ValueError):
84
113
  to_ome_zarr(
@@ -5,7 +5,9 @@
5
5
  # GPUs run in parallel; the merge is a single CPU job.
6
6
 
7
7
  # ---- input / output ---------------------------------------------------------
8
- input: "/path/to/scan.ims" # .ims, .czi, .lif, .nd2, ome-tiff, or an .zarr
8
+ input: "/path/to/scan.ims" # .ims, .czi, .lif, .nd2, ome-tiff, .zarr, or a
9
+ # glob (e.g. "/path/*.tif") when sequence_pattern
10
+ # is set below
9
11
  work_dir: "/path/to/results" # everything is written under here
10
12
  # Final outputs (under work_dir):
11
13
  # image.zarr — converted, pyramidal OME-ZARR
@@ -15,6 +17,10 @@ work_dir: "/path/to/results" # everything is written under here
15
17
  reuse_pyramid: false # for .ims: copy its own pyramid (fast); else rebuild
16
18
  convert_chunks: null # null → patchworks' bounded auto chunks; or [c,z,y,x]
17
19
  shard: false # true → pack chunks into shards (zarr v3; fewer files)
20
+ # For a folder of single-plane TIFFs (input: a glob), set the filename regex
21
+ # below; named groups become axes, e.g. one file per Z/C plane:
22
+ # sequence_pattern: '_T(?P<T>\d+)_Z(?P<Z>\d+)_C(?P<C>\d+)_V\d+'
23
+ sequence_pattern: null
18
24
  # Re-running reuses an existing image.zarr automatically (skips conversion).
19
25
  # To force a rebuild: delete image.zarr, or `snakemake --forcerun convert`.
20
26
 
@@ -16,6 +16,7 @@ chunks = cfg.get("convert_chunks")
16
16
  to_ome_zarr(
17
17
  cfg["input"],
18
18
  str(snakemake.output[0]).removesuffix("/zarr.json"), # noqa: F821
19
+ sequence_pattern=cfg.get("sequence_pattern"),
19
20
  chunks=tuple(chunks) if chunks else None,
20
21
  shard=bool(cfg.get("shard", False)),
21
22
  reuse_pyramid=bool(cfg.get("reuse_pyramid", False)),
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes