Skip to content

Commit 0cec271

Browse files
Merge pull request #49 from bioio-devs/feature/time_standard_metadata
feature/time_standard_metadata
2 parents 988cb09 + feeedcd commit 0cec271

File tree

6 files changed

+69573
-32
lines changed

6 files changed

+69573
-32
lines changed

bioio_base/reader.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
imaged_by,
2222
imaging_datetime,
2323
objective,
24+
timelapse_interval,
25+
total_time_duration,
2426
)
2527
from .types import PhysicalPixelSizes, Scale, TimeInterval
2628

@@ -904,7 +906,9 @@ def scale(self) -> Scale:
904906
"""
905907

906908
return Scale(
907-
T=self.time_interval,
909+
T=self.time_interval.total_seconds()
910+
if self.time_interval is not None
911+
else None,
908912
C=None,
909913
Z=self.physical_pixel_sizes.Z,
910914
Y=self.physical_pixel_sizes.Y,
@@ -1113,7 +1117,6 @@ def standard_metadata(self) -> StandardMetadata:
11131117
image_size_y=getattr(self.dims, DimensionNames.SpatialY, None),
11141118
image_size_z=getattr(self.dims, DimensionNames.SpatialZ, None),
11151119
timelapse=image_size_t is not None and image_size_t > 0,
1116-
timelapse_interval=self.time_interval,
11171120
pixel_size_x=self.physical_pixel_sizes.X,
11181121
pixel_size_y=self.physical_pixel_sizes.Y,
11191122
pixel_size_z=self.physical_pixel_sizes.Z,
@@ -1122,6 +1125,12 @@ def standard_metadata(self) -> StandardMetadata:
11221125
imaged_by=imaged_by(ome) if ome is not None else None,
11231126
imaging_datetime=imaging_datetime(ome) if ome is not None else None,
11241127
objective=objective(ome) if ome is not None else None,
1128+
timelapse_interval=timelapse_interval(ome, self.current_scene_index)
1129+
if ome
1130+
else self.time_interval,
1131+
total_time_duration=total_time_duration(ome, self.current_scene_index)
1132+
if ome is not None
1133+
else None,
11251134
)
11261135

11271136
return metadata

bioio_base/standard_metadata.py

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import logging
22
from dataclasses import dataclass
3-
from datetime import datetime
3+
from datetime import datetime, timedelta
44
from typing import Optional, Sequence
55

66
from ome_types import OME
7+
from ome_types.model import UnitsTime
78

89
log = logging.getLogger(__name__)
910

@@ -66,11 +67,10 @@ class StandardMetadata:
6667
timelapse: Optional[bool]
6768
Is the data a timelapse?
6869
69-
timelapse_interval: Optional[float]
70-
Time interval between frames, measured from the beginning of the first
71-
time point to the beginning of the second timepoint.
70+
timelapse_interval: Optional[timedelta]
71+
Average time interval between timepoints.
7272
73-
total_time_duration: Optional[str]
73+
total_time_duration: Optional[timedelta]
7474
Total time duration of imaging, measured from the beginning of the first
7575
time point to the beginning of the final time point.
7676
@@ -95,8 +95,8 @@ class StandardMetadata:
9595
position_index: Optional[int] = None
9696
row: Optional[str] = None
9797
timelapse: Optional[bool] = None
98-
timelapse_interval: Optional[float] = None
99-
total_time_duration: Optional[str] = None
98+
timelapse_interval: Optional[timedelta] = None
99+
total_time_duration: Optional[timedelta] = None
100100

101101
FIELD_LABELS = {
102102
"binning": "Binning",
@@ -232,3 +232,71 @@ def objective(ome: OME) -> Optional[str]:
232232
except Exception as exc:
233233
log.warning("Failed to extract Objective: %s", exc, exc_info=True)
234234
return None
235+
236+
237+
def _convert_to_timedelta(delta_t: float, unit: Optional[UnitsTime]) -> timedelta:
238+
"""
239+
Converts delta_t to a timedelta object based on the provided unit.
240+
"""
241+
if unit is None:
242+
# Assume seconds if unit is None
243+
return timedelta(seconds=delta_t)
244+
245+
unit_value = unit.value # Access the string representation of the enum
246+
247+
if unit_value == "ms":
248+
return timedelta(milliseconds=delta_t)
249+
elif unit_value == "µs":
250+
return timedelta(microseconds=delta_t)
251+
elif unit_value == "ns":
252+
return timedelta(microseconds=delta_t / 1000.0)
253+
else:
254+
# Default to seconds for unrecognized units
255+
log.warning("No units found for timedelta, defaulting to seconds.")
256+
return timedelta(seconds=delta_t)
257+
258+
259+
def total_time_duration(ome: OME, scene_index: int) -> Optional[timedelta]:
260+
"""
261+
Computes the total time duration from the beginning of the first
262+
timepoint to the beginning of the final timepoint.
263+
"""
264+
try:
265+
image = ome.images[scene_index]
266+
planes = image.pixels.planes
267+
268+
# Initialize variables to track the maximum the_t and corresponding plane
269+
max_t = -1
270+
target_plane = None
271+
272+
for p in planes:
273+
if p.the_z == 0 and p.the_c == 0 and p.the_t is not None:
274+
if p.the_t > max_t:
275+
max_t = p.the_t
276+
target_plane = p
277+
278+
if target_plane is None or target_plane.delta_t is None:
279+
return None
280+
281+
return _convert_to_timedelta(target_plane.delta_t, target_plane.delta_t_unit)
282+
except Exception:
283+
return None
284+
285+
286+
def timelapse_interval(ome: OME, scene_index: int) -> Optional[timedelta]:
287+
"""
288+
Computes the average time interval between consecutive timepoints.
289+
"""
290+
try:
291+
image = ome.images[scene_index]
292+
size_t = image.pixels.size_t
293+
if size_t is None or size_t < 2:
294+
return None
295+
296+
total_duration = total_time_duration(ome, scene_index)
297+
if total_duration is None:
298+
return None
299+
300+
return total_duration / (size_t - 1)
301+
except Exception:
302+
return None

0 commit comments

Comments
 (0)