Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion napari_czifile2/_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def reader_function(paths):
num_scenes = CZISceneFile.get_num_scenes(path)
for scene_index in range(num_scenes):
with CZISceneFile(path, scene_index) as f:
data = f.as_tzcyx0_array(max_workers=cpu_count())
data = f.as_tzcyx0_array(maxworkers=cpu_count())
# https://github.com/BodenmillerGroup/napari-czifile2/issues/5
contrast_limits = None
if data.dtype == np.uint16:
Expand Down
82 changes: 33 additions & 49 deletions napari_czifile2/io.py
Original file line number Diff line number Diff line change
@@ -1,77 +1,74 @@
from functools import cached_property
from pathlib import Path
from typing import Iterable, List, Optional, Union
from typing import List, Optional, Union
from xml.etree import ElementTree

import numpy as np
from czifile import CziFile, DimensionEntryDV1, DirectoryEntryDV
from tifffile import lazyattr
from czifile import CziDirectoryEntryDV, CziFile


class CZISceneFile(CziFile):
@staticmethod
def get_num_scenes(path: Union[str, Path], *args, **kwargs) -> int:
with CziFile(path, *args, **kwargs) as czi_file:
if "S" in czi_file.axes:
return czi_file.shape[czi_file.axes.index("S")]
return 1
return len(czi_file.scenes)

def __init__(self, path: Union[str, Path], scene_index: int, *args, **kwargs):
super(CZISceneFile, self).__init__(str(path), *args, **kwargs)
self.scene_index = scene_index
self.axes = list(self.scenes[self.scene_index].sizes.keys())

@lazyattr
def _get_start(self, dim: str) -> int:
try:
return self.scenes[self.scene_index].start[self.axes.index(dim)]
except ValueError:
return 0

@cached_property
def pos_x_um(self) -> float:
return self.scale_x_um * min(
(dim_entry.start for dim_entry in self._iter_dim_entries("X")), default=0.0
)
return self.scale_x_um * self._get_start("X")

@lazyattr
@cached_property
def pos_y_um(self) -> float:
return self.scale_y_um * min(
(dim_entry.start for dim_entry in self._iter_dim_entries("Y")), default=0.0
)
return self.scale_y_um * self._get_start("Y")

@lazyattr
@cached_property
def pos_z_um(self) -> float:
return self.scale_z_um * min(
(dim_entry.start for dim_entry in self._iter_dim_entries("Z")), default=0.0
)
return self.scale_z_um * self._get_start("Z")

@lazyattr
@cached_property
def pos_t_seconds(self) -> float:
return self.scale_t_seconds * min(
(dim_entry.start for dim_entry in self._iter_dim_entries("T")), default=0.0
)
return self.scale_t_seconds * self._get_start("T")

@lazyattr
@cached_property
def scale_x_um(self) -> float:
return self._get_scale("X", multiplier=10.0**6)

@lazyattr
@cached_property
def scale_y_um(self) -> float:
return self._get_scale("Y", multiplier=10.0**6)

@lazyattr
@cached_property
def scale_z_um(self) -> float:
return self._get_scale("Z", multiplier=10.0**6)

@lazyattr
@cached_property
def scale_t_seconds(self) -> float:
return self._get_scale("T")

@lazyattr
@cached_property
def channel_names(self) -> Optional[List[str]]:
if "C" in self.axes:
channel_elements = self._metadata_xml.findall(
".//Metadata/Information/Image/Dimensions/Channels/Channel"
)
if len(channel_elements) == self.shape[self.axes.index("C")]:
if len(channel_elements) == self.scenes[self.scene_index].sizes["C"]:
return [c.attrib.get("Name", c.attrib["Id"]) for c in channel_elements]
return None

@lazyattr
@cached_property
def is_rgb(self) -> bool:
return "0" in self.axes and self.shape[self.axes.index("0")] > 1
return "0" in self.axes and self.scenes[self.scene_index].sizes["0"] > 1

def as_tzcyx0_array(self, *args, **kwargs) -> np.ndarray:
data = self.asarray(*args, **kwargs)
Expand Down Expand Up @@ -105,12 +102,6 @@ def as_tzcyx0_array(self, *args, **kwargs) -> np.ndarray:
data.shape = data.shape[:6]
return data

def _iter_dim_entries(self, dimension: str) -> Iterable[DimensionEntryDV1]:
for dir_entry in self.filtered_subblock_directory:
for dim_entry in dir_entry.dimension_entries:
if dim_entry.dimension == dimension:
yield dim_entry

def _get_scale(self, dimension: str, multiplier: float = 1.0):
scale_element = self._metadata_xml.find(
f'.//Metadata/Scaling/Items/Distance[@Id="{dimension}"]/Value'
Expand All @@ -121,12 +112,12 @@ def _get_scale(self, dimension: str, multiplier: float = 1.0):
return scale * multiplier
return 1.0

@lazyattr
@cached_property
def _metadata_xml(self) -> ElementTree.Element:
return ElementTree.fromstring(self.metadata())

@lazyattr
def filtered_subblock_directory(self) -> List[DirectoryEntryDV]:
@cached_property
def filtered_subblock_directory(self) -> List[CziDirectoryEntryDV]:
dir_entries = super(CZISceneFile, self).filtered_subblock_directory
return list(
filter(
Expand All @@ -136,13 +127,6 @@ def filtered_subblock_directory(self) -> List[DirectoryEntryDV]:
)

@staticmethod
def _get_scene_index(dir_entry: DirectoryEntryDV) -> int:
scene_indices = {
dim_entry.start
for dim_entry in dir_entry.dimension_entries
if dim_entry.dimension == "S"
}
if len(scene_indices) == 0:
return 0
assert len(scene_indices) == 1
return scene_indices.pop()
def _get_scene_index(dir_entry: CziDirectoryEntryDV) -> int:
scene_index = dir_entry.scene_index
return 0 if scene_index == -1 else scene_index
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ project_urls =
[options]
include_package_data = True
install_requires =
czifile
czifile >= 2026
imagecodecs
numpy
tifffile
Expand Down