- pyproject towards PEP621

- catch errors reading metadata in tifread
- read dimension order from ome
This commit is contained in:
Wim Pomp
2025-03-19 15:04:57 +01:00
parent 6daa372ccf
commit 0ac22aff87
4 changed files with 85 additions and 75 deletions

View File

@@ -6,9 +6,10 @@ from pathlib import Path
import numpy as np import numpy as np
import tifffile import tifffile
import yaml import yaml
from ome_types import model import warnings
from ome_types import model, from_xml
from .. import AbstractReader from .. import AbstractReader, try_default
class Reader(AbstractReader, ABC): class Reader(AbstractReader, ABC):
@@ -25,61 +26,75 @@ class Reader(AbstractReader, ABC):
@cached_property @cached_property
def metadata(self): def metadata(self):
return {key: yaml.safe_load(value) if isinstance(value, str) else value return {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):
page = self.reader.pages[0] if self.reader.is_ome:
size_y = page.imagelength with warnings.catch_warnings():
size_x = page.imagewidth warnings.simplefilter('ignore', category=UserWarning)
if self.p_ndim == 3: return from_xml(self.reader.ome_metadata)
size_c = page.samplesperpixel
size_t = self.metadata.get('frames', 1) # // C
else: else:
size_c = self.metadata.get('channels', 1) page = self.reader.pages[0]
size_t = self.metadata.get('frames', 1) size_y = page.imagelength
size_z = self.metadata.get('slices', 1) size_x = page.imagewidth
if 282 in page.tags and 296 in page.tags and page.tags[296].value == 1: if self.p_ndim == 3:
f = page.tags[282].value size_c = page.samplesperpixel
pxsize = f[1] / f[0] size_t = self.metadata.get('frames', 1) # // C
else: else:
pxsize = None size_c = self.metadata.get('channels', 1)
size_t = self.metadata.get('frames', 1)
size_z = self.metadata.get('slices', 1)
if 282 in page.tags and 296 in page.tags and page.tags[296].value == 1:
f = page.tags[282].value
pxsize = f[1] / f[0]
else:
pxsize = None
dtype = page.dtype.name dtype = page.dtype.name
if dtype not in ('int8', 'int16', 'int32', 'uint8', 'uint16', 'uint32', if dtype not in ('int8', 'int16', 'int32', 'uint8', 'uint16', 'uint32',
'float', 'double', 'complex', 'double-complex', 'bit'): 'float', 'double', 'complex', 'double-complex', 'bit'):
dtype = 'float' dtype = 'float'
interval_t = self.metadata.get('interval', 0) interval_t = self.metadata.get('interval', 0)
ome = model.OME() ome = model.OME()
ome.instruments.append(model.Instrument(id='Instrument:0')) ome.instruments.append(model.Instrument(id='Instrument:0'))
ome.instruments[0].objectives.append(model.Objective(id='Objective:0')) ome.instruments[0].objectives.append(model.Objective(id='Objective:0'))
ome.images.append( ome.images.append(
model.Image( model.Image(
id='Image:0', id='Image:0',
pixels=model.Pixels( pixels=model.Pixels(
id='Pixels:0', id='Pixels:0',
size_c=size_c, size_z=size_z, size_t=size_t, size_x=size_x, size_y=size_y, size_c=size_c, size_z=size_z, size_t=size_t, size_x=size_x, size_y=size_y,
dimension_order='XYCZT', type=dtype, # type: ignore dimension_order='XYCZT', type=dtype, # type: ignore
physical_size_x=pxsize, physical_size_y=pxsize), physical_size_x=pxsize, physical_size_y=pxsize),
objective_settings=model.ObjectiveSettings(id='Objective:0'))) objective_settings=model.ObjectiveSettings(id='Objective:0')))
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))
return ome return ome
def open(self): def open(self):
self.reader = tifffile.TiffFile(self.path) self.reader = tifffile.TiffFile(self.path)
page = self.reader.pages[0] page = self.reader.pages.first
self.p_ndim = page.ndim # noqa self.p_ndim = page.ndim # noqa
if self.p_ndim == 3: if self.p_ndim == 3:
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:
self.p_transpose = [i for i in [page.axes.find(j) for j in 'YX'] if i >= 0] # noqa
def close(self): def close(self):
self.reader.close() self.reader.close()
def __frame__(self, c, z, t): def __frame__(self, c: int, z: int, t: int):
dimension_order = self.ome.images[0].pixels.dimension_order.value
if self.p_ndim == 3: if self.p_ndim == 3:
return np.transpose(self.reader.asarray(z + t * self.base_shape['z']), self.p_transpose)[c] axes = ''.join([ax.lower() for ax in dimension_order if ax.lower() in 'zt'])
ct = {'z': z, 't': t}
n = sum([ct[ax] * np.prod(self.base_shape[axes[:i]]) for i, ax in enumerate(axes)])
return np.transpose(self.reader.asarray(int(n)), self.p_transpose)[int(c)]
else: else:
return self.reader.asarray(c + z * self.base_shape['c'] + t * self.base_shape['c'] * self.base_shape['z']) axes = ''.join([ax.lower() for ax in dimension_order if ax.lower() in 'czt'])
czt = {'c': c, 'z': z, 't': t}
n = sum([czt[ax] * np.prod(self.base_shape[axes[:i]]) for i, ax in enumerate(axes)])
return np.transpose(self.reader.asarray(int(n)), self.p_transpose)

View File

@@ -1,6 +1,4 @@
#Insight Transform File V1.0 #Insight Transform File V1.0
#Transform 0
Transform: CompositeTransform_double_2_2
#Transform 1 #Transform 1
Transform: AffineTransform_double_2_2 Transform: AffineTransform_double_2_2
Parameters: 1 0 0 1 0 0 Parameters: 1 0 0 1 0 0

View File

@@ -299,7 +299,7 @@ class Transform:
new.shape = [float(t) for t in transform['Size']] new.shape = [float(t) for t in transform['Size']]
new.origin = [(t - 1) / 2 for t in new.shape] new.origin = [(t - 1) / 2 for t in new.shape]
else: else:
raise NotImplementedError(f'{kind} tranforms not implemented (yet)') raise NotImplementedError(f'{kind} transforms not implemented (yet)')
new.dparameters = 6 * [np.nan] new.dparameters = 6 * [np.nan]
return new return new

View File

@@ -1,46 +1,43 @@
[tool.poetry] [project]
name = "ndbioimage" name = "ndbioimage"
version = "2025.2.1" version = "2025.3.0"
description = "Bio image reading, metadata and some affine registration." description = "Bio image reading, metadata and some affine registration."
authors = ["W. Pomp <w.pomp@nki.nl>"] authors = [
license = "GPLv3" { name = "W. Pomp", email = "w.pomp@nki.nl" }
]
license = { text = "GPL-3.0-or-later"}
readme = "README.md" readme = "README.md"
keywords = ["bioformats", "imread", "numpy", "metadata"] keywords = ["bioformats", "imread", "numpy", "metadata"]
include = ["transform.txt"] include = ["transform.txt"]
repository = "https://github.com/wimpomp/ndbioimage" requires-python = ">=3.10"
exclude = ["ndbioimage/jars"] exclude = ["ndbioimage/jars"]
[tool.poetry.dependencies] dependencies = [
python = "^3.10" "czifile == 2019.7.2",
numpy = ">=1.20.0" "imagecodecs",
pandas = "*" "JPype1",
tifffile = "*" "lxml",
czifile = "2019.7.2" "numpy >= 1.20",
tiffwrite = ">=2024.12.1" "ome-types",
ome-types = ">=0.4.0" "pandas",
pint = "*" "parfor >= 2025.1.0",
tqdm = "*" "pint",
lxml = "*" "pyyaml",
pyyaml = "*" "SimpleITK-SimpleElastix; sys_platform != 'darwin'",
parfor = ">=2025.1.0" "scikit-image",
JPype1 = "*" "tifffile <= 2025.1.10",
SimpleITK-SimpleElastix = [ "tiffwrite >= 2024.12.1",
{ version = "*", python = "<3.12" }, "tqdm",
{ version = "*", python = ">=3.12", markers = "sys_platform != 'darwin'" },
{ version = "*", python = ">=3.12", markers = "platform_machine == 'aarch64'" },
] ]
scikit-image = "*"
imagecodecs = "*"
xsdata = "^23" # until pydantic is up-to-date
matplotlib = { version = "*", optional = true }
scikit-video = { version = "*", optional = true }
pytest = { version = "*", optional = true }
[tool.poetry.extras] [project.optional-dependencies]
test = ["pytest"] test = ["pytest"]
write = ["matplotlib", "scikit-video"] write = ["matplotlib", "scikit-video"]
[tool.poetry.scripts] [project.urls]
repository = "https://github.com/wimpomp/ndbioimage"
[project.scripts]
ndbioimage = "ndbioimage:main" ndbioimage = "ndbioimage:main"
[tool.pytest.ini_options] [tool.pytest.ini_options]