- support some sequences of files in tifread
This commit is contained in:
@@ -192,4 +192,4 @@ class Reader(AbstractReader, ABC):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def __frame__(self, c=0, z=0, t=0):
|
def __frame__(self, c=0, z=0, t=0):
|
||||||
return tifffile.imread(self.path / self.filedict[(c, z, t)])
|
return tifffile.imread(self.path / self.filedict[c, z, t])
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
|
from contextlib import ExitStack
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from itertools import product
|
from itertools import product
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import tifffile
|
import tifffile
|
||||||
import yaml
|
import yaml
|
||||||
from ome_types import from_xml, model
|
from ome_types import OME, from_xml, model
|
||||||
|
|
||||||
from .. import AbstractReader, try_default
|
from .. import AbstractReader, Shape, try_default
|
||||||
|
|
||||||
|
|
||||||
class Reader(AbstractReader, ABC):
|
class Reader(AbstractReader, ABC):
|
||||||
@@ -18,7 +20,7 @@ class Reader(AbstractReader, ABC):
|
|||||||
do_not_pickle = "reader"
|
do_not_pickle = "reader"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _can_open(path):
|
def _can_open(path) -> bool:
|
||||||
if isinstance(path, Path) and path.suffix in (".tif", ".tiff"):
|
if isinstance(path, Path) and path.suffix in (".tif", ".tiff"):
|
||||||
with tifffile.TiffFile(path) as tif:
|
with tifffile.TiffFile(path) as tif:
|
||||||
return tif.is_imagej and tif.pages[-1]._nextifd() == 0 # noqa
|
return tif.is_imagej and tif.pages[-1]._nextifd() == 0 # noqa
|
||||||
@@ -26,13 +28,13 @@ class Reader(AbstractReader, ABC):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def metadata(self):
|
def metadata(self) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
key: try_default(yaml.safe_load, value, value) if isinstance(value, str) else value
|
key: try_default(yaml.safe_load, value, value) if isinstance(value, str) else value
|
||||||
for key, value in self.reader.imagej_metadata.items()
|
for key, value in self.reader.imagej_metadata.items()
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_ome(self):
|
def get_ome(self) -> OME:
|
||||||
if self.reader.is_ome:
|
if self.reader.is_ome:
|
||||||
pos_number_pat = re.compile(r"\d+")
|
pos_number_pat = re.compile(r"\d+")
|
||||||
|
|
||||||
@@ -88,13 +90,17 @@ class Reader(AbstractReader, ABC):
|
|||||||
page = self.reader.pages[0]
|
page = self.reader.pages[0]
|
||||||
size_y = page.imagelength
|
size_y = page.imagelength
|
||||||
size_x = page.imagewidth
|
size_x = page.imagewidth
|
||||||
if self.p_ndim == 3:
|
max_c = max_z = max_t = 0
|
||||||
size_c = page.samplesperpixel
|
if self.file_dict:
|
||||||
size_t = self.metadata.get("frames", 1) # // C
|
for ci, zi, ti in self.file_dict.keys():
|
||||||
else:
|
max_c = max(max_c, ci)
|
||||||
size_c = self.metadata.get("channels", 1)
|
max_z = max(max_z, zi)
|
||||||
size_t = self.metadata.get("frames", 1)
|
max_t = max(max_t, ti)
|
||||||
size_z = self.metadata.get("slices", 1)
|
|
||||||
|
size_c: int = self.mod_shape["c"] * (max_c + 1) # type: ignore
|
||||||
|
size_z: int = self.mod_shape["z"] * (max_z + 1) # type: ignore
|
||||||
|
size_t: int = self.mod_shape["t"] * (max_t + 1) # type: ignore
|
||||||
|
|
||||||
if 282 in page.tags and 296 in page.tags and page.tags[296].value == 1:
|
if 282 in page.tags and 296 in page.tags and page.tags[296].value == 1:
|
||||||
f = page.tags[282].value
|
f = page.tags[282].value
|
||||||
pxsize = f[1] / f[0]
|
pxsize = f[1] / f[0]
|
||||||
@@ -149,7 +155,7 @@ class Reader(AbstractReader, ABC):
|
|||||||
|
|
||||||
return ome
|
return ome
|
||||||
|
|
||||||
def open(self):
|
def open(self) -> None:
|
||||||
if self.series != 0:
|
if self.series != 0:
|
||||||
raise FileNotFoundError(
|
raise FileNotFoundError(
|
||||||
f"Series {self.series} not found in {self.path}. Tifread only supports one series."
|
f"Series {self.series} not found in {self.path}. Tifread only supports one series."
|
||||||
@@ -161,19 +167,68 @@ class Reader(AbstractReader, ABC):
|
|||||||
self.p_transpose = [i for i in [page.axes.find(j) for j in "SYX"] if i >= 0] # noqa
|
self.p_transpose = [i for i in [page.axes.find(j) for j in "SYX"] if i >= 0] # noqa
|
||||||
else:
|
else:
|
||||||
self.p_transpose = [i for i in [page.axes.find(j) for j in "YX"] if i >= 0] # noqa
|
self.p_transpose = [i for i in [page.axes.find(j) for j in "YX"] if i >= 0] # noqa
|
||||||
|
self.file_dict = {} # noqa
|
||||||
|
pat = re.compile(r"^.*?((?:_[czt]\d+)*)$", re.IGNORECASE)
|
||||||
|
if (m := pat.match(self.path.stem)) is not None:
|
||||||
|
m = m.group(1)
|
||||||
|
cpat = re.compile(r"_c(\d+)", re.IGNORECASE)
|
||||||
|
zpat = re.compile(r"_z(\d+)", re.IGNORECASE)
|
||||||
|
tpat = re.compile(r"_t(\d+)", re.IGNORECASE)
|
||||||
|
for file in self.path.parent.glob(
|
||||||
|
re.sub(m, re.sub(r"\d+", "*", m), self.path.stem, re.IGNORECASE) + self.path.suffix
|
||||||
|
):
|
||||||
|
m = pat.match(file.stem)
|
||||||
|
if m:
|
||||||
|
m = m.group(0)
|
||||||
|
c = cpat.findall(m)
|
||||||
|
z = zpat.findall(m)
|
||||||
|
t = tpat.findall(m)
|
||||||
|
c = int(c[0]) if len(c) else 0
|
||||||
|
z = int(z[0]) if len(z) else 0
|
||||||
|
t = int(t[0]) if len(t) else 0
|
||||||
|
self.file_dict[c, z, t] = file.name
|
||||||
|
|
||||||
def close(self):
|
page = self.reader.pages[0]
|
||||||
|
if self.p_ndim == 3:
|
||||||
|
mod_c = page.samplesperpixel
|
||||||
|
mod_t = self.metadata.get("frames", 1) # // C
|
||||||
|
else:
|
||||||
|
mod_c = self.metadata.get("channels", 1)
|
||||||
|
mod_t = self.metadata.get("frames", 1)
|
||||||
|
|
||||||
|
mod_z = self.metadata.get("slices")
|
||||||
|
if mod_z is None:
|
||||||
|
if self.metadata.get("spacing") is None:
|
||||||
|
mod_t = len(self.reader.pages)
|
||||||
|
else:
|
||||||
|
mod_z = len(self.reader.pages)
|
||||||
|
|
||||||
|
self.mod_shape = Shape((mod_c, mod_z, mod_t), "czt") # noqa
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
self.reader.close()
|
self.reader.close()
|
||||||
|
|
||||||
def __frame__(self, c: int, z: int, t: int):
|
def __frame__(self, c: int, z: int, t: int) -> np.ndarray:
|
||||||
|
with ExitStack() as stack:
|
||||||
|
if self.file_dict:
|
||||||
|
filename = self.file_dict[
|
||||||
|
c - c % self.mod_shape["c"], z - z % self.mod_shape["z"], t - t % self.mod_shape["t"]
|
||||||
|
]
|
||||||
|
if self.reader.filename == filename:
|
||||||
|
reader = self.reader
|
||||||
|
else:
|
||||||
|
reader = stack.enter_context(tifffile.TiffFile(self.path.parent / filename))
|
||||||
|
else:
|
||||||
|
reader = self.reader
|
||||||
|
|
||||||
dimension_order = self.ome.images[0].pixels.dimension_order.value
|
dimension_order = self.ome.images[0].pixels.dimension_order.value
|
||||||
if self.p_ndim == 3:
|
if self.p_ndim == 3:
|
||||||
axes = "".join([ax.lower() for ax in dimension_order if ax.lower() in "zt"])
|
axes = "".join([ax.lower() for ax in dimension_order if ax.lower() in "zt"])
|
||||||
ct = {"z": z, "t": t}
|
ct = {"z": z % self.mod_shape["z"], "t": t % self.mod_shape["t"]}
|
||||||
n = sum([ct[ax] * np.prod(self.base_shape[axes[:i]]) for i, ax in enumerate(axes)])
|
n = sum([ct[ax] * np.prod(self.mod_shape[axes[:i]]) for i, ax in enumerate(axes)])
|
||||||
return np.transpose(self.reader.asarray(int(n)), self.p_transpose)[int(c)]
|
return np.transpose(reader.asarray(int(n)), self.p_transpose)[int(c % self.mod_shape["c"])]
|
||||||
else:
|
else:
|
||||||
axes = "".join([ax.lower() for ax in dimension_order if ax.lower() in "czt"])
|
axes = "".join([ax.lower() for ax in dimension_order if ax.lower() in "czt"])
|
||||||
czt = {"c": c, "z": z, "t": t}
|
czt = {"c": c % self.mod_shape["c"], "z": z % self.mod_shape["z"], "t": t % self.mod_shape["t"]}
|
||||||
n = sum([czt[ax] * np.prod(self.base_shape[axes[:i]]) for i, ax in enumerate(axes)])
|
n = sum([czt[ax] * np.prod(self.mod_shape[axes[:i]]) for i, ax in enumerate(axes)])
|
||||||
return np.transpose(self.reader.asarray(int(n)), self.p_transpose)
|
return np.transpose(reader.asarray(int(n)), self.p_transpose)
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "ndbioimage"
|
name = "ndbioimage"
|
||||||
version = "2026.7.0"
|
version = "2026.7.1"
|
||||||
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" }
|
||||||
|
|||||||
Reference in New Issue
Block a user