- Some typing.
- Ignore sitk on python3.12 on intel mac.
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import re
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
from abc import ABC, ABCMeta, abstractmethod
|
from abc import ABC, ABCMeta, abstractmethod
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
@@ -11,18 +11,17 @@ from datetime import datetime
|
|||||||
from functools import cached_property, wraps
|
from functools import cached_property, wraps
|
||||||
from importlib.metadata import version
|
from importlib.metadata import version
|
||||||
from itertools import product
|
from itertools import product
|
||||||
from numbers import Number
|
|
||||||
from operator import truediv
|
from operator import truediv
|
||||||
from pathlib import Path, PosixPath, WindowsPath, PurePath
|
from pathlib import Path
|
||||||
from traceback import print_exc
|
from traceback import print_exc
|
||||||
from typing import Any, Callable, Mapping, Optional
|
from typing import Any, Callable, Generator, Iterable, Optional, Sequence, TypeVar
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import ome_types
|
|
||||||
import yaml
|
import yaml
|
||||||
from ome_types import OME, model, ureg
|
from numpy.typing import ArrayLike, DTypeLike
|
||||||
|
from ome_types import OME, from_xml, model, ureg
|
||||||
from pint import set_application_registry
|
from pint import set_application_registry
|
||||||
from tiffwrite import IFD, IJTiffFile
|
from tiffwrite import IFD, IJTiffFile # noqa
|
||||||
from tqdm.auto import tqdm
|
from tqdm.auto import tqdm
|
||||||
|
|
||||||
from .jvm import JVM
|
from .jvm import JVM
|
||||||
@@ -44,6 +43,7 @@ except Exception: # noqa
|
|||||||
ureg.default_format = '~P'
|
ureg.default_format = '~P'
|
||||||
set_application_registry(ureg)
|
set_application_registry(ureg)
|
||||||
warnings.filterwarnings('ignore', 'Reference to unknown ID')
|
warnings.filterwarnings('ignore', 'Reference to unknown ID')
|
||||||
|
Number = int | float | np.integer | np.floating
|
||||||
|
|
||||||
|
|
||||||
class ReaderNotFoundError(Exception):
|
class ReaderNotFoundError(Exception):
|
||||||
@@ -79,7 +79,7 @@ class DequeDict(OrderedDict):
|
|||||||
self.__truncate__()
|
self.__truncate__()
|
||||||
|
|
||||||
|
|
||||||
def find(obj: Mapping, **kwargs: Any) -> Any:
|
def find(obj: Sequence[Any], **kwargs: Any) -> Any:
|
||||||
for item in obj:
|
for item in obj:
|
||||||
try:
|
try:
|
||||||
if all([getattr(item, key) == value for key, value in kwargs.items()]):
|
if all([getattr(item, key) == value for key, value in kwargs.items()]):
|
||||||
@@ -88,7 +88,10 @@ def find(obj: Mapping, **kwargs: Any) -> Any:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def try_default(fun: Callable, default: Any, *args: Any, **kwargs: Any) -> Any:
|
R = TypeVar('R')
|
||||||
|
|
||||||
|
|
||||||
|
def try_default(fun: Callable[..., R], default: Any, *args: Any, **kwargs: Any) -> R:
|
||||||
try:
|
try:
|
||||||
return fun(*args, **kwargs)
|
return fun(*args, **kwargs)
|
||||||
except Exception: # noqa
|
except Exception: # noqa
|
||||||
@@ -103,7 +106,7 @@ def bioformats_ome(path: str | Path) -> OME:
|
|||||||
reader = jvm.image_reader()
|
reader = jvm.image_reader()
|
||||||
reader.setMetadataStore(ome_meta)
|
reader.setMetadataStore(ome_meta)
|
||||||
reader.setId(str(path))
|
reader.setId(str(path))
|
||||||
ome = ome_types.from_xml(str(ome_meta.dumpXML()), parser='lxml')
|
ome = from_xml(str(ome_meta.dumpXML()), parser='lxml')
|
||||||
except Exception: # noqa
|
except Exception: # noqa
|
||||||
print_exc()
|
print_exc()
|
||||||
ome = model.OME()
|
ome = model.OME()
|
||||||
@@ -113,12 +116,12 @@ def bioformats_ome(path: str | Path) -> OME:
|
|||||||
|
|
||||||
|
|
||||||
class Shape(tuple):
|
class Shape(tuple):
|
||||||
def __new__(cls, shape: tuple[int] | Shape[int], axes: str = 'yxczt') -> Shape[int]:
|
def __new__(cls, shape: Sequence[int] | Shape, axes: str = 'yxczt') -> Shape:
|
||||||
if isinstance(shape, Shape):
|
if isinstance(shape, Shape):
|
||||||
axes = shape.axes # type: ignore
|
axes = shape.axes # type: ignore
|
||||||
instance = super().__new__(cls, shape)
|
new = super().__new__(cls, shape)
|
||||||
instance.axes = axes.lower()
|
new.axes = axes.lower()
|
||||||
return instance # type: ignore
|
return new # type: ignore
|
||||||
|
|
||||||
def __getitem__(self, n: int | str) -> int | tuple[int]:
|
def __getitem__(self, n: int | str) -> int | tuple[int]:
|
||||||
if isinstance(n, str):
|
if isinstance(n, str):
|
||||||
@@ -170,11 +173,8 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
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
|
||||||
optional:
|
optional:
|
||||||
series: in case multiple experiments are saved in one file, like in .lif files
|
axes: order of axes, default: cztyx, but omitting any axes with lenght 1
|
||||||
dtype: datatype to be used when returning frames
|
dtype: datatype to be used when returning frames
|
||||||
meta: define metadata, used for pickle-ing
|
|
||||||
|
|
||||||
NOTE: run imread.kill_vm() at the end of your script/program, otherwise python might not terminate
|
|
||||||
|
|
||||||
modify images on the fly with a decorator function:
|
modify images on the fly with a decorator function:
|
||||||
define a function which takes an instance of this object, one image frame,
|
define a function which takes an instance of this object, one image frame,
|
||||||
@@ -183,21 +183,18 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
then use imread as usually
|
then use imread as usually
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
>> im = imread('/DATA/lenstra_lab/w.pomp/data/20190913/01_YTL639_JF646_DefiniteFocus.czi')
|
>> im = Imread('/path/to/file.image', axes='czt)
|
||||||
>> im
|
>> im
|
||||||
<< shows summary
|
<< shows summary
|
||||||
>> im.shape
|
>> im.shape
|
||||||
<< (256, 256, 2, 1, 600)
|
<< (15, 26, 1000, 1000)
|
||||||
>> plt.imshow(im(1, 0, 100))
|
>> im.axes
|
||||||
<< plots frame at position c=1, z=0, t=100 (python type indexing), note: round brackets; always 2d array
|
<< 'ztyx'
|
||||||
with 1 frame
|
>> plt.imshow(im[1, 0])
|
||||||
>> data = im[:,:,0,0,:25]
|
<< plots frame at position z=1, t=0 (python type indexing)
|
||||||
<< retrieves 5d numpy array containing first 25 frames at c=0, z=0, note: square brackets; always 5d array
|
>> plt.imshow(im[:, 0].max('z'))
|
||||||
>> plt.imshow(im.max(0, None, 0))
|
<< plots max-z projection at t=0
|
||||||
<< plots max-z projection at c=0, t=0
|
>> im.pxsize_um
|
||||||
>> len(im)
|
|
||||||
<< total number of frames
|
|
||||||
>> im.pxsize
|
|
||||||
<< 0.09708737864077668 image-plane pixel size in um
|
<< 0.09708737864077668 image-plane pixel size in um
|
||||||
>> im.laserwavelengths
|
>> im.laserwavelengths
|
||||||
<< [642, 488]
|
<< [642, 488]
|
||||||
@@ -210,15 +207,37 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
Subclass AbstractReader to add more file types. A subclass should always have at least the following
|
Subclass AbstractReader to add more file types. A subclass should always have at least the following
|
||||||
methods:
|
methods:
|
||||||
staticmethod _can_open(path): returns True when the subclass can open the image in path
|
staticmethod _can_open(path): returns True when the subclass can open the image in path
|
||||||
__metadata__(self): pulls some metadata from the file and do other format specific things,
|
|
||||||
it needs to define a few properties, like shape, etc.
|
|
||||||
__frame__(self, c, z, t): this should return a single frame at channel c, slice z and time t
|
__frame__(self, c, z, t): this should return a single frame at channel c, slice z and time t
|
||||||
|
optional open(self): code to be run during initialization, e.g. to open a file handle
|
||||||
optional close(self): close the file in a proper way
|
optional close(self): close the file in a proper way
|
||||||
optional field priority: subclasses with lower priority will be tried first, default = 99
|
optional class field priority: subclasses with lower priority will be tried first, default = 99
|
||||||
|
optional get_ome(self) -> OME: return an OME structure with metadata,
|
||||||
|
if not present bioformats will be used to generate an OME
|
||||||
Any other method can be overridden as needed
|
Any other method can be overridden as needed
|
||||||
wp@tl2019-2023 """
|
"""
|
||||||
|
|
||||||
def __new__(cls, path=None, *args, **kwargs):
|
isclosed: Optional[bool]
|
||||||
|
channel_names: Optional[list[str]]
|
||||||
|
series: Optional[int]
|
||||||
|
pxsize_um: Optional[float]
|
||||||
|
deltaz_um: Optional[float]
|
||||||
|
exposuretime_s: Optional[list[float]]
|
||||||
|
timeinterval: Optional[float]
|
||||||
|
binning: Optional[list[int]]
|
||||||
|
laserwavelengths: Optional[list[tuple[float]]]
|
||||||
|
laserpowers: Optional[list[tuple[float]]]
|
||||||
|
objective: Optional[model.Objective]
|
||||||
|
magnification: Optional[float]
|
||||||
|
tubelens: Optional[model.Objective]
|
||||||
|
filter: Optional[str]
|
||||||
|
powermode: Optional[str]
|
||||||
|
collimator: Optional[str]
|
||||||
|
tirfangle: Optional[list[float]]
|
||||||
|
gain: Optional[list[float]]
|
||||||
|
pcf: Optional[list[float]]
|
||||||
|
__frame__: Callable[[int, int, int], np.ndarray]
|
||||||
|
|
||||||
|
def __new__(cls, path: Path | str | Imread | Any = None, dtype: DTypeLike = None, axes: str = None) -> Imread:
|
||||||
if cls is not Imread:
|
if cls is not Imread:
|
||||||
return super().__new__(cls)
|
return super().__new__(cls)
|
||||||
if len(AbstractReader.__subclasses__()) == 0:
|
if len(AbstractReader.__subclasses__()) == 0:
|
||||||
@@ -227,7 +246,7 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
return path
|
return path
|
||||||
path, _ = AbstractReader.split_path_series(path)
|
path, _ = AbstractReader.split_path_series(path)
|
||||||
for subclass in sorted(AbstractReader.__subclasses__(), key=lambda subclass_: subclass_.priority):
|
for subclass in sorted(AbstractReader.__subclasses__(), key=lambda subclass_: subclass_.priority):
|
||||||
if subclass._can_open(path):
|
if subclass._can_open(path): # noqa
|
||||||
do_not_pickle = (AbstractReader.do_not_pickle,) if isinstance(AbstractReader.do_not_pickle, str) \
|
do_not_pickle = (AbstractReader.do_not_pickle,) if isinstance(AbstractReader.do_not_pickle, str) \
|
||||||
else AbstractReader.do_not_pickle
|
else AbstractReader.do_not_pickle
|
||||||
subclass_do_not_pickle = (subclass.do_not_pickle,) if isinstance(subclass.do_not_pickle, str) \
|
subclass_do_not_pickle = (subclass.do_not_pickle,) if isinstance(subclass.do_not_pickle, str) \
|
||||||
@@ -237,7 +256,11 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
return super().__new__(subclass)
|
return super().__new__(subclass)
|
||||||
raise ReaderNotFoundError(f'No reader found for {path}.')
|
raise ReaderNotFoundError(f'No reader found for {path}.')
|
||||||
|
|
||||||
def __init__(self, base=None, slice=None, shape=(0, 0, 0, 0, 0), dtype=None, frame_decorator=None):
|
def __init__(self, base: Imread = None,
|
||||||
|
slice: tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray] = None, # noqa
|
||||||
|
shape: tuple[int, ...] = (0, 0, 0, 0, 0),
|
||||||
|
dtype: DTypeLike = None,
|
||||||
|
frame_decorator: Callable[[Imread, np.ndarray, int, int, int], np.ndarray] = None) -> None:
|
||||||
self.base = base or self
|
self.base = base or self
|
||||||
self.slice = slice
|
self.slice = slice
|
||||||
self._shape = Shape(shape)
|
self._shape = Shape(shape)
|
||||||
@@ -247,15 +270,15 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
self.flags = dict(C_CONTIGUOUS=False, F_CONTIGUOUS=False, OWNDATA=False, WRITEABLE=False,
|
self.flags = dict(C_CONTIGUOUS=False, F_CONTIGUOUS=False, OWNDATA=False, WRITEABLE=False,
|
||||||
ALIGNED=False, WRITEBACKIFCOPY=False, UPDATEIFCOPY=False)
|
ALIGNED=False, WRITEBACKIFCOPY=False, UPDATEIFCOPY=False)
|
||||||
|
|
||||||
def __call__(self, c=None, z=None, t=None, x=None, y=None):
|
def __call__(self, c: int = None, z: int = None, t: int = None, x: int = None, y: int = None) -> np.ndarray:
|
||||||
""" same as im[] but allowing keyword axes, but slices need to made with slice() or np.s_ """
|
""" same as im[] but allowing keyword axes, but slices need to made with slice() or np.s_ """
|
||||||
return self[{k: slice(v) if v is None else v for k, v in dict(c=c, z=z, t=t, x=x, y=y).items()}]
|
return self[{k: slice(v) if v is None else v for k, v in dict(c=c, z=z, t=t, x=x, y=y).items()}]
|
||||||
|
|
||||||
def __copy__(self):
|
def __copy__(self) -> Imread:
|
||||||
return self.copy()
|
return self.copy()
|
||||||
|
|
||||||
def __contains__(self, item):
|
def __contains__(self, item: Number) -> bool:
|
||||||
def unique_yield(a, b):
|
def unique_yield(a: Iterable[Any], b: Iterable[Any]) -> Generator[Any, None, None]:
|
||||||
for k in a:
|
for k in a:
|
||||||
yield k
|
yield k
|
||||||
for k in b:
|
for k in b:
|
||||||
@@ -270,16 +293,17 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self) -> Imread:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *args, **kwargs):
|
def __exit__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
if not self.isclosed:
|
if not self.isclosed:
|
||||||
self.isclosed = True
|
self.isclosed = True
|
||||||
if hasattr(self, 'close'):
|
if hasattr(self, 'close'):
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def __getitem__(self, n):
|
def __getitem__(self, n: int | Sequence[int] | slice | type(Ellipsis) |
|
||||||
|
dict[str, int | Sequence[int] | slice | type(Ellipsis)]) -> Number | Imread | np.ndarray:
|
||||||
""" slice like a numpy array but return an Imread instance """
|
""" slice like a numpy array but return an Imread instance """
|
||||||
if self.isclosed:
|
if self.isclosed:
|
||||||
raise OSError('file is closed')
|
raise OSError('file is closed')
|
||||||
@@ -326,26 +350,26 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
if not isinstance(s, Number)])
|
if not isinstance(s, Number)])
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self) -> dict[str: Any]:
|
||||||
return {key: value for key, value in self.__dict__.items() if key not in self.do_not_pickle}
|
return {key: value for key, value in self.__dict__.items() if key not in self.do_not_pickle}
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self) -> int:
|
||||||
return self.shape[0]
|
return self.shape[0]
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return self.summary
|
return self.summary
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state: dict[str, Any]) -> None:
|
||||||
""" What happens during unpickling """
|
""" What happens during unpickling """
|
||||||
self.__dict__.update(state)
|
self.__dict__.update(state)
|
||||||
if isinstance(self, AbstractReader):
|
if isinstance(self, AbstractReader):
|
||||||
self.open()
|
self.open()
|
||||||
self.cache = DequeDict(16)
|
self.cache = DequeDict(16)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return str(self.path)
|
return str(self.path)
|
||||||
|
|
||||||
def __array__(self, dtype=None):
|
def __array__(self, dtype: DTypeLike = None) -> np.ndarray:
|
||||||
block = self.block(*self.slice)
|
block = self.block(*self.slice)
|
||||||
axes_idx = [self.shape.axes.find(i) for i in 'yxczt']
|
axes_idx = [self.shape.axes.find(i) for i in 'yxczt']
|
||||||
axes_squeeze = tuple({i for i, j in enumerate(axes_idx) if j == -1}.union(
|
axes_squeeze = tuple({i for i, j in enumerate(axes_idx) if j == -1}.union(
|
||||||
@@ -358,7 +382,8 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
axes = ''.join(j for i, j in enumerate('yxczt') if i not in axes_squeeze)
|
axes = ''.join(j for i, j in enumerate('yxczt') if i not in axes_squeeze)
|
||||||
return block.transpose([axes.find(i) for i in self.shape.axes if i in axes])
|
return block.transpose([axes.find(i) for i in self.shape.axes if i in axes])
|
||||||
|
|
||||||
def __array_arg_fun__(self, fun, axis=None, out=None):
|
def __array_arg_fun__(self, fun: Callable[[ArrayLike, Optional[int]], Number | np.ndarray],
|
||||||
|
axis: int | str = None, out: np.ndarray = None) -> Number | np.ndarray:
|
||||||
""" frame-wise application of np.argmin and np.argmax """
|
""" frame-wise application of np.argmin and np.argmax """
|
||||||
if axis is None:
|
if axis is None:
|
||||||
value = arg = None
|
value = arg = None
|
||||||
@@ -366,13 +391,13 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
yxczt = (slice(None), slice(None)) + idx
|
yxczt = (slice(None), slice(None)) + idx
|
||||||
in_idx = tuple(yxczt['yxczt'.find(i)] for i in self.axes)
|
in_idx = tuple(yxczt['yxczt'.find(i)] for i in self.axes)
|
||||||
new = np.asarray(self[in_idx])
|
new = np.asarray(self[in_idx])
|
||||||
new_arg = np.unravel_index(fun(new), new.shape)
|
new_arg = np.unravel_index(fun(new), new.shape) # type: ignore
|
||||||
new_value = new[new_arg]
|
new_value = new[new_arg]
|
||||||
if value is None:
|
if value is None:
|
||||||
arg = new_arg + idx
|
arg = new_arg + idx
|
||||||
value = new_value
|
value = new_value
|
||||||
else:
|
else:
|
||||||
i = fun((value, new_value))
|
i = fun((value, new_value)) # type: ignore
|
||||||
arg = (arg, new_arg + idx)[i]
|
arg = (arg, new_arg + idx)[i]
|
||||||
value = (value, new_value)[i]
|
value = (value, new_value)[i]
|
||||||
axes = ''.join(i for i in self.axes if i in 'yx') + 'czt'
|
axes = ''.join(i for i in self.axes if i in 'yx') + 'czt'
|
||||||
@@ -420,8 +445,11 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
out[out_idx] = np.where(i, new_arg, out[out_idx])
|
out[out_idx] = np.where(i, new_arg, out[out_idx])
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def __array_fun__(self, funs, axis=None, dtype=None, out=None, keepdims=False, initials=None, where=True,
|
def __array_fun__(self, funs: Sequence[Callable[[ArrayLike], Number | np.ndarray]], axis: int | str = None,
|
||||||
ffuns=None, cfun=None):
|
dtype: DTypeLike = None, out: np.ndarray = None, keepdims: bool = False,
|
||||||
|
initials: list[Number | np.ndarray] = None, where: bool | int | np.ndarray = True,
|
||||||
|
ffuns: Sequence[Callable[[ArrayLike], np.ndarray]] = None,
|
||||||
|
cfun: Callable[..., np.ndarray] = None) -> Number | np.ndarray:
|
||||||
""" frame-wise application of np.min, np.max, np.sum, np.mean and their nan equivalents """
|
""" frame-wise application of np.min, np.max, np.sum, np.mean and their nan equivalents """
|
||||||
p = re.compile(r'\d')
|
p = re.compile(r'\d')
|
||||||
dtype = self.dtype if dtype is None else np.dtype(dtype)
|
dtype = self.dtype if dtype is None else np.dtype(dtype)
|
||||||
@@ -430,11 +458,11 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
if ffuns is None:
|
if ffuns is None:
|
||||||
ffuns = [None for _ in funs]
|
ffuns = [None for _ in funs]
|
||||||
|
|
||||||
def ffun_(frame):
|
def ffun_(frame: ArrayLike) -> np.ndarray:
|
||||||
return np.asarray(frame)
|
return np.asarray(frame)
|
||||||
ffuns = [ffun_ if ffun is None else ffun for ffun in ffuns]
|
ffuns = [ffun_ if ffun is None else ffun for ffun in ffuns]
|
||||||
if cfun is None:
|
if cfun is None:
|
||||||
def cfun(*res):
|
def cfun(*res): # noqa
|
||||||
return res[0]
|
return res[0]
|
||||||
|
|
||||||
# TODO: smarter transforms
|
# TODO: smarter transforms
|
||||||
@@ -501,26 +529,26 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def axes(self):
|
def axes(self) -> str:
|
||||||
return self.shape.axes
|
return self.shape.axes
|
||||||
|
|
||||||
@axes.setter
|
@axes.setter
|
||||||
def axes(self, value):
|
def axes(self, value: str) -> None:
|
||||||
shape = self.shape[value]
|
shape = self.shape[value]
|
||||||
if isinstance(shape, Number):
|
if isinstance(shape, Number):
|
||||||
shape = (shape,)
|
shape = (shape,)
|
||||||
self._shape = Shape(shape, value)
|
self._shape = Shape(shape, value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dtype(self):
|
def dtype(self) -> np.dtype:
|
||||||
return self._dtype
|
return self._dtype
|
||||||
|
|
||||||
@dtype.setter
|
@dtype.setter
|
||||||
def dtype(self, value):
|
def dtype(self, value: DTypeLike) -> None:
|
||||||
self._dtype = np.dtype(value)
|
self._dtype = np.dtype(value)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def extrametadata(self):
|
def extrametadata(self) -> Optional[Any]:
|
||||||
if isinstance(self.path, Path):
|
if isinstance(self.path, Path):
|
||||||
if self.path.with_suffix('.pzl2').exists():
|
if self.path.with_suffix('.pzl2').exists():
|
||||||
pname = self.path.with_suffix('.pzl2')
|
pname = self.path.with_suffix('.pzl2')
|
||||||
@@ -535,26 +563,26 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
return
|
return
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ndim(self):
|
def ndim(self) -> int:
|
||||||
return len(self.shape)
|
return len(self.shape)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def size(self):
|
def size(self) -> int:
|
||||||
return np.prod(self.shape)
|
return np.prod(self.shape)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def shape(self):
|
def shape(self) -> Shape:
|
||||||
return self._shape
|
return self._shape
|
||||||
|
|
||||||
@shape.setter
|
@shape.setter
|
||||||
def shape(self, value):
|
def shape(self, value: Shape | tuple[int, ...]) -> None:
|
||||||
if isinstance(value, Shape):
|
if isinstance(value, Shape):
|
||||||
self._shape = value
|
self._shape = value
|
||||||
else:
|
else:
|
||||||
self._shape = Shape((value['yxczt'.find(i.lower())] for i in self.axes), self.axes)
|
self._shape = Shape([value['yxczt'.find(i.lower())] for i in self.axes], self.axes)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def summary(self):
|
def summary(self) -> str:
|
||||||
""" gives a helpful summary of the recorded experiment """
|
""" gives a helpful summary of the recorded experiment """
|
||||||
s = [f'path/filename: {self.path}',
|
s = [f'path/filename: {self.path}',
|
||||||
f'series/pos: {self.series}',
|
f'series/pos: {self.series}',
|
||||||
@@ -598,44 +626,44 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
return '\n'.join(s)
|
return '\n'.join(s)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def T(self):
|
def T(self) -> Imread: # noqa
|
||||||
return self.transpose()
|
return self.transpose()
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def timeseries(self):
|
def timeseries(self) -> bool:
|
||||||
return self.shape['t'] > 1
|
return self.shape['t'] > 1
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def zstack(self):
|
def zstack(self) -> bool:
|
||||||
return self.shape['z'] > 1
|
return self.shape['z'] > 1
|
||||||
|
|
||||||
@wraps(np.argmax)
|
@wraps(np.ndarray.argmax)
|
||||||
def argmax(self, *args, **kwargs):
|
def argmax(self, *args, **kwargs):
|
||||||
return self.__array_arg_fun__(np.argmax, *args, **kwargs)
|
return self.__array_arg_fun__(np.argmax, *args, **kwargs)
|
||||||
|
|
||||||
@wraps(np.argmin)
|
@wraps(np.ndarray.argmin)
|
||||||
def argmin(self, *args, **kwargs):
|
def argmin(self, *args, **kwargs):
|
||||||
return self.__array_arg_fun__(np.argmin, *args, **kwargs)
|
return self.__array_arg_fun__(np.argmin, *args, **kwargs)
|
||||||
|
|
||||||
@wraps(np.max)
|
@wraps(np.ndarray.max)
|
||||||
def max(self, axis=None, out=None, keepdims=False, initial=None, where=True, **kwargs):
|
def max(self, axis=None, out=None, keepdims=False, initial=None, where=True, **_):
|
||||||
return self.__array_fun__([np.max], axis, None, out, keepdims, [initial], where)
|
return self.__array_fun__([np.max], axis, None, out, keepdims, [initial], where)
|
||||||
|
|
||||||
@wraps(np.mean)
|
@wraps(np.ndarray.mean)
|
||||||
def mean(self, axis=None, dtype=None, out=None, keepdims=False, *, where=True, **kwargs):
|
def mean(self, axis=None, dtype=None, out=None, keepdims=False, *, where=True, **_):
|
||||||
dtype = dtype or float
|
dtype = dtype or float
|
||||||
n = np.prod(self.shape) if axis is None else self.shape[axis]
|
n = np.prod(self.shape) if axis is None else self.shape[axis]
|
||||||
|
|
||||||
def sfun(frame):
|
def sfun(frame: ArrayLike) -> np.ndarray:
|
||||||
return np.asarray(frame).astype(float)
|
return np.asarray(frame).astype(float)
|
||||||
|
|
||||||
def cfun(res):
|
def cfun(res: np.ndarray) -> np.ndarray:
|
||||||
return res / n
|
return res / n
|
||||||
|
|
||||||
return self.__array_fun__([np.sum], axis, dtype, out, keepdims, None, where, [sfun], cfun)
|
return self.__array_fun__([np.sum], axis, dtype, out, keepdims, None, where, [sfun], cfun)
|
||||||
|
|
||||||
@wraps(np.min)
|
@wraps(np.ndarray.min)
|
||||||
def min(self, axis=None, out=None, keepdims=False, initial=None, where=True, **kwargs):
|
def min(self, axis=None, out=None, keepdims=False, initial=None, where=True, **_):
|
||||||
return self.__array_fun__([np.min], axis, None, out, keepdims, [initial], where)
|
return self.__array_fun__([np.min], axis, None, out, keepdims, [initial], where)
|
||||||
|
|
||||||
@wraps(np.moveaxis)
|
@wraps(np.moveaxis)
|
||||||
@@ -643,11 +671,11 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
raise NotImplementedError('moveaxis is not implemented')
|
raise NotImplementedError('moveaxis is not implemented')
|
||||||
|
|
||||||
@wraps(np.nanmax)
|
@wraps(np.nanmax)
|
||||||
def nanmax(self, axis=None, out=None, keepdims=False, initial=None, where=True, **kwargs):
|
def nanmax(self, axis=None, out=None, keepdims=False, initial=None, where=True, **_):
|
||||||
return self.__array_fun__([np.nanmax], axis, None, out, keepdims, [initial], where)
|
return self.__array_fun__([np.nanmax], axis, None, out, keepdims, [initial], where)
|
||||||
|
|
||||||
@wraps(np.nanmean)
|
@wraps(np.nanmean)
|
||||||
def nanmean(self, axis=None, dtype=None, out=None, keepdims=False, *, where=True, **kwargs):
|
def nanmean(self, axis=None, dtype=None, out=None, keepdims=False, *, where=True, **_):
|
||||||
dtype = dtype or float
|
dtype = dtype or float
|
||||||
|
|
||||||
def sfun(frame):
|
def sfun(frame):
|
||||||
@@ -659,11 +687,11 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
return self.__array_fun__([np.nansum, np.sum], axis, dtype, out, keepdims, None, where, (sfun, nfun), truediv)
|
return self.__array_fun__([np.nansum, np.sum], axis, dtype, out, keepdims, None, where, (sfun, nfun), truediv)
|
||||||
|
|
||||||
@wraps(np.nanmin)
|
@wraps(np.nanmin)
|
||||||
def nanmin(self, axis=None, out=None, keepdims=False, initial=None, where=True, **kwargs):
|
def nanmin(self, axis=None, out=None, keepdims=False, initial=None, where=True, **_):
|
||||||
return self.__array_fun__([np.nanmin], axis, None, out, keepdims, [initial], where)
|
return self.__array_fun__([np.nanmin], axis, None, out, keepdims, [initial], where)
|
||||||
|
|
||||||
@wraps(np.nansum)
|
@wraps(np.nansum)
|
||||||
def nansum(self, axis=None, dtype=None, out=None, keepdims=False, initial=None, where=True, **kwargs):
|
def nansum(self, axis=None, dtype=None, out=None, keepdims=False, initial=None, where=True, **_):
|
||||||
return self.__array_fun__([np.nansum], axis, dtype, out, keepdims, [initial], where)
|
return self.__array_fun__([np.nansum], axis, dtype, out, keepdims, [initial], where)
|
||||||
|
|
||||||
@wraps(np.nanstd)
|
@wraps(np.nanstd)
|
||||||
@@ -692,14 +720,15 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
return self.__array_fun__([np.nansum, np.nansum, np.sum], axis, dtype, out, keepdims, None, where,
|
return self.__array_fun__([np.nansum, np.nansum, np.sum], axis, dtype, out, keepdims, None, where,
|
||||||
(sfun, s2fun, nfun), cfun)
|
(sfun, s2fun, nfun), cfun)
|
||||||
|
|
||||||
|
@wraps(np.ndarray.flatten)
|
||||||
def flatten(self, *args, **kwargs):
|
def flatten(self, *args, **kwargs):
|
||||||
return np.asarray(self).flatten(*args, **kwargs)
|
return np.asarray(self).flatten(*args, **kwargs)
|
||||||
|
|
||||||
@wraps(np.reshape)
|
@wraps(np.ndarray.reshape)
|
||||||
def reshape(self, *args, **kwargs):
|
def reshape(self, *args, **kwargs):
|
||||||
return np.asarray(self).reshape(*args, **kwargs)
|
return np.asarray(self).reshape(*args, **kwargs)
|
||||||
|
|
||||||
@wraps(np.squeeze)
|
@wraps(np.ndarray.squeeze)
|
||||||
def squeeze(self, axes=None):
|
def squeeze(self, axes=None):
|
||||||
new = self.copy()
|
new = self.copy()
|
||||||
if axes is None:
|
if axes is None:
|
||||||
@@ -713,15 +742,15 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
new.axes = ''.join(j for i, j in enumerate(new.axes) if i not in axes)
|
new.axes = ''.join(j for i, j in enumerate(new.axes) if i not in axes)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
@wraps(np.std)
|
@wraps(np.ndarray.std)
|
||||||
def std(self, axis=None, dtype=None, out=None, ddof=0, keepdims=None, *, where=True):
|
def std(self, axis=None, dtype=None, out=None, ddof=0, keepdims=None, *, where=True):
|
||||||
return self.var(axis, dtype, out, ddof, keepdims, where=where, std=True)
|
return self.var(axis, dtype, out, ddof, keepdims, where=where, std=True)
|
||||||
|
|
||||||
@wraps(np.sum)
|
@wraps(np.ndarray.sum)
|
||||||
def sum(self, axis=None, dtype=None, out=None, keepdims=False, initial=None, where=True, **kwargs):
|
def sum(self, axis=None, dtype=None, out=None, keepdims=False, initial=None, where=True, **_):
|
||||||
return self.__array_fun__([np.sum], axis, dtype, out, keepdims, [initial], where)
|
return self.__array_fun__([np.sum], axis, dtype, out, keepdims, [initial], where)
|
||||||
|
|
||||||
@wraps(np.swapaxes)
|
@wraps(np.ndarray.swapaxes)
|
||||||
def swapaxes(self, axis1, axis2):
|
def swapaxes(self, axis1, axis2):
|
||||||
new = self.copy()
|
new = self.copy()
|
||||||
axes = new.axes
|
axes = new.axes
|
||||||
@@ -733,7 +762,7 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
new.axes = axes
|
new.axes = axes
|
||||||
return new
|
return new
|
||||||
|
|
||||||
@wraps(np.transpose)
|
@wraps(np.ndarray.transpose)
|
||||||
def transpose(self, *axes):
|
def transpose(self, *axes):
|
||||||
new = self.copy()
|
new = self.copy()
|
||||||
if not axes:
|
if not axes:
|
||||||
@@ -742,7 +771,7 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
new.axes = ''.join(ax if isinstance(ax, str) else new.axes[ax] for ax in axes)
|
new.axes = ''.join(ax if isinstance(ax, str) else new.axes[ax] for ax in axes)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
@wraps(np.var)
|
@wraps(np.ndarray.var)
|
||||||
def var(self, axis=None, dtype=None, out=None, ddof=0, keepdims=None, *, where=True, std=False):
|
def var(self, axis=None, dtype=None, out=None, ddof=0, keepdims=None, *, where=True, std=False):
|
||||||
dtype = dtype or float
|
dtype = dtype or float
|
||||||
n = np.prod(self.shape) if axis is None else self.shape[axis]
|
n = np.prod(self.shape) if axis is None else self.shape[axis]
|
||||||
@@ -761,15 +790,18 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
return (s2 - s ** 2 / n) / (n - ddof)
|
return (s2 - s ** 2 / n) / (n - ddof)
|
||||||
return self.__array_fun__([np.sum, np.sum], axis, dtype, out, keepdims, None, where, (sfun, s2fun), cfun)
|
return self.__array_fun__([np.sum, np.sum], axis, dtype, out, keepdims, None, where, (sfun, s2fun), cfun)
|
||||||
|
|
||||||
def asarray(self):
|
def asarray(self) -> np.ndarray:
|
||||||
return self.__array__()
|
return self.__array__()
|
||||||
|
|
||||||
def astype(self, dtype, *args, **kwargs):
|
@wraps(np.ndarray.astype)
|
||||||
|
def astype(self, dtype, *_, **__):
|
||||||
new = self.copy()
|
new = self.copy()
|
||||||
new.dtype = dtype
|
new.dtype = dtype
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def block(self, y=None, x=None, c=None, z=None, t=None):
|
def block(self, y: int | Sequence[int] = None, x: int | Sequence[int] = None,
|
||||||
|
c: int | Sequence[int] = None, z: int | Sequence[int] = None,
|
||||||
|
t: int | Sequence[int] = None) -> np.ndarray:
|
||||||
""" returns 5D block of frames """
|
""" returns 5D block of frames """
|
||||||
y, x, c, z, t = (np.arange(self.shape[i]) if e is None else np.array(e, ndmin=1)
|
y, x, c, z, t = (np.arange(self.shape[i]) if e is None else np.array(e, ndmin=1)
|
||||||
for i, e in zip('yxczt', (y, x, c, z, t)))
|
for i, e in zip('yxczt', (y, x, c, z, t)))
|
||||||
@@ -778,15 +810,15 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
d[:, :, ci, zi, ti] = self.frame(cj, zj, tj)[y][:, x]
|
d[:, :, ci, zi, ti] = self.frame(cj, zj, tj)[y][:, x]
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def copy(self):
|
def copy(self) -> View:
|
||||||
return View(self)
|
return View(self)
|
||||||
|
|
||||||
def data(self, c=0, z=0, t=0):
|
def data(self, c: int | Sequence[int] = 0, z: int | Sequence[int] = 0, t: int | Sequence[int] = 0) -> np.ndarray:
|
||||||
""" returns 3D stack of frames """
|
""" returns 3D stack of frames """
|
||||||
c, z, t = (np.arange(self.shape[i]) if e is None else np.array(e, ndmin=1) for i, e in zip('czt', (c, z, t)))
|
c, z, t = (np.arange(self.shape[i]) if e is None else np.array(e, ndmin=1) for i, e in zip('czt', (c, z, t)))
|
||||||
return np.dstack([self.frame(ci, zi, ti) for ci, zi, ti in product(c, z, t)])
|
return np.dstack([self.frame(ci, zi, ti) for ci, zi, ti in product(c, z, t)])
|
||||||
|
|
||||||
def frame(self, c=0, z=0, t=0):
|
def frame(self, c: int = 0, z: int = 0, t: int = 0) -> np.ndarray:
|
||||||
""" returns single 2D frame """
|
""" returns single 2D frame """
|
||||||
c = self.get_channel(c)
|
c = self.get_channel(c)
|
||||||
c %= self.base.shape['c']
|
c %= self.base.shape['c']
|
||||||
@@ -808,7 +840,7 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
else:
|
else:
|
||||||
return f.copy()
|
return f.copy()
|
||||||
|
|
||||||
def get_channel(self, channel_name):
|
def get_channel(self, channel_name: str | int) -> int:
|
||||||
if not isinstance(channel_name, str):
|
if not isinstance(channel_name, str):
|
||||||
return channel_name
|
return channel_name
|
||||||
else:
|
else:
|
||||||
@@ -818,14 +850,14 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
return c[0]
|
return c[0]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_config(file):
|
def get_config(file: Path | str) -> Any:
|
||||||
""" Open a yml config file """
|
""" Open a yml config file """
|
||||||
loader = yaml.SafeLoader
|
loader = yaml.SafeLoader
|
||||||
loader.add_implicit_resolver(
|
loader.add_implicit_resolver(
|
||||||
r'tag:yaml.org,2002:float',
|
r'tag:yaml.org,2002:float',
|
||||||
re.compile(r'''^(?:
|
re.compile(r'''^(?:
|
||||||
[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+]?[0-9]+)?
|
[-+]?([0-9][0-9_]*)\.[0-9_]*(?:[eE][-+]?[0-9]+)?
|
||||||
|[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)
|
|[-+]?([0-9][0-9_]*)([eE][-+]?[0-9]+)
|
||||||
|\.[0-9_]+(?:[eE][-+][0-9]+)?
|
|\.[0-9_]+(?:[eE][-+][0-9]+)?
|
||||||
|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*
|
|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*
|
||||||
|[-+]?\\.(?:inf|Inf|INF)
|
|[-+]?\\.(?:inf|Inf|INF)
|
||||||
@@ -834,7 +866,8 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
with open(file) as f:
|
with open(file) as f:
|
||||||
return yaml.load(f, loader)
|
return yaml.load(f, loader)
|
||||||
|
|
||||||
def get_czt(self, c, z, t):
|
def get_czt(self, c: int | Sequence[int], z: int | Sequence[int],
|
||||||
|
t: int | Sequence[int]) -> tuple[list[int], list[int], list[int]]:
|
||||||
czt = []
|
czt = []
|
||||||
for i, n in zip('czt', (c, z, t)):
|
for i, n in zip('czt', (c, z, t)):
|
||||||
if n is None:
|
if n is None:
|
||||||
@@ -848,10 +881,10 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
stop = n.stop
|
stop = n.stop
|
||||||
czt.append(list(range(n.start % self.shape[i], stop, n.step)))
|
czt.append(list(range(n.start % self.shape[i], stop, n.step)))
|
||||||
elif isinstance(n, Number):
|
elif isinstance(n, Number):
|
||||||
czt.append([n % self.shape[i]]) # noqa
|
czt.append([n % self.shape[i]])
|
||||||
else:
|
else:
|
||||||
czt.append([k % self.shape[i] for k in n])
|
czt.append([k % self.shape[i] for k in n])
|
||||||
return [self.get_channel(c) for c in czt[0]], *czt[1:]
|
return [self.get_channel(c) for c in czt[0]], *czt[1:3] # type: ignore
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def bioformats_ome(path: [str, Path]) -> OME:
|
def bioformats_ome(path: [str, Path]) -> OME:
|
||||||
@@ -872,7 +905,7 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
i = np.argsort(z[:, 1])
|
i = np.argsort(z[:, 1])
|
||||||
image.pixels.physical_size_z = np.nanmean(np.true_divide(*np.diff(z[i], axis=0).T)) * 1e6
|
image.pixels.physical_size_z = np.nanmean(np.true_divide(*np.diff(z[i], axis=0).T)) * 1e6
|
||||||
image.pixels.physical_size_z_unit = 'µm'
|
image.pixels.physical_size_z_unit = 'µm'
|
||||||
except Exception:
|
except Exception: # noqa
|
||||||
pass
|
pass
|
||||||
return ome
|
return ome
|
||||||
|
|
||||||
@@ -896,7 +929,7 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
cache[self.path] = self.fix_ome(ome)
|
cache[self.path] = self.fix_ome(ome)
|
||||||
return cache[self.path]
|
return cache[self.path]
|
||||||
|
|
||||||
def is_noise(self, volume=None):
|
def is_noise(self, volume: ArrayLike = None) -> bool:
|
||||||
""" True if volume only has noise """
|
""" True if volume only has noise """
|
||||||
if volume is None:
|
if volume is None:
|
||||||
volume = self
|
volume = self
|
||||||
@@ -905,16 +938,19 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
return 1 - corr[tuple([0] * corr.ndim)] < 0.0067
|
return 1 - corr[tuple([0] * corr.ndim)] < 0.0067
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def kill_vm():
|
def kill_vm() -> None:
|
||||||
JVM().kill_vm()
|
JVM().kill_vm()
|
||||||
|
|
||||||
def new(self, *args, **kwargs):
|
def new(self, *args: Any, **kwargs: Any) -> View:
|
||||||
warnings.warn('Imread.new has been deprecated, use Imread.view instead.', DeprecationWarning, 2)
|
warnings.warn('Imread.new has been deprecated, use Imread.view instead.', DeprecationWarning, 2)
|
||||||
return self.view(*args, **kwargs)
|
return self.view(*args, **kwargs)
|
||||||
|
|
||||||
def save_as_tiff(self, fname=None, c=None, z=None, t=None, split=False, bar=True, pixel_type='uint16', **kwargs):
|
def save_as_tiff(self, fname: Path | str = None, c: int | Sequence[int] = None, z: int | Sequence[int] = None,
|
||||||
|
t: int | Sequence[int] = None, split: bool = False, bar: bool = True, pixel_type: str = 'uint16',
|
||||||
|
**kwargs: Any) -> None:
|
||||||
""" saves the image as a tif file
|
""" saves the image as a tif file
|
||||||
split: split channels into different files """
|
split: split channels into different files """
|
||||||
|
fname = Path(fname)
|
||||||
if fname is None:
|
if fname is None:
|
||||||
fname = self.path.with_suffix('.tif')
|
fname = self.path.with_suffix('.tif')
|
||||||
if fname == self.path:
|
if fname == self.path:
|
||||||
@@ -944,7 +980,8 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
total=np.prod(shape), desc='Saving tiff', disable=not bar):
|
total=np.prod(shape), desc='Saving tiff', disable=not bar):
|
||||||
tif.save(m, *i)
|
tif.save(m, *i)
|
||||||
|
|
||||||
def with_transform(self, channels=True, drift=False, file=None, bead_files=()):
|
def with_transform(self, channels: bool = True, drift: bool = False, file: Path | str = None,
|
||||||
|
bead_files: Sequence[Path | str] = ()) -> View:
|
||||||
""" returns a view where channels and/or frames are registered with an affine transformation
|
""" returns a view where channels and/or frames are registered with an affine transformation
|
||||||
channels: True/False register channels using bead_files
|
channels: True/False register channels using bead_files
|
||||||
drift: True/False register frames to correct drift
|
drift: True/False register frames to correct drift
|
||||||
@@ -955,10 +992,12 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
view = self.view()
|
view = self.view()
|
||||||
if file is None:
|
if file is None:
|
||||||
file = Path(view.path.parent) / 'transform.yml'
|
file = Path(view.path.parent) / 'transform.yml'
|
||||||
|
else:
|
||||||
|
file = Path(file)
|
||||||
if not bead_files:
|
if not bead_files:
|
||||||
try:
|
try:
|
||||||
bead_files = Transforms.get_bead_files(view.path.parent)
|
bead_files = Transforms.get_bead_files(view.path.parent)
|
||||||
except Exception:
|
except Exception: # noqa
|
||||||
if not file.exists():
|
if not file.exists():
|
||||||
raise Exception('No transform file and no bead file found.')
|
raise Exception('No transform file and no bead file found.')
|
||||||
bead_files = ()
|
bead_files = ()
|
||||||
@@ -981,23 +1020,23 @@ class Imread(np.lib.mixins.NDArrayOperatorsMixin, ABC):
|
|||||||
return view
|
return view
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def split_path_series(path):
|
def split_path_series(path: Path | str) -> tuple[Path, int]:
|
||||||
if isinstance(path, str):
|
if isinstance(path, str):
|
||||||
path = Path(path)
|
path = Path(path)
|
||||||
if isinstance(path, Path) and path.name.startswith('Pos') and path.name.lstrip('Pos').isdigit():
|
if isinstance(path, Path) and path.name.startswith('Pos') and path.name.lstrip('Pos').isdigit():
|
||||||
return path.parent, int(path.name.lstrip('Pos'))
|
return path.parent, int(path.name.lstrip('Pos'))
|
||||||
return path, 0
|
return path, 0
|
||||||
|
|
||||||
def view(self, *args, **kwargs):
|
def view(self, *args: Any, **kwargs: Any) -> View:
|
||||||
return View(self, *args, **kwargs)
|
return View(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class View(Imread, ABC):
|
class View(Imread, ABC):
|
||||||
def __init__(self, base, dtype=None):
|
def __init__(self, base: Imread, dtype: DTypeLike = None) -> None:
|
||||||
super().__init__(base.base, base.slice, base.shape, dtype or base.dtype, base.frame_decorator)
|
super().__init__(base.base, base.slice, base.shape, dtype or base.dtype, base.frame_decorator)
|
||||||
self.transform = base.transform
|
self.transform = base.transform
|
||||||
|
|
||||||
def __getattr__(self, item):
|
def __getattr__(self, item: str) -> Any:
|
||||||
if not hasattr(self.base, item):
|
if not hasattr(self.base, item):
|
||||||
raise AttributeError(f'{self.__class__} object has no attribute {item}')
|
raise AttributeError(f'{self.__class__} object has no attribute {item}')
|
||||||
return self.base.__getattribute__(item)
|
return self.base.__getattribute__(item)
|
||||||
@@ -1010,21 +1049,25 @@ class AbstractReader(Imread, metaclass=ABCMeta):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _can_open(path): # Override this method, and return true when the subclass can open the file
|
def _can_open(path: Path | str) -> bool:
|
||||||
|
""" Override this method, and return true when the subclass can open the file """
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def __frame__(self, c, z, t): # Override this, return the frame at c, z, t
|
def __frame__(self, c: int, z: int, t: int) -> np.ndarray:
|
||||||
|
""" Override this, return the frame at c, z, t """
|
||||||
return np.random.randint(0, 255, self.shape['yx'])
|
return np.random.randint(0, 255, self.shape['yx'])
|
||||||
|
|
||||||
def open(self): # Optionally override this, open file handles etc.
|
def open(self) -> None:
|
||||||
""" filehandles cannot be pickled and should be marked such by setting do_not_pickle = 'file_handle_name' """
|
""" Optionally override this, open file handles etc.
|
||||||
|
filehandles cannot be pickled and should be marked such by setting do_not_pickle = 'file_handle_name' """
|
||||||
return
|
return
|
||||||
|
|
||||||
def close(self): # Optionally override this, close file handles etc.
|
def close(self) -> None:
|
||||||
|
""" Optionally override this, close file handles etc. """
|
||||||
return
|
return
|
||||||
|
|
||||||
def __init__(self, path, dtype=None, axes=None):
|
def __init__(self, path: Path | str | Imread | Any = None, dtype: DTypeLike = None, axes: str = None) -> None:
|
||||||
if isinstance(path, Imread):
|
if isinstance(path, Imread):
|
||||||
return
|
return
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -1114,7 +1157,8 @@ class AbstractReader(Imread, metaclass=ABCMeta):
|
|||||||
for channel in pixels.channels if channel.excitation_wavelength_quantity]
|
for channel in pixels.channels if channel.excitation_wavelength_quantity]
|
||||||
self.laserpowers = try_default(lambda: [(1 - channel.light_source_settings.attenuation,)
|
self.laserpowers = try_default(lambda: [(1 - channel.light_source_settings.attenuation,)
|
||||||
for channel in pixels.channels], [])
|
for channel in pixels.channels], [])
|
||||||
self.filter = try_default(lambda: [find(instrument.filter_sets, id=channel.filter_set_ref.id).model
|
self.filter = try_default( # type: ignore
|
||||||
|
lambda: [find(instrument.filter_sets, id=channel.filter_set_ref.id).model
|
||||||
for channel in image.pixels.channels], None)
|
for channel in image.pixels.channels], None)
|
||||||
self.pxsize_um = None if self.pxsize is None else self.pxsize.to(self.ureg.um).m
|
self.pxsize_um = None if self.pxsize is None else self.pxsize.to(self.ureg.um).m
|
||||||
self.exposuretime_s = [None if i is None else i.to(self.ureg.s).m for i in self.exposuretime]
|
self.exposuretime_s = [None if i is None else i.to(self.ureg.s).m for i in self.exposuretime]
|
||||||
@@ -1157,7 +1201,7 @@ class AbstractReader(Imread, metaclass=ABCMeta):
|
|||||||
sigma = np.hstack(sigma)
|
sigma = np.hstack(sigma)
|
||||||
sigma[sigma == 0] = 600 * ureg.nm
|
sigma[sigma == 0] = 600 * ureg.nm
|
||||||
sigma /= 2 * self.NA * self.pxsize
|
sigma /= 2 * self.NA * self.pxsize
|
||||||
self.sigma = sigma.magnitude.tolist()
|
self.sigma = sigma.magnitude.tolist() # type: ignore
|
||||||
except Exception: # noqa
|
except Exception: # noqa
|
||||||
self.sigma = [2] * self.shape['c']
|
self.sigma = [2] * self.shape['c']
|
||||||
if not self.NA:
|
if not self.NA:
|
||||||
@@ -1176,11 +1220,11 @@ class AbstractReader(Imread, metaclass=ABCMeta):
|
|||||||
self.track, self.detector = zip(*[[int(i) for i in p.findall(find(
|
self.track, self.detector = zip(*[[int(i) for i in p.findall(find(
|
||||||
self.ome.images[self.series].pixels.channels, id=f'Channel:{c}').detector_settings.id)[0]]
|
self.ome.images[self.series].pixels.channels, id=f'Channel:{c}').detector_settings.id)[0]]
|
||||||
for c in range(self.shape['c'])])
|
for c in range(self.shape['c'])])
|
||||||
except Exception:
|
except Exception: # noqa
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
parser = ArgumentParser(description='Display info and save as tif')
|
parser = ArgumentParser(description='Display info and save as tif')
|
||||||
parser.add_argument('file', help='image_file')
|
parser.add_argument('file', help='image_file')
|
||||||
parser.add_argument('out', help='path to tif out', type=str, default=None, nargs='?')
|
parser.add_argument('out', help='path to tif out', type=str, default=None, nargs='?')
|
||||||
@@ -1209,4 +1253,4 @@ def main():
|
|||||||
f.write(im.ome.to_xml())
|
f.write(im.ome.to_xml())
|
||||||
|
|
||||||
|
|
||||||
from .readers import *
|
from .readers import * # noqa
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ from abc import ABC
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from itertools import product
|
from itertools import product
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any, TypeVar, Optional
|
||||||
|
|
||||||
import czifile
|
import czifile
|
||||||
import imagecodecs
|
import imagecodecs
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from ome_types import model
|
from ome_types import model, OME
|
||||||
from tifffile import repeat_nd
|
from tifffile import repeat_nd
|
||||||
|
|
||||||
from .. import AbstractReader
|
from .. import AbstractReader
|
||||||
@@ -24,9 +25,12 @@ except ImportError:
|
|||||||
zoom = None
|
zoom = None
|
||||||
|
|
||||||
|
|
||||||
def zstd_decode(data: bytes) -> bytes:
|
Element = TypeVar('Element')
|
||||||
|
|
||||||
|
|
||||||
|
def zstd_decode(data: bytes) -> bytes: # noqa
|
||||||
""" decode zstd bytes, copied from BioFormats ZeissCZIReader """
|
""" decode zstd bytes, copied from BioFormats ZeissCZIReader """
|
||||||
def read_var_int(stream: BytesIO) -> int:
|
def read_var_int(stream: BytesIO) -> int: # noqa
|
||||||
a = stream.read(1)[0]
|
a = stream.read(1)[0]
|
||||||
if a & 128:
|
if a & 128:
|
||||||
b = stream.read(1)[0]
|
b = stream.read(1)[0]
|
||||||
@@ -48,7 +52,7 @@ def zstd_decode(data: bytes) -> bytes:
|
|||||||
else:
|
else:
|
||||||
raise ValueError(f'Invalid chunk id: {chunk_id}')
|
raise ValueError(f'Invalid chunk id: {chunk_id}')
|
||||||
pointer = stream.tell()
|
pointer = stream.tell()
|
||||||
except Exception:
|
except Exception: # noqa
|
||||||
high_low_unpacking = False
|
high_low_unpacking = False
|
||||||
pointer = 0
|
pointer = 0
|
||||||
|
|
||||||
@@ -60,9 +64,9 @@ def zstd_decode(data: bytes) -> bytes:
|
|||||||
return decoded
|
return decoded
|
||||||
|
|
||||||
|
|
||||||
def data(self, raw=False, resize=True, order=0):
|
def data(self, raw: bool = False, resize: bool = True, order: int = 0) -> np.ndarray:
|
||||||
"""Read image data from file and return as numpy array."""
|
"""Read image data from file and return as numpy array."""
|
||||||
DECOMPRESS = czifile.czifile.DECOMPRESS
|
DECOMPRESS = czifile.czifile.DECOMPRESS # noqa
|
||||||
DECOMPRESS[5] = imagecodecs.zstd_decode
|
DECOMPRESS[5] = imagecodecs.zstd_decode
|
||||||
DECOMPRESS[6] = zstd_decode
|
DECOMPRESS[6] = zstd_decode
|
||||||
|
|
||||||
@@ -71,32 +75,32 @@ def data(self, raw=False, resize=True, order=0):
|
|||||||
if raw:
|
if raw:
|
||||||
with fh.lock:
|
with fh.lock:
|
||||||
fh.seek(self.data_offset)
|
fh.seek(self.data_offset)
|
||||||
data = fh.read(self.data_size)
|
data = fh.read(self.data_size) # noqa
|
||||||
return data
|
return data
|
||||||
if de.compression:
|
if de.compression:
|
||||||
# if de.compression not in DECOMPRESS:
|
# if de.compression not in DECOMPRESS:
|
||||||
# raise ValueError('compression unknown or not supported')
|
# raise ValueError('compression unknown or not supported')
|
||||||
with fh.lock:
|
with fh.lock:
|
||||||
fh.seek(self.data_offset)
|
fh.seek(self.data_offset)
|
||||||
data = fh.read(self.data_size)
|
data = fh.read(self.data_size) # noqa
|
||||||
data = DECOMPRESS[de.compression](data)
|
data = DECOMPRESS[de.compression](data) # noqa
|
||||||
if de.compression == 2:
|
if de.compression == 2:
|
||||||
# LZW
|
# LZW
|
||||||
data = np.fromstring(data, de.dtype) # noqa
|
data = np.fromstring(data, de.dtype) # noqa
|
||||||
elif de.compression in (5, 6):
|
elif de.compression in (5, 6):
|
||||||
# ZSTD
|
# ZSTD
|
||||||
data = np.frombuffer(data, de.dtype)
|
data = np.frombuffer(data, de.dtype) # noqa
|
||||||
else:
|
else:
|
||||||
dtype = np.dtype(de.dtype)
|
dtype = np.dtype(de.dtype)
|
||||||
with fh.lock:
|
with fh.lock:
|
||||||
fh.seek(self.data_offset)
|
fh.seek(self.data_offset)
|
||||||
data = fh.read_array(dtype, self.data_size // dtype.itemsize)
|
data = fh.read_array(dtype, self.data_size // dtype.itemsize) # noqa
|
||||||
|
|
||||||
data = data.reshape(de.stored_shape)
|
data = data.reshape(de.stored_shape) # noqa
|
||||||
if de.compression != 4 and de.stored_shape[-1] in (3, 4):
|
if de.compression != 4 and de.stored_shape[-1] in (3, 4):
|
||||||
if de.stored_shape[-1] == 3:
|
if de.stored_shape[-1] == 3:
|
||||||
# BGR -> RGB
|
# BGR -> RGB
|
||||||
data = data[..., ::-1]
|
data = data[..., ::-1] # noqa
|
||||||
else:
|
else:
|
||||||
# BGRA -> RGBA
|
# BGRA -> RGBA
|
||||||
tmp = data[..., 0].copy()
|
tmp = data[..., 0].copy()
|
||||||
@@ -112,7 +116,7 @@ def data(self, raw=False, resize=True, order=0):
|
|||||||
|
|
||||||
# use repeat if possible
|
# use repeat if possible
|
||||||
if order == 0 and all(isinstance(f, int) for f in factors):
|
if order == 0 and all(isinstance(f, int) for f in factors):
|
||||||
data = repeat_nd(data, factors).copy()
|
data = repeat_nd(data, factors).copy() # noqa
|
||||||
data.shape = de.shape
|
data.shape = de.shape
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@@ -133,11 +137,11 @@ def data(self, raw=False, resize=True, order=0):
|
|||||||
if shape[-1] in (3, 4) and factors[-1] == 1.0:
|
if shape[-1] in (3, 4) and factors[-1] == 1.0:
|
||||||
factors = factors[:-1]
|
factors = factors[:-1]
|
||||||
old = data
|
old = data
|
||||||
data = np.empty(de.shape, de.dtype[-2:])
|
data = np.empty(de.shape, de.dtype[-2:]) # noqa
|
||||||
for i in range(shape[-1]):
|
for i in range(shape[-1]):
|
||||||
data[..., i] = zoom(old[..., i], zoom=factors, order=order)
|
data[..., i] = zoom(old[..., i], zoom=factors, order=order)
|
||||||
else:
|
else:
|
||||||
data = zoom(data, zoom=factors, order=order)
|
data = zoom(data, zoom=factors, order=order) # noqa
|
||||||
|
|
||||||
data.shape = de.shape
|
data.shape = de.shape
|
||||||
return data
|
return data
|
||||||
@@ -152,10 +156,10 @@ class Reader(AbstractReader, ABC):
|
|||||||
do_not_pickle = 'reader', 'filedict'
|
do_not_pickle = 'reader', 'filedict'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _can_open(path):
|
def _can_open(path: Path) -> bool:
|
||||||
return isinstance(path, Path) and path.suffix == '.czi'
|
return isinstance(path, Path) and path.suffix == '.czi'
|
||||||
|
|
||||||
def open(self):
|
def open(self) -> None:
|
||||||
self.reader = czifile.CziFile(self.path)
|
self.reader = czifile.CziFile(self.path)
|
||||||
filedict = {}
|
filedict = {}
|
||||||
for directory_entry in self.reader.filtered_subblock_directory:
|
for directory_entry in self.reader.filtered_subblock_directory:
|
||||||
@@ -170,10 +174,10 @@ class Reader(AbstractReader, ABC):
|
|||||||
filedict[c, z, t] = [directory_entry]
|
filedict[c, z, t] = [directory_entry]
|
||||||
self.filedict = filedict # noqa
|
self.filedict = filedict # noqa
|
||||||
|
|
||||||
def close(self):
|
def close(self) -> None:
|
||||||
self.reader.close()
|
self.reader.close()
|
||||||
|
|
||||||
def get_ome(self):
|
def get_ome(self) -> OME:
|
||||||
xml = self.reader.metadata()
|
xml = self.reader.metadata()
|
||||||
attachments = {i.attachment_entry.name: i.attachment_entry.data_segment()
|
attachments = {i.attachment_entry.name: i.attachment_entry.data_segment()
|
||||||
for i in self.reader.attachments()}
|
for i in self.reader.attachments()}
|
||||||
@@ -190,14 +194,14 @@ class Reader(AbstractReader, ABC):
|
|||||||
elif version == '1.2':
|
elif version == '1.2':
|
||||||
return self.ome_12(tree, attachments)
|
return self.ome_12(tree, attachments)
|
||||||
|
|
||||||
def ome_12(self, tree, attachments):
|
def ome_12(self, tree: etree, attachments: dict[str, Any]) -> OME:
|
||||||
def text(item, default=""):
|
def text(item: Optional[Element], default: str = "") -> str:
|
||||||
return default if item is None else item.text
|
return default if item is None else item.text
|
||||||
|
|
||||||
def def_list(item):
|
def def_list(item: Any) -> list[Any]:
|
||||||
return [] if item is None else item
|
return [] if item is None else item
|
||||||
|
|
||||||
ome = model.OME()
|
ome = OME()
|
||||||
|
|
||||||
metadata = tree.find('Metadata')
|
metadata = tree.find('Metadata')
|
||||||
|
|
||||||
@@ -235,7 +239,7 @@ class Reader(AbstractReader, ABC):
|
|||||||
try:
|
try:
|
||||||
nominal_magnification = float(re.findall(r'\d+(?:[,.]\d*)?',
|
nominal_magnification = float(re.findall(r'\d+(?:[,.]\d*)?',
|
||||||
tubelens.attrib['Name'])[0].replace(',', '.'))
|
tubelens.attrib['Name'])[0].replace(',', '.'))
|
||||||
except Exception:
|
except Exception: # noqa
|
||||||
nominal_magnification = 1.0
|
nominal_magnification = 1.0
|
||||||
|
|
||||||
ome.instruments[0].objectives.append(
|
ome.instruments[0].objectives.append(
|
||||||
@@ -376,14 +380,14 @@ class Reader(AbstractReader, ABC):
|
|||||||
idx += 1
|
idx += 1
|
||||||
return ome
|
return ome
|
||||||
|
|
||||||
def ome_10(self, tree, attachments):
|
def ome_10(self, tree: etree, attachments: dict[str, Any]) -> OME:
|
||||||
def text(item, default=""):
|
def text(item: Optional[Element], default: str = "") -> str:
|
||||||
return default if item is None else item.text
|
return default if item is None else item.text
|
||||||
|
|
||||||
def def_list(item):
|
def def_list(item: Any) -> list[Any]:
|
||||||
return [] if item is None else item
|
return [] if item is None else item
|
||||||
|
|
||||||
ome = model.OME()
|
ome = OME()
|
||||||
|
|
||||||
metadata = tree.find('Metadata')
|
metadata = tree.find('Metadata')
|
||||||
|
|
||||||
@@ -580,7 +584,7 @@ class Reader(AbstractReader, ABC):
|
|||||||
idx += 1
|
idx += 1
|
||||||
return ome
|
return ome
|
||||||
|
|
||||||
def __frame__(self, c=0, z=0, t=0):
|
def __frame__(self, c: int = 0, z: int = 0, t: int = 0) -> np.ndarray:
|
||||||
f = np.zeros(self.base.shape['yx'], self.dtype)
|
f = np.zeros(self.base.shape['yx'], self.dtype)
|
||||||
if (c, z, t) in self.filedict:
|
if (c, z, t) in self.filedict:
|
||||||
directory_entries = self.filedict[c, z, t]
|
directory_entries = self.filedict[c, z, t]
|
||||||
@@ -598,5 +602,5 @@ class Reader(AbstractReader, ABC):
|
|||||||
return f
|
return f
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_index(directory_entry, start):
|
def get_index(directory_entry: czifile.DirectoryEntryDV, start: tuple[int]) -> list[tuple[int, int]]:
|
||||||
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)]
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ def lazy_property(function, field, *arg_fields):
|
|||||||
|
|
||||||
class Plane(model.Plane):
|
class Plane(model.Plane):
|
||||||
""" Lazily retrieve delta_t from metadata """
|
""" Lazily retrieve delta_t from metadata """
|
||||||
def __init__(self, t0, file, **kwargs):
|
def __init__(self, t0, file, **kwargs): # noqa
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
# setting fields here because they would be removed by ome_types/pydantic after class definition
|
# setting fields here because they would be removed by ome_types/pydantic after class definition
|
||||||
setattr(self.__class__, 'delta_t', lazy_property(self.get_delta_t, 'delta_t', 't0', 'file'))
|
setattr(self.__class__, 'delta_t', lazy_property(self.get_delta_t, 'delta_t', 't0', 'file'))
|
||||||
setattr(self.__class__, 'delta_t_quantity', _quantity_property('delta_t'))
|
setattr(self.__class__, 'delta_t_quantity', _quantity_property('delta_t'))
|
||||||
self.__dict__['t0'] = t0
|
self.__dict__['t0'] = t0 # noqa
|
||||||
self.__dict__['file'] = file
|
self.__dict__['file'] = file # noqa
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_delta_t(t0, file):
|
def get_delta_t(t0, file):
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "ndbioimage"
|
name = "ndbioimage"
|
||||||
version = "2024.4.4"
|
version = "2024.4.5"
|
||||||
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 = ["W. Pomp <w.pomp@nki.nl>"]
|
||||||
license = "GPLv3"
|
license = "GPLv3"
|
||||||
@@ -24,7 +24,11 @@ lxml = "*"
|
|||||||
pyyaml = "*"
|
pyyaml = "*"
|
||||||
parfor = ">=2024.3.0"
|
parfor = ">=2024.3.0"
|
||||||
JPype1 = "*"
|
JPype1 = "*"
|
||||||
SimpleITK-SimpleElastix = "*"
|
SimpleITK-SimpleElastix = [
|
||||||
|
{ version = "*", python = "<3.12" },
|
||||||
|
{ version = "*", python = ">=3.12", markers = "sys_platform != 'darwin'" },
|
||||||
|
{ version = "*", python = ">=3.12", markers = "platform_machine == 'aarch64'" },
|
||||||
|
]
|
||||||
scikit-image = "*"
|
scikit-image = "*"
|
||||||
imagecodecs = "*"
|
imagecodecs = "*"
|
||||||
xsdata = "^23" # until pydantic is up-to-date
|
xsdata = "^23" # until pydantic is up-to-date
|
||||||
@@ -39,6 +43,9 @@ ndbioimage = "ndbioimage:main"
|
|||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
filterwarnings = ["ignore:::(colorcet)"]
|
filterwarnings = ["ignore:::(colorcet)"]
|
||||||
|
|
||||||
|
[tool.isort]
|
||||||
|
line_length = 119
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|||||||
Reference in New Issue
Block a user