Skip to content

Commit 12e5023

Browse files
ziw-liuieivanov
andauthored
Convert time scale for OME-TIFF (#308)
* convert time scale * warn about ndtiff * update tests * clarify t_scale class membership --------- Co-authored-by: Ivan Ivanov <ivan.ivanov@czbiohub.org>
1 parent 14940bd commit 12e5023

5 files changed

Lines changed: 71 additions & 44 deletions

File tree

iohub/convert.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,11 @@ def _gen_chunks(self, input_chunks):
263263
return tuple(chunks)
264264

265265
def _scale_voxels(self):
266+
example_fov = next(iter(self.reader))[1]
266267
return [
267268
TransformationMeta(
268-
type="scale", scale=[1.0, 1.0, *self.reader.zyx_scale]
269+
type="scale",
270+
scale=[example_fov.t_scale, 1.0, *example_fov.zyx_scale],
269271
)
270272
]
271273

iohub/mm_fov.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ def root(self) -> Path:
4040
def zyx_scale(self) -> tuple[float, float, float]:
4141
return self.parent.zyx_scale
4242

43+
@property
44+
def t_scale(self) -> float:
45+
return self.parent.t_scale
46+
4347
@property
4448
def channel_names(self) -> list[str]:
4549
return self.parent.channel_names
@@ -195,3 +199,8 @@ def hcs_position_labels(self):
195199
def zyx_scale(self) -> tuple[float, float, float]:
196200
"""ZXY pixel size in micrometers."""
197201
raise NotImplementedError
202+
203+
@property
204+
def t_scale(self) -> float:
205+
"""Time scale in seconds."""
206+
raise NotImplementedError

iohub/mmstack.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,6 @@ def shape(self) -> tuple[int, int, int, int, int]:
6565
def dtype(self) -> np.dtype:
6666
return self._xdata.dtype
6767

68-
@property
69-
def t_scale(self) -> float:
70-
return self.parent._t_scale
71-
7268
def __getitem__(self, key: int | slice | tuple[int | slice]) -> ArrayLike:
7369
return self._xdata[key]
7470

@@ -387,3 +383,7 @@ def zyx_scale(self) -> tuple[float, float, float]:
387383
self._xy_pixel_size,
388384
self._xy_pixel_size,
389385
)
386+
387+
@property
388+
def t_scale(self) -> float:
389+
return self._t_scale

iohub/ndtiff.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,6 @@ def shape(self) -> tuple[int, int, int, int, int]:
3636
def dtype(self) -> np.dtype:
3737
return self._xdata.dtype
3838

39-
@property
40-
def t_scale(self) -> float:
41-
return 1.0
42-
4339
def __getitem__(
4440
self, key: int | slice | tuple[int | slice, ...]
4541
) -> ArrayLike:
@@ -134,6 +130,14 @@ def close(self) -> None:
134130
def zyx_scale(self) -> tuple[float, float, float]:
135131
return self._zyx_scale
136132

133+
@property
134+
def t_scale(self) -> float:
135+
_logger.warning(
136+
"NDTiff does not store the planned time interval. "
137+
"Returning 1.0 as a placeholder."
138+
)
139+
return 1.0
140+
137141
def _get_summary_metadata(self):
138142
pm_metadata = self.dataset.summary_metadata
139143
pm_metadata["MicroManagerVersion"] = "pycromanager"

tests/test_converter.py

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from tifffile import TiffFile
1111

1212
from iohub.convert import TIFFConverter
13+
from iohub.mm_fov import MicroManagerFOV
1314
from iohub.ngff import Position, open_ome_zarr
1415
from iohub.reader import MMStack, NDTiffDataset
1516
from tests.conftest import (
@@ -35,15 +36,17 @@ def pytest_generate_tests(metafunc):
3536
)
3637

3738

38-
def _check_scale_transform(position: Position) -> None:
39+
def _check_scale_transform(fov: MicroManagerFOV, position: Position) -> None:
3940
"""Check scale transformation of the highest resolution level."""
4041
tf = (
4142
position.metadata.multiscales[0]
4243
.datasets[0]
4344
.coordinate_transformations[0]
4445
)
4546
assert tf.type == "scale"
46-
assert tf.scale[:2] == [1.0, 1.0]
47+
assert tf.scale[0] == fov.t_scale
48+
assert tf.scale[1] == 1.0
49+
assert tf.scale[2:] == list(fov.zyx_scale)
4750

4851

4952
def _check_chunks(
@@ -62,6 +65,33 @@ def _check_chunks(
6265
assert False
6366

6467

68+
def _check_result(
69+
output: Path,
70+
expected_sum: float,
71+
converter: TIFFConverter,
72+
grid_layout: bool,
73+
chunks: Literal["XY", "XYZ"] | tuple[int] | None,
74+
) -> None:
75+
with open_ome_zarr(output, mode="r") as result:
76+
intensity = 0
77+
if grid_layout and converter.p > 1:
78+
assert len(result) < converter.p
79+
for (_, example_fov), (pos_name, pos) in zip(
80+
converter.reader, result.positions(), strict=True
81+
):
82+
_check_scale_transform(example_fov, pos)
83+
_check_chunks(pos, chunks)
84+
intensity += pos["0"][:].sum()
85+
with open(
86+
output / pos_name / "0" / "image_plane_metadata.json"
87+
) as f:
88+
metadata = json.load(f)
89+
key = "0/0/0"
90+
assert key in metadata
91+
assert "ElapsedTime-ms" in metadata[key]
92+
assert intensity == expected_sum
93+
94+
6595
def test_converter_inputs(mm2gamma_ome_tiff, tmpdir):
6696
# Test that output directory needs to end with ".zarr"
6797
output = tmpdir / "converted"
@@ -89,27 +119,21 @@ def test_converter_ometiff(mm2gamma_ome_tiff, grid_layout, chunks, tmpdir):
89119
"Summary",
90120
]
91121
converter()
92-
with open_ome_zarr(output, mode="r") as result:
93-
intensity = 0
94-
if grid_layout and converter.p > 1:
95-
assert len(result) < converter.p
96-
for pos_name, pos in result.positions():
97-
_check_scale_transform(pos)
98-
_check_chunks(pos, chunks)
99-
intensity += pos["0"][:].sum()
100-
assert os.path.isfile(
101-
os.path.join(
102-
output, pos_name, "0", "image_plane_metadata.json"
103-
)
104-
)
105-
assert intensity == raw_array.sum()
122+
_check_result(
123+
output=output,
124+
expected_sum=raw_array.sum(),
125+
converter=converter,
126+
grid_layout=grid_layout,
127+
chunks=chunks,
128+
)
106129

107130

108131
@pytest.fixture(scope="module")
109132
def example_ome_tiff() -> Path:
110133
for d in mm2gamma_ome_tiffs:
111134
if d.name == "mm2.0-20201209_4p_2t_5z_1c_512k_1":
112135
return d
136+
raise FileNotFoundError("Corrupted test data directory")
113137

114138

115139
@pytest.fixture(scope="function")
@@ -191,25 +215,13 @@ def test_converter_ndtiff(ndtiff_datasets: Path, grid_layout, chunks, tmpdir):
191215
"Summary",
192216
]
193217
converter()
194-
with open_ome_zarr(output, mode="r") as result:
195-
intensity = 0
196-
for pos_name, pos in result.positions():
197-
_check_scale_transform(pos)
198-
_check_chunks(pos, chunks)
199-
intensity += pos["0"][:].sum()
200-
assert os.path.isfile(
201-
os.path.join(
202-
output, pos_name, "0", "image_plane_metadata.json"
203-
)
204-
)
205-
assert intensity == raw_array.sum()
206-
with open(
207-
os.path.join(output, pos_name, "0", "image_plane_metadata.json")
208-
) as f:
209-
metadata = json.load(f)
210-
key = "0/0/0"
211-
assert key in metadata
212-
assert "ElapsedTime-ms" in metadata[key]
218+
_check_result(
219+
output=output,
220+
expected_sum=raw_array.sum(),
221+
converter=converter,
222+
grid_layout=grid_layout,
223+
chunks=chunks,
224+
)
213225

214226

215227
def test_converter_ndtiff_v3_position_labels(tmpdir):

0 commit comments

Comments
 (0)