- czi: read tirf angle
PyTest / pytest (3.10) (push) Failing after 1m52s
PyTest / pytest (3.11) (push) Failing after 1m0s
PyTest / pytest (3.12) (push) Failing after 32s
PyTest / pytest (3.13) (push) Failing after 58s

- tiff: read spacing
This commit is contained in:
w.pomp
2026-07-27 14:14:18 +02:00
parent 2f076bd7b8
commit 74b81da78c
6 changed files with 56 additions and 26 deletions
+1
View File
@@ -11,3 +11,4 @@
/poetry.lock /poetry.lock
/dist/ /dist/
/uv.lock /uv.lock
.agentbridge
+8 -1
View File
@@ -191,7 +191,8 @@ def get_positions(path: str | Path) -> Optional[list[int]]:
return subclass.get_positions(AbstractReader.split_path_series(path)[0]) return subclass.get_positions(AbstractReader.split_path_series(path)[0])
class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC): # noinspection PyAbstractClass
class Imread(np.lib.mixins.NDArrayOperatorsMixin):
"""class to read image files, while taking good care of important metadata, """class to read image files, while taking good care of important metadata,
currently optimized for .czi files, but can open anything that bioformats can handle currently optimized for .czi files, but can open anything that bioformats can handle
path: path to the image file path: path to the image file
@@ -1528,6 +1529,12 @@ class AbstractReader(Imread, metaclass=ABCMeta):
self.immersionN = 1 self.immersionN = 1
p = re.compile(r"(\d+):(\d+)$") p = re.compile(r"(\d+):(\d+)$")
if self.ome.structured_annotations is not None:
tirf_angles = {}
for annotation in self.ome.structured_annotations:
if annotation.description is not None and annotation.description.lower() == "tirfangle":
tirf_angles[int(annotation.id.split(":")[1])] = annotation.value
self.tirfangle = [angle for track, angle in sorted(tirf_angles.items(), key=lambda x: x[0])]
try: try:
self.track, self.detector = zip( self.track, self.detector = zip(
*[ *[
+36 -21
View File
@@ -248,7 +248,7 @@ class Reader(AbstractReader, ABC):
return [(i - j, i - j + k) for i, j, k in zip(directory_entry.start, start, directory_entry.shape)] return [(i - j, i - j + k) for i, j, k in zip(directory_entry.start, start, directory_entry.shape)]
@cached_property @cached_property
def tiles(self): def tiles(self) -> tuple[int, int]:
columns = 1 columns = 1
rows = 1 rows = 1
xml = self.reader.metadata() xml = self.reader.metadata()
@@ -335,6 +335,7 @@ class OmeParse:
self.get_light_sources() self.get_light_sources()
self.get_filters() self.get_filters()
self.get_pixels() self.get_pixels()
self.get_tirf_angle()
self.get_channels() self.get_channels()
self.get_planes() self.get_planes()
self.get_annotations() self.get_annotations()
@@ -555,6 +556,18 @@ class OmeParse:
elif self.size_z > 1 and distance.attrib["Id"] == "Z": elif self.size_z > 1 and distance.attrib["Id"] == "Z":
self.ome.images[0].pixels.physical_size_z = float(self.text(distance.find("Value"))) * 1e6 self.ome.images[0].pixels.physical_size_z = float(self.text(distance.find("Value"))) * 1e6
def get_tirf_angle(self) -> None:
if self.version == "1.0":
for track_setup in self.multi_track_setup:
tirf_angle = track_setup.find("TirfAngle")
if tirf_angle is not None:
self.ome.structured_annotations.append(
model.DoubleAnnotation(
description="TirfAngle", id="Annotation:0", value=50 * float(tirf_angle.text)
)
)
self.ome.images[0].annotation_refs.append(model.AnnotationRef(id="Annotation:0"))
@cached_property @cached_property
def positions(self) -> tuple[float, float, Optional[float]]: def positions(self) -> tuple[float, float, Optional[float]]:
if self.version == "1.0": if self.version == "1.0":
@@ -677,28 +690,30 @@ class OmeParse:
else: else:
light_source_settings = None light_source_settings = None
self.ome.images[0].pixels.channels.append( channel = dict(
model.Channel( id=f"Channel:{idx}",
id=f"Channel:{idx}", name=channel.attrib["Name"],
name=channel.attrib["Name"], acquisition_mode=self.text(channel.find("AcquisitionMode")).replace( # type: ignore
acquisition_mode=self.text(channel.find("AcquisitionMode")).replace( # type: ignore "SingleMoleculeLocalisation", "SingleMoleculeImaging"
"SingleMoleculeLocalisation", "SingleMoleculeImaging" ),
), detector_settings=model.DetectorSettings(
color=color, id=detector.attrib["Id"].replace(" ", ""), binning=binning
detector_settings=model.DetectorSettings( ),
id=detector.attrib["Id"].replace(" ", ""), binning=binning emission_wavelength=emission_wavelength,
), excitation_wavelength=self.try_default(
emission_wavelength=emission_wavelength, float, None, self.text(channel.find("ExcitationWavelength"))
excitation_wavelength=self.try_default( ),
float, None, self.text(channel.find("ExcitationWavelength")) # filter_set_ref=model.FilterSetRef(id=ome.instruments[0].filter_sets[filterset_idx].id),
), illumination_type=self.text(channel.find("IlluminationType")), # type: ignore
# filter_set_ref=model.FilterSetRef(id=ome.instruments[0].filter_sets[filterset_idx].id), light_source_settings=light_source_settings,
illumination_type=self.text(channel.find("IlluminationType")), # type: ignore samples_per_pixel=samples_per_pixel,
light_source_settings=light_source_settings,
samples_per_pixel=samples_per_pixel,
)
) )
if color is not None:
channel["color"] = color
self.ome.images[0].pixels.channels.append(model.Channel(**channel))
def get_planes(self) -> None: def get_planes(self) -> None:
try: try:
exposure_times = [ exposure_times = [
+8 -1
View File
@@ -142,11 +142,18 @@ class Reader(AbstractReader, ABC):
) )
for c, z, t in product(range(size_c), range(size_z), range(size_t)): for c, z, t in product(range(size_c), range(size_z), range(size_t)):
ome.images[0].pixels.planes.append(model.Plane(the_c=c, the_z=z, the_t=t, delta_t=interval_t * t)) ome.images[0].pixels.planes.append(model.Plane(the_c=c, the_z=z, the_t=t, delta_t=interval_t * t))
if (spacing := self.metadata.get("spacing")) is not None:
ome.images[0].pixels.physical_size_z = spacing
if (unit := self.metadata.get("unit")) is not None and unit.lower() != "micron":
raise ValueError(f"cannot parse unit {unit}")
return ome return ome
def open(self): def open(self):
if self.series != 0: if self.series != 0:
raise FileNotFoundError(f"Series {self.series} not found in {self.path}. Tifread only supports one series.") raise FileNotFoundError(
f"Series {self.series} not found in {self.path}. Tifread only supports one series."
)
self.reader = tifffile.TiffFile(self.path) self.reader = tifffile.TiffFile(self.path)
page = self.reader.pages.first page = self.reader.pages.first
self.p_ndim = page.ndim # noqa self.p_ndim = page.ndim # noqa
+3 -3
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "ndbioimage" name = "ndbioimage"
version = "2026.4.0" version = "2026.7.0"
description = "Bio image reading, metadata and some affine registration." description = "Bio image reading, metadata and some affine registration."
authors = [ authors = [
{ name = "W. Pomp", email = "w.pomp@nki.nl" } { name = "W. Pomp", email = "w.pomp@nki.nl" }
@@ -24,8 +24,8 @@ dependencies = [
"pyyaml", "pyyaml",
"SimpleITK-SimpleElastix; sys_platform != 'darwin'", "SimpleITK-SimpleElastix; sys_platform != 'darwin'",
"scikit-image", "scikit-image",
"tifffile <= 2025.1.10", "tifffile >= 2024.1.30, <= 2025.1.10",
"tiffwrite >= 2024.12.1", "tiffwrite >= 2026.5.0",
"tqdm", "tqdm",
] ]