- 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
/dist/
/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])
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,
currently optimized for .czi files, but can open anything that bioformats can handle
path: path to the image file
@@ -1528,6 +1529,12 @@ class AbstractReader(Imread, metaclass=ABCMeta):
self.immersionN = 1
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:
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)]
@cached_property
def tiles(self):
def tiles(self) -> tuple[int, int]:
columns = 1
rows = 1
xml = self.reader.metadata()
@@ -335,6 +335,7 @@ class OmeParse:
self.get_light_sources()
self.get_filters()
self.get_pixels()
self.get_tirf_angle()
self.get_channels()
self.get_planes()
self.get_annotations()
@@ -555,6 +556,18 @@ class OmeParse:
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
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
def positions(self) -> tuple[float, float, Optional[float]]:
if self.version == "1.0":
@@ -677,28 +690,30 @@ class OmeParse:
else:
light_source_settings = None
self.ome.images[0].pixels.channels.append(
model.Channel(
id=f"Channel:{idx}",
name=channel.attrib["Name"],
acquisition_mode=self.text(channel.find("AcquisitionMode")).replace( # type: ignore
"SingleMoleculeLocalisation", "SingleMoleculeImaging"
),
color=color,
detector_settings=model.DetectorSettings(
id=detector.attrib["Id"].replace(" ", ""), binning=binning
),
emission_wavelength=emission_wavelength,
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
light_source_settings=light_source_settings,
samples_per_pixel=samples_per_pixel,
)
channel = dict(
id=f"Channel:{idx}",
name=channel.attrib["Name"],
acquisition_mode=self.text(channel.find("AcquisitionMode")).replace( # type: ignore
"SingleMoleculeLocalisation", "SingleMoleculeImaging"
),
detector_settings=model.DetectorSettings(
id=detector.attrib["Id"].replace(" ", ""), binning=binning
),
emission_wavelength=emission_wavelength,
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
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:
try:
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)):
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
def open(self):
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)
page = self.reader.pages.first
self.p_ndim = page.ndim # noqa
+3 -3
View File
@@ -1,6 +1,6 @@
[project]
name = "ndbioimage"
version = "2026.4.0"
version = "2026.7.0"
description = "Bio image reading, metadata and some affine registration."
authors = [
{ name = "W. Pomp", email = "w.pomp@nki.nl" }
@@ -24,8 +24,8 @@ dependencies = [
"pyyaml",
"SimpleITK-SimpleElastix; sys_platform != 'darwin'",
"scikit-image",
"tifffile <= 2025.1.10",
"tiffwrite >= 2024.12.1",
"tifffile >= 2024.1.30, <= 2025.1.10",
"tiffwrite >= 2026.5.0",
"tqdm",
]