- add doc strings
This commit is contained in:
+366
-99
@@ -47,9 +47,15 @@ class Imread:
|
||||
TODO: argmax, argmin, nanmax, nanmin, nanmean, nansum, nanstd, nanvar, std, var
|
||||
"""
|
||||
@property
|
||||
def reader_name(self) -> builtins.str: ...
|
||||
def reader_name(self) -> builtins.str:
|
||||
r"""
|
||||
the name of the reader used to open the file
|
||||
"""
|
||||
@property
|
||||
def transform(self) -> None: ...
|
||||
def transform(self) -> None:
|
||||
r"""
|
||||
get the transformation matrix (not yet implemented)
|
||||
"""
|
||||
@property
|
||||
def path(self) -> pathlib.Path:
|
||||
r"""
|
||||
@@ -71,7 +77,10 @@ class Imread:
|
||||
the shape of the view
|
||||
"""
|
||||
@property
|
||||
def slice(self) -> builtins.list[builtins.str]: ...
|
||||
def slice(self) -> builtins.list[builtins.str]:
|
||||
r"""
|
||||
the current slice applied to the view
|
||||
"""
|
||||
@property
|
||||
def size(self) -> builtins.int:
|
||||
r"""
|
||||
@@ -83,25 +92,40 @@ class Imread:
|
||||
the number of dimensions in the view
|
||||
"""
|
||||
@property
|
||||
def T(self) -> Imread: ...
|
||||
def T(self) -> Imread:
|
||||
r"""
|
||||
transposed view (alias for transpose(None))
|
||||
"""
|
||||
@property
|
||||
def dtype(self) -> numpy.dtype: ...
|
||||
def dtype(self) -> numpy.dtype:
|
||||
r"""
|
||||
the numpy dtype of the view
|
||||
"""
|
||||
@property
|
||||
def z_stack(self) -> builtins.bool: ...
|
||||
def z_stack(self) -> builtins.bool:
|
||||
r"""
|
||||
whether the view contains a z-stack (more than one z slice)
|
||||
"""
|
||||
@property
|
||||
def zstack(self) -> builtins.bool:
|
||||
r"""
|
||||
backwards compatibility
|
||||
"""
|
||||
@property
|
||||
def time_series(self) -> builtins.bool: ...
|
||||
def time_series(self) -> builtins.bool:
|
||||
r"""
|
||||
whether the view contains a time series (more than one time point)
|
||||
"""
|
||||
@property
|
||||
def timeseries(self) -> builtins.bool:
|
||||
r"""
|
||||
backwards compatibility
|
||||
"""
|
||||
@property
|
||||
def pixel_size(self) -> typing.Optional[builtins.float]: ...
|
||||
def pixel_size(self) -> typing.Optional[builtins.float]:
|
||||
r"""
|
||||
the pixel size in micrometers
|
||||
"""
|
||||
@property
|
||||
def pxsize_um(self) -> typing.Optional[builtins.float]:
|
||||
r"""
|
||||
@@ -113,9 +137,15 @@ class Imread:
|
||||
backwards compatibility
|
||||
"""
|
||||
@property
|
||||
def delta_z(self) -> typing.Optional[builtins.float]: ...
|
||||
def delta_z(self) -> typing.Optional[builtins.float]:
|
||||
r"""
|
||||
the z-step size in micrometers
|
||||
"""
|
||||
@property
|
||||
def time_interval(self) -> typing.Optional[builtins.float]: ...
|
||||
def time_interval(self) -> typing.Optional[builtins.float]:
|
||||
r"""
|
||||
the time interval between frames in seconds
|
||||
"""
|
||||
@property
|
||||
def timeinterval(self) -> typing.Optional[builtins.float]:
|
||||
r"""
|
||||
@@ -127,15 +157,24 @@ class Imread:
|
||||
backwards compatibility
|
||||
"""
|
||||
@property
|
||||
def objective_name(self) -> typing.Optional[builtins.str]: ...
|
||||
def objective_name(self) -> typing.Optional[builtins.str]:
|
||||
r"""
|
||||
the name of the objective
|
||||
"""
|
||||
@property
|
||||
def magnification(self) -> typing.Optional[builtins.float]: ...
|
||||
def magnification(self) -> typing.Optional[builtins.float]:
|
||||
r"""
|
||||
the total magnification (objective × tube lens)
|
||||
"""
|
||||
@property
|
||||
def tube_lens_name(self) -> typing.Optional[builtins.str]: ...
|
||||
def tube_lens_name(self) -> typing.Optional[builtins.str]:
|
||||
r"""
|
||||
the name of the tube lens
|
||||
"""
|
||||
def __new__(
|
||||
cls,
|
||||
path: str | pathlib.Path | Imread | bytes,
|
||||
dtype: numpy.typing.DTypeLike = None,
|
||||
dtype: typing.Optional[numpy.typing.DTypeLike] = None,
|
||||
axes: builtins.str = "cztyx",
|
||||
reader: typing.Optional[builtins.str] = None,
|
||||
) -> Imread:
|
||||
@@ -145,20 +184,29 @@ class Imread:
|
||||
@staticmethod
|
||||
def get_positions(
|
||||
path: str | pathlib.Path | Imread | bytes,
|
||||
) -> builtins.set[builtins.int]: ...
|
||||
) -> builtins.set[builtins.int]:
|
||||
r"""
|
||||
get all available positions (series) in the file
|
||||
"""
|
||||
@staticmethod
|
||||
def kill_vm() -> None:
|
||||
r"""
|
||||
only remains for backwards compatibility
|
||||
"""
|
||||
def reshape(self, order: builtins.str, copy: builtins.bool) -> typing.Any: ...
|
||||
def reshape(self, order: builtins.str, copy: builtins.bool) -> typing.Any:
|
||||
r"""
|
||||
reshape the view with a new axis order
|
||||
"""
|
||||
def with_transform(
|
||||
self,
|
||||
channels: builtins.bool = True,
|
||||
drift: builtins.bool = False,
|
||||
file: typing.Optional[typing.Any] = None,
|
||||
bead_files: typing.Optional[typing.Any] = None,
|
||||
) -> Imread: ...
|
||||
) -> Imread:
|
||||
r"""
|
||||
return a new view with transformations applied (channel alignment, drift correction)
|
||||
"""
|
||||
def squeeze(self) -> numpy.ndarray | int | float: ...
|
||||
def close(self) -> None:
|
||||
r"""
|
||||
@@ -178,69 +226,219 @@ class Imread:
|
||||
"""
|
||||
def __array__(
|
||||
self,
|
||||
dtype: numpy.typing.DTypeLike = None,
|
||||
dtype: typing.Optional[numpy.typing.DTypeLike] = None,
|
||||
copy: typing.Optional[builtins.bool] = None,
|
||||
) -> typing.Any: ...
|
||||
def __contains__(self, _item: typing.Any) -> builtins.bool: ...
|
||||
def __lt__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __le__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __eq__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __ne__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __gt__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __ge__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __add__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __radd__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __sub__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __rsub__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __mul__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __rmul__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __truediv__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __rtruediv__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __floordiv__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __rfloordiv__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __mod__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __rmod__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __matmul__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __rmatmul__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __and__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __rand__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __or__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __ror__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __xor__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __rxor__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __lshift__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __rlshift__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __rshift__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __rrshift__(self, other: typing.Any) -> typing.Any: ...
|
||||
def __neg__(self) -> typing.Any: ...
|
||||
def __pos__(self) -> typing.Any: ...
|
||||
def __abs__(self) -> typing.Any: ...
|
||||
def __invert__(self) -> typing.Any: ...
|
||||
def __enter__(self) -> Imread: ...
|
||||
) -> typing.Any:
|
||||
r"""
|
||||
convert to a numpy array, optionally with a different dtype
|
||||
"""
|
||||
def __contains__(self, _item: typing.Any) -> builtins.bool:
|
||||
r"""
|
||||
check if an item is contained in the view (not implemented)
|
||||
"""
|
||||
def __lt__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise less than comparison
|
||||
"""
|
||||
def __le__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise less than or equal comparison
|
||||
"""
|
||||
def __eq__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise equality comparison
|
||||
"""
|
||||
def __ne__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise not equal comparison
|
||||
"""
|
||||
def __gt__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise greater than comparison
|
||||
"""
|
||||
def __ge__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise greater than or equal comparison
|
||||
"""
|
||||
def __add__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise addition
|
||||
"""
|
||||
def __radd__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise addition (reflected)
|
||||
"""
|
||||
def __sub__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise subtraction
|
||||
"""
|
||||
def __rsub__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise subtraction (reflected)
|
||||
"""
|
||||
def __mul__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise multiplication
|
||||
"""
|
||||
def __rmul__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise multiplication (reflected)
|
||||
"""
|
||||
def __truediv__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise true division
|
||||
"""
|
||||
def __rtruediv__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise true division (reflected)
|
||||
"""
|
||||
def __floordiv__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise floor division
|
||||
"""
|
||||
def __rfloordiv__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise floor division (reflected)
|
||||
"""
|
||||
def __mod__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise modulo
|
||||
"""
|
||||
def __rmod__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise modulo (reflected)
|
||||
"""
|
||||
def __matmul__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise matrix multiplication
|
||||
"""
|
||||
def __rmatmul__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise matrix multiplication (reflected)
|
||||
"""
|
||||
def __and__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise bitwise AND
|
||||
"""
|
||||
def __rand__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise bitwise AND (reflected)
|
||||
"""
|
||||
def __or__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise bitwise OR
|
||||
"""
|
||||
def __ror__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise bitwise OR (reflected)
|
||||
"""
|
||||
def __xor__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise bitwise XOR
|
||||
"""
|
||||
def __rxor__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise bitwise XOR (reflected)
|
||||
"""
|
||||
def __lshift__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise left shift
|
||||
"""
|
||||
def __rlshift__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise left shift (reflected)
|
||||
"""
|
||||
def __rshift__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise right shift
|
||||
"""
|
||||
def __rrshift__(self, other: typing.Any) -> typing.Any:
|
||||
r"""
|
||||
element-wise right shift (reflected)
|
||||
"""
|
||||
def __neg__(self) -> typing.Any:
|
||||
r"""
|
||||
element-wise negation
|
||||
"""
|
||||
def __pos__(self) -> typing.Any:
|
||||
r"""
|
||||
element-wise positive
|
||||
"""
|
||||
def __abs__(self) -> typing.Any:
|
||||
r"""
|
||||
element-wise absolute value
|
||||
"""
|
||||
def __invert__(self) -> typing.Any:
|
||||
r"""
|
||||
element-wise bitwise inversion
|
||||
"""
|
||||
def __enter__(self) -> Imread:
|
||||
r"""
|
||||
context manager entry
|
||||
"""
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: typing.Optional[typing.Any] = None,
|
||||
exc_val: typing.Optional[typing.Any] = None,
|
||||
exc_tb: typing.Optional[typing.Any] = None,
|
||||
) -> None: ...
|
||||
def __getnewargs__(self) -> tuple[builtins.list[builtins.int]]: ...
|
||||
def __copy__(self) -> Imread: ...
|
||||
def __deepcopy__(self) -> Imread: ...
|
||||
def copy(self) -> Imread: ...
|
||||
def __iter__(self) -> Imread: ...
|
||||
def __next__(self) -> typing.Optional[typing.Any]: ...
|
||||
def __len__(self) -> builtins.int: ...
|
||||
def __repr__(self) -> builtins.str: ...
|
||||
def __str__(self) -> builtins.str: ...
|
||||
) -> None:
|
||||
r"""
|
||||
context manager exit
|
||||
"""
|
||||
def __getnewargs__(self) -> tuple[builtins.list[builtins.int]]:
|
||||
r"""
|
||||
arguments for pickling
|
||||
"""
|
||||
def __copy__(self) -> Imread:
|
||||
r"""
|
||||
shallow copy of the view
|
||||
"""
|
||||
def __deepcopy__(self) -> Imread:
|
||||
r"""
|
||||
deep copy of the view (same as shallow copy for this type)
|
||||
"""
|
||||
def copy(self) -> Imread:
|
||||
r"""
|
||||
create a copy of the view
|
||||
"""
|
||||
def __iter__(self) -> Imread:
|
||||
r"""
|
||||
iterate over the first axis
|
||||
"""
|
||||
def __next__(self) -> typing.Optional[typing.Any]:
|
||||
r"""
|
||||
get the next item in the iteration
|
||||
"""
|
||||
def __len__(self) -> builtins.int:
|
||||
r"""
|
||||
number of elements in the first axis
|
||||
"""
|
||||
def __repr__(self) -> builtins.str:
|
||||
r"""
|
||||
string representation with a summary of the image
|
||||
"""
|
||||
def __str__(self) -> builtins.str:
|
||||
r"""
|
||||
the file path as a string
|
||||
"""
|
||||
def get_frame(
|
||||
self, c: builtins.int, z: builtins.int, t: builtins.int
|
||||
) -> typing.Any:
|
||||
r"""
|
||||
retrieve a single frame at czt, sliced accordingly
|
||||
"""
|
||||
def flatten(self) -> typing.Any: ...
|
||||
def to_bytes(self) -> builtins.list[builtins.int]: ...
|
||||
def tobytes(self) -> builtins.list[builtins.int]: ...
|
||||
def flatten(self) -> typing.Any:
|
||||
r"""
|
||||
flatten the view into a 1D numpy array
|
||||
"""
|
||||
def to_bytes(self) -> builtins.list[builtins.int]:
|
||||
r"""
|
||||
convert the view to bytes
|
||||
"""
|
||||
def tobytes(self) -> builtins.list[builtins.int]:
|
||||
r"""
|
||||
convert the view to bytes (alias for to_bytes)
|
||||
"""
|
||||
def get_ax(self, axis: int | str) -> builtins.int:
|
||||
r"""
|
||||
find the position of an axis
|
||||
@@ -259,18 +457,32 @@ class Imread:
|
||||
r"""
|
||||
collect data into a numpy array
|
||||
"""
|
||||
def exposure_time(
|
||||
self, channel: builtins.int
|
||||
) -> typing.Optional[builtins.float]: ...
|
||||
def binning(self, channel: builtins.int) -> typing.Optional[builtins.int]: ...
|
||||
def exposure_time(self, channel: builtins.int) -> typing.Optional[builtins.float]:
|
||||
r"""
|
||||
the exposure time for a given channel in seconds
|
||||
"""
|
||||
def binning(self, channel: builtins.int) -> typing.Optional[builtins.int]:
|
||||
r"""
|
||||
the binning for a given channel
|
||||
"""
|
||||
def laser_wavelengths(
|
||||
self, channel: builtins.int
|
||||
) -> typing.Optional[builtins.float]: ...
|
||||
def laser_power(self, channel: builtins.int) -> typing.Optional[builtins.float]: ...
|
||||
def filter_set_name(
|
||||
self, channel: builtins.int
|
||||
) -> typing.Optional[builtins.str]: ...
|
||||
def gain(self, channel: builtins.int) -> typing.Optional[builtins.float]: ...
|
||||
) -> typing.Optional[builtins.float]:
|
||||
r"""
|
||||
the laser wavelength for a given channel in nanometers
|
||||
"""
|
||||
def laser_power(self, channel: builtins.int) -> typing.Optional[builtins.float]:
|
||||
r"""
|
||||
the laser power for a given channel as a fraction
|
||||
"""
|
||||
def filter_set_name(self, channel: builtins.int) -> typing.Optional[builtins.str]:
|
||||
r"""
|
||||
the name of the filter set for a given channel
|
||||
"""
|
||||
def gain(self, channel: builtins.int) -> typing.Optional[builtins.float]:
|
||||
r"""
|
||||
the detector gain for a given channel
|
||||
"""
|
||||
def summary(self) -> builtins.str:
|
||||
r"""
|
||||
gives a helpful summary of the recorded experiment
|
||||
@@ -297,7 +509,10 @@ class Imread:
|
||||
colors: typing.Optional[typing.Sequence[builtins.str]] = None,
|
||||
overwrite: builtins.bool = False,
|
||||
bar: builtins.bool = True,
|
||||
) -> None: ...
|
||||
) -> None:
|
||||
r"""
|
||||
save the view as a TIFF file
|
||||
"""
|
||||
def save_as_movie(
|
||||
self,
|
||||
file: builtins.str | os.PathLike | pathlib.Path,
|
||||
@@ -308,7 +523,10 @@ class Imread:
|
||||
overwrite: builtins.bool = False,
|
||||
register: builtins.bool = False,
|
||||
no_scaling: builtins.bool = False,
|
||||
) -> None: ...
|
||||
) -> None:
|
||||
r"""
|
||||
save the view as a movie file (MP4)
|
||||
"""
|
||||
def set_cache_size(self, size: builtins.int) -> None:
|
||||
r"""
|
||||
backwards compatibility
|
||||
@@ -328,7 +546,7 @@ class Imread:
|
||||
def max(
|
||||
self,
|
||||
axis: int | str = None,
|
||||
dtype: numpy.typing.DTypeLike = None,
|
||||
dtype: typing.Optional[numpy.typing.DTypeLike] = None,
|
||||
out: typing.Any = None,
|
||||
keepdims: bool = False,
|
||||
initial: int | float = None,
|
||||
@@ -340,7 +558,7 @@ class Imread:
|
||||
def min(
|
||||
self,
|
||||
axis: int | str = None,
|
||||
dtype: numpy.typing.DTypeLike = None,
|
||||
dtype: typing.Optional[numpy.typing.DTypeLike] = None,
|
||||
out: typing.Any = None,
|
||||
keepdims: bool = False,
|
||||
initial: int | float = None,
|
||||
@@ -352,7 +570,7 @@ class Imread:
|
||||
def mean(
|
||||
self,
|
||||
axis: int | str = None,
|
||||
dtype: numpy.typing.DTypeLike = None,
|
||||
dtype: typing.Optional[numpy.typing.DTypeLike] = None,
|
||||
out: typing.Any = None,
|
||||
keepdims: bool = False,
|
||||
) -> Imread | numpy.typing.NDArray | int | float:
|
||||
@@ -362,7 +580,7 @@ class Imread:
|
||||
def sum(
|
||||
self,
|
||||
axis: int | str = None,
|
||||
dtype: numpy.typing.DTypeLike = None,
|
||||
dtype: typing.Optional[numpy.typing.DTypeLike] = None,
|
||||
out: typing.Any = None,
|
||||
keepdims: bool = False,
|
||||
initial: int | float = None,
|
||||
@@ -373,18 +591,39 @@ class Imread:
|
||||
"""
|
||||
|
||||
class Shape:
|
||||
r"""
|
||||
represents the shape of an image with named dimensions (c, z, t, y, x)
|
||||
"""
|
||||
@property
|
||||
def c(self) -> builtins.int: ...
|
||||
def c(self) -> builtins.int:
|
||||
r"""
|
||||
the number of channels
|
||||
"""
|
||||
@property
|
||||
def z(self) -> builtins.int: ...
|
||||
def z(self) -> builtins.int:
|
||||
r"""
|
||||
the number of z slices
|
||||
"""
|
||||
@property
|
||||
def t(self) -> builtins.int: ...
|
||||
def t(self) -> builtins.int:
|
||||
r"""
|
||||
the number of time points
|
||||
"""
|
||||
@property
|
||||
def y(self) -> builtins.int: ...
|
||||
def y(self) -> builtins.int:
|
||||
r"""
|
||||
the number of pixels along y
|
||||
"""
|
||||
@property
|
||||
def x(self) -> builtins.int: ...
|
||||
def x(self) -> builtins.int:
|
||||
r"""
|
||||
the number of pixels along x
|
||||
"""
|
||||
@property
|
||||
def axes(self) -> builtins.str: ...
|
||||
def axes(self) -> builtins.str:
|
||||
r"""
|
||||
the axis order as a string (e.g., "CZTYX")
|
||||
"""
|
||||
def __new__(
|
||||
cls,
|
||||
order: builtins.str,
|
||||
@@ -393,9 +632,18 @@ class Shape:
|
||||
t: builtins.int = 1,
|
||||
y: builtins.int = 1,
|
||||
x: builtins.int = 1,
|
||||
) -> Shape: ...
|
||||
def __str__(self) -> builtins.str: ...
|
||||
def __repr__(self) -> builtins.str: ...
|
||||
) -> Shape:
|
||||
r"""
|
||||
create a new shape with the given dimensions
|
||||
"""
|
||||
def __str__(self) -> builtins.str:
|
||||
r"""
|
||||
string representation
|
||||
"""
|
||||
def __repr__(self) -> builtins.str:
|
||||
r"""
|
||||
detailed representation for debugging
|
||||
"""
|
||||
def __getnewargs__(
|
||||
self,
|
||||
) -> tuple[
|
||||
@@ -405,12 +653,24 @@ class Shape:
|
||||
builtins.int,
|
||||
builtins.int,
|
||||
builtins.int,
|
||||
]: ...
|
||||
]:
|
||||
r"""
|
||||
arguments for pickling
|
||||
"""
|
||||
def __getitem__(
|
||||
self, idx: str | int | None | Ellipsis | slice | list[int] | tuple[int]
|
||||
) -> typing.Optional[int | list[int]]: ...
|
||||
def __len__(self) -> builtins.int: ...
|
||||
def to_list(self) -> builtins.list[builtins.int]: ...
|
||||
) -> typing.Optional[int | list[int]]:
|
||||
r"""
|
||||
get dimension size by index or axis name
|
||||
"""
|
||||
def __len__(self) -> builtins.int:
|
||||
r"""
|
||||
number of dimensions in the shape
|
||||
"""
|
||||
def to_list(self) -> builtins.list[builtins.int]:
|
||||
r"""
|
||||
convert shape to a list of dimension sizes in order
|
||||
"""
|
||||
|
||||
def batch_to_tiff(
|
||||
files_in: typing.Sequence[builtins.str | os.PathLike | pathlib.Path],
|
||||
@@ -422,5 +682,12 @@ def batch_to_tiff(
|
||||
overwrite: builtins.bool = False,
|
||||
bar: builtins.bool = True,
|
||||
message: typing.Optional[builtins.str] = None,
|
||||
) -> None: ...
|
||||
def main() -> None: ...
|
||||
) -> None:
|
||||
r"""
|
||||
batch convert multiple image files to TIFF format
|
||||
"""
|
||||
|
||||
def main() -> None:
|
||||
r"""
|
||||
main entry point for the command-line interface
|
||||
"""
|
||||
|
||||
+22
@@ -32,12 +32,18 @@ pub trait Ax {
|
||||
)]
|
||||
#[strum(ascii_case_insensitive)]
|
||||
pub enum Axis {
|
||||
/// channel
|
||||
C,
|
||||
/// z slice
|
||||
Z,
|
||||
/// time
|
||||
T,
|
||||
/// y coordinate
|
||||
Y,
|
||||
/// x coordinate
|
||||
X,
|
||||
#[strum(serialize = "N")]
|
||||
/// a new axis created by slicing, can be operated on but has no reader data
|
||||
New,
|
||||
}
|
||||
|
||||
@@ -114,10 +120,15 @@ impl Ax for usize {
|
||||
Clone, Debug, Serialize, Deserialize, EnumString, AsRefStr, Display, PartialEq, Eq, Hash,
|
||||
)]
|
||||
#[strum(ascii_case_insensitive)]
|
||||
/// an operation to reduce an axis
|
||||
pub enum Operation {
|
||||
/// take the max along the axis
|
||||
Max,
|
||||
/// take the min along the axis
|
||||
Min,
|
||||
/// take the sum along the axis
|
||||
Sum,
|
||||
/// take the mean along the axis
|
||||
Mean,
|
||||
}
|
||||
|
||||
@@ -229,6 +240,7 @@ impl IntoIterator for &Slice {
|
||||
}
|
||||
}
|
||||
|
||||
/// the size of a view along the axes it contains
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Shape {
|
||||
/// size c (# channels)
|
||||
@@ -241,6 +253,7 @@ pub struct Shape {
|
||||
pub y: usize,
|
||||
/// size x (horizontal)
|
||||
pub x: usize,
|
||||
/// the axes that are present in the view, in order
|
||||
pub order: Vec<Axis>,
|
||||
}
|
||||
|
||||
@@ -330,6 +343,7 @@ impl From<Shape> for HashMap<Axis, usize> {
|
||||
}
|
||||
}
|
||||
|
||||
/// iterator over the sizes along the axes of a [`Shape`]
|
||||
pub struct ShapeIter {
|
||||
shape: Shape,
|
||||
index: usize,
|
||||
@@ -344,6 +358,7 @@ impl Iterator for ShapeIter {
|
||||
}
|
||||
}
|
||||
|
||||
/// iterator over the sizes along the axes of a borrowed [`Shape`]
|
||||
pub struct ShapeIterBorrow<'a> {
|
||||
shape: &'a Shape,
|
||||
index: usize,
|
||||
@@ -371,6 +386,7 @@ impl IntoIterator for Shape {
|
||||
}
|
||||
|
||||
impl Shape {
|
||||
/// create an empty shape with all sizes set to 1 and no axes
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
c: 1,
|
||||
@@ -382,6 +398,7 @@ impl Shape {
|
||||
}
|
||||
}
|
||||
|
||||
/// iterate over the sizes along the axes, in order
|
||||
pub fn iter(&self) -> ShapeIterBorrow<'_> {
|
||||
ShapeIterBorrow {
|
||||
shape: self,
|
||||
@@ -389,18 +406,22 @@ impl Shape {
|
||||
}
|
||||
}
|
||||
|
||||
/// the number of axes in the shape
|
||||
pub fn len(&self) -> usize {
|
||||
self.order.len()
|
||||
}
|
||||
|
||||
/// whether the shape has no axes
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.order.is_empty()
|
||||
}
|
||||
|
||||
/// the sizes along the axes, in order
|
||||
pub fn to_vec(&self) -> Vec<usize> {
|
||||
self.order.iter().map(|axis| self[axis]).collect()
|
||||
}
|
||||
|
||||
/// the sizes along the axes as a map from axis to size
|
||||
pub fn to_hashmap(&self) -> HashMap<Axis, usize> {
|
||||
let mut map = HashMap::new();
|
||||
for axis in self.order.iter() {
|
||||
@@ -409,6 +430,7 @@ impl Shape {
|
||||
map
|
||||
}
|
||||
|
||||
/// set the size of an axis
|
||||
pub fn set_axis(&mut self, axis: &Axis, value: usize) {
|
||||
match axis {
|
||||
Axis::C => self.c = value,
|
||||
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
use crate::axes::{Axis, Operation};
|
||||
use indexmap::{Equivalent, IndexMap};
|
||||
use ndarray::{ArrayD, SliceInfoElem};
|
||||
use std::any::Any;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
|
||||
use crate::readers::Frame;
|
||||
|
||||
/// maximum number of frames held in the cache
|
||||
const DEFAULT_FRAME_CACHE_SIZE: usize = 128;
|
||||
|
||||
/// maximum number of materialized arrays held in the cache
|
||||
const DEFAULT_ARRAY_CACHE_SIZE: usize = 32;
|
||||
|
||||
/// identity of the reader a frame was read from
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct ReaderKey {
|
||||
pub(crate) name: String,
|
||||
pub(crate) path: PathBuf,
|
||||
pub(crate) series: usize,
|
||||
pub(crate) position: usize,
|
||||
}
|
||||
|
||||
/// borrowed view of [`ReaderKey`] for cache lookups without allocation.
|
||||
/// hashes byte-identically to [`ReaderKey`] (str/String and Path/PathBuf hash
|
||||
/// the same), so it only matches the same reader identity.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) struct ReaderKeyRef<'a> {
|
||||
pub(crate) name: &'a str,
|
||||
pub(crate) path: &'a Path,
|
||||
pub(crate) series: usize,
|
||||
pub(crate) position: usize,
|
||||
}
|
||||
|
||||
impl Hash for ReaderKeyRef<'_> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.name.hash(state);
|
||||
self.path.hash(state);
|
||||
self.series.hash(state);
|
||||
self.position.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Equivalent<ReaderKey> for ReaderKeyRef<'_> {
|
||||
fn equivalent(&self, key: &ReaderKey) -> bool {
|
||||
self.name == key.name
|
||||
&& self.path == key.path.as_path()
|
||||
&& self.series == key.series
|
||||
&& self.position == key.position
|
||||
}
|
||||
}
|
||||
|
||||
impl ReaderKeyRef<'_> {
|
||||
pub(crate) fn to_owned(&self) -> ReaderKey {
|
||||
ReaderKey {
|
||||
name: self.name.to_string(),
|
||||
path: self.path.to_path_buf(),
|
||||
series: self.series,
|
||||
position: self.position,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type FrameKey = (ReaderKey, usize, usize, usize);
|
||||
|
||||
/// borrowed view of [`FrameKey`] for cache lookups without allocation.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) struct FrameKeyRef<'a> {
|
||||
pub(crate) reader: ReaderKeyRef<'a>,
|
||||
pub(crate) c: usize,
|
||||
pub(crate) z: usize,
|
||||
pub(crate) t: usize,
|
||||
}
|
||||
|
||||
impl Hash for FrameKeyRef<'_> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.reader.hash(state);
|
||||
self.c.hash(state);
|
||||
self.z.hash(state);
|
||||
self.t.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Equivalent<FrameKey> for FrameKeyRef<'_> {
|
||||
fn equivalent(&self, key: &FrameKey) -> bool {
|
||||
let (rk, c, z, t) = key;
|
||||
self.reader.equivalent(rk) && self.c == *c && self.z == *z && self.t == *t
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameKeyRef<'_> {
|
||||
pub(crate) fn to_owned(&self) -> FrameKey {
|
||||
(self.reader.to_owned(), self.c, self.z, self.t)
|
||||
}
|
||||
}
|
||||
|
||||
/// identity of a materialized array in the process-wide cache
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct ArrayKey {
|
||||
pub(crate) reader: ReaderKey,
|
||||
pub(crate) dtype: &'static str,
|
||||
pub(crate) slice: Vec<SliceInfoElem>,
|
||||
pub(crate) axes: Vec<Axis>,
|
||||
pub(crate) operations: Vec<(Axis, Operation)>,
|
||||
}
|
||||
|
||||
/// borrowed view of [`ArrayKey`] for cache lookups without allocation.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) struct ArrayKeyRef<'a> {
|
||||
pub(crate) reader: ReaderKeyRef<'a>,
|
||||
pub(crate) dtype: &'static str,
|
||||
pub(crate) slice: &'a [SliceInfoElem],
|
||||
pub(crate) axes: &'a [Axis],
|
||||
pub(crate) operations: &'a IndexMap<Axis, Operation>,
|
||||
}
|
||||
|
||||
impl Hash for ArrayKeyRef<'_> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.reader.hash(state);
|
||||
self.dtype.hash(state);
|
||||
self.slice.hash(state);
|
||||
self.axes.hash(state);
|
||||
self.operations.len().hash(state);
|
||||
for (ax, op) in self.operations.iter() {
|
||||
ax.hash(state);
|
||||
op.hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Equivalent<ArrayKey> for ArrayKeyRef<'_> {
|
||||
fn equivalent(&self, key: &ArrayKey) -> bool {
|
||||
self.reader.equivalent(&key.reader)
|
||||
&& self.dtype == key.dtype
|
||||
&& self.slice == key.slice
|
||||
&& self.axes == key.axes
|
||||
&& self.operations.len() == key.operations.len()
|
||||
&& self
|
||||
.operations
|
||||
.iter()
|
||||
.zip(&key.operations)
|
||||
.all(|((ax, op), (key_ax, key_op))| ax == key_ax && op == key_op)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArrayKeyRef<'_> {
|
||||
pub(crate) fn to_owned(&self) -> ArrayKey {
|
||||
ArrayKey {
|
||||
reader: self.reader.to_owned(),
|
||||
dtype: self.dtype,
|
||||
slice: self.slice.to_vec(),
|
||||
axes: self.axes.to_vec(),
|
||||
operations: self
|
||||
.operations
|
||||
.iter()
|
||||
.map(|(ax, op)| (*ax, op.clone()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// process-wide LRU cache of frames, shared between all views and threads
|
||||
static GLOBAL_FRAME_CACHE: OnceLock<FrameCache> = OnceLock::new();
|
||||
|
||||
/// thread-safe LRU cache of frames read from the underlying reader
|
||||
pub(crate) struct FrameCache {
|
||||
inner: Mutex<FrameCacheInner>,
|
||||
}
|
||||
|
||||
struct FrameCacheInner {
|
||||
map: IndexMap<FrameKey, Arc<Frame>>,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl Default for FrameCache {
|
||||
fn default() -> Self {
|
||||
Self::new(DEFAULT_FRAME_CACHE_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameCache {
|
||||
pub(crate) fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
inner: Mutex::new(FrameCacheInner {
|
||||
map: IndexMap::with_capacity(capacity),
|
||||
capacity,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn global() -> &'static FrameCache {
|
||||
GLOBAL_FRAME_CACHE.get_or_init(FrameCache::default)
|
||||
}
|
||||
|
||||
pub(crate) fn capacity(&self) -> usize {
|
||||
self.inner.lock().unwrap().capacity
|
||||
}
|
||||
|
||||
pub(crate) fn set_capacity(&self, capacity: usize) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.capacity = capacity;
|
||||
while inner.map.len() > inner.capacity {
|
||||
inner.map.shift_remove_index(0);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get<Q>(&self, key: &Q) -> Option<Arc<Frame>>
|
||||
where
|
||||
Q: ?Sized + Hash + Equivalent<FrameKey>,
|
||||
{
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
if let Some(idx) = inner.map.get_index_of(key) {
|
||||
let (key, frame) = inner.map.shift_remove_index(idx).unwrap();
|
||||
inner.map.insert(key, frame.clone());
|
||||
Some(frame)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn insert(&self, key: FrameKey, frame: Arc<Frame>) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.map.insert(key, frame);
|
||||
while inner.map.len() > inner.capacity {
|
||||
inner.map.shift_remove_index(0);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.inner.lock().unwrap().map.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for FrameCache {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("FrameCache").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
/// process-wide LRU cache of materialized arrays, shared between all views and threads
|
||||
static GLOBAL_ARRAY_CACHE: OnceLock<ArrayCache> = OnceLock::new();
|
||||
|
||||
/// thread-safe LRU cache of materialized arrays produced by `as_array_dyn`
|
||||
pub(crate) struct ArrayCache {
|
||||
inner: Mutex<ArrayCacheInner>,
|
||||
}
|
||||
|
||||
struct ArrayCacheInner {
|
||||
map: IndexMap<ArrayKey, Arc<dyn Any + Send + Sync>>,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl Default for ArrayCache {
|
||||
fn default() -> Self {
|
||||
Self::new(DEFAULT_ARRAY_CACHE_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArrayCache {
|
||||
pub(crate) fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
inner: Mutex::new(ArrayCacheInner {
|
||||
map: IndexMap::with_capacity(capacity),
|
||||
capacity,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn global() -> &'static ArrayCache {
|
||||
GLOBAL_ARRAY_CACHE.get_or_init(ArrayCache::default)
|
||||
}
|
||||
|
||||
pub(crate) fn capacity(&self) -> usize {
|
||||
self.inner.lock().unwrap().capacity
|
||||
}
|
||||
|
||||
pub(crate) fn set_capacity(&self, capacity: usize) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.capacity = capacity;
|
||||
while inner.map.len() > inner.capacity {
|
||||
inner.map.shift_remove_index(0);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get<T, Q>(&self, key: &Q) -> Option<Arc<ArrayD<T>>>
|
||||
where
|
||||
Q: ?Sized + Hash + Equivalent<ArrayKey>,
|
||||
T: Any + Send + Sync,
|
||||
{
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
if let Some(idx) = inner.map.get_index_of(key) {
|
||||
let (key, array) = inner.map.shift_remove_index(idx).unwrap();
|
||||
inner.map.insert(key, array.clone());
|
||||
array.downcast::<ArrayD<T>>().ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn insert<T: Any + Send + Sync>(&self, key: ArrayKey, array: ArrayD<T>) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.map.insert(key, Arc::new(array));
|
||||
while inner.map.len() > inner.capacity {
|
||||
inner.map.shift_remove_index(0);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.inner.lock().unwrap().map.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ArrayCache {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ArrayCache").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ use phf::phf_map;
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// a static map from color names (like `"red"` or single letters like `"r"`) to hex strings
|
||||
pub static COLORS: phf::Map<&'static str, &'static str> = phf_map! {
|
||||
"b" => "#0000FF",
|
||||
"g" => "#008000",
|
||||
@@ -162,6 +163,7 @@ pub static COLORS: phf::Map<&'static str, &'static str> = phf_map! {
|
||||
"yellowgreen" => "#9ACD32",
|
||||
};
|
||||
|
||||
/// a color with red, green and blue components, parsed from a hex string (`#RRGGBB`) or a name in [`COLORS`]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Color {
|
||||
r: u8,
|
||||
@@ -196,6 +198,7 @@ impl Display for Color {
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// the color as an RGB vector `[r, g, b]`
|
||||
pub fn to_rgb(&self) -> Vec<u8> {
|
||||
vec![self.r, self.g, self.b]
|
||||
}
|
||||
|
||||
@@ -1,121 +1,171 @@
|
||||
use strum::IntoStaticStr;
|
||||
use thiserror::Error;
|
||||
|
||||
/// the error type used throughout the crate
|
||||
#[derive(Debug, Error, IntoStaticStr)]
|
||||
pub enum Error {
|
||||
/// an io error
|
||||
#[error(transparent)]
|
||||
IO(#[from] std::io::Error),
|
||||
/// an ndarray shape error
|
||||
#[error(transparent)]
|
||||
Shape(#[from] ndarray::ShapeError),
|
||||
#[cfg(feature = "bioformats_java")]
|
||||
/// an error from the j4rs java bridge
|
||||
#[error(transparent)]
|
||||
J4rs(#[from] j4rs::errors::J4RsError),
|
||||
/// an infallible conversion
|
||||
#[error(transparent)]
|
||||
Infallible(#[from] std::convert::Infallible),
|
||||
/// an integer parse error
|
||||
#[error(transparent)]
|
||||
ParseIntError(#[from] std::num::ParseIntError),
|
||||
/// an ome metadata error
|
||||
#[error(transparent)]
|
||||
Ome(#[from] ome_metadata::error::Error),
|
||||
#[cfg(feature = "bioformats_java")]
|
||||
/// an error while downloading (e.g. the bioformats jar)
|
||||
#[error(transparent)]
|
||||
Downloader(#[from] downloader::Error),
|
||||
/// an error parsing an enum string with strum
|
||||
#[error(transparent)]
|
||||
Strum(#[from] strum::ParseError),
|
||||
#[cfg(feature = "tiffwrite")]
|
||||
/// an indicatif progress bar template error
|
||||
#[error(transparent)]
|
||||
TemplateError(#[from] indicatif::style::TemplateError),
|
||||
#[cfg(feature = "tiffwrite")]
|
||||
/// an error from the tiffwrite crate
|
||||
#[error(transparent)]
|
||||
TiffWrite(#[from] tiffwrite::error::Error),
|
||||
#[cfg(feature = "tiffseq")]
|
||||
/// a yaml (de)serialization error
|
||||
#[error(transparent)]
|
||||
SerdeYaml(#[from] serde_yaml::Error),
|
||||
#[cfg(any(feature = "tiffseq", feature = "tiff"))]
|
||||
/// an error from the tiff crate
|
||||
#[error(transparent)]
|
||||
Tiff(#[from] tiff::TiffError),
|
||||
#[cfg(feature = "python")]
|
||||
/// a postcard (de)serialization error
|
||||
#[error(transparent)]
|
||||
PostCard(#[from] postcard::Error),
|
||||
#[cfg(feature = "czi")]
|
||||
/// an error from the libczi binding
|
||||
#[error(transparent)]
|
||||
LibCzi(#[from] libczirw_sys::error::Error),
|
||||
/// a regex error
|
||||
#[error(transparent)]
|
||||
RegexError(#[from] regex::Error),
|
||||
#[cfg(feature = "czi")]
|
||||
/// an xmltree error
|
||||
#[error(transparent)]
|
||||
XmlTree(#[from] xmltree::Error),
|
||||
#[cfg(feature = "czi")]
|
||||
/// an xmltree parse error
|
||||
#[error(transparent)]
|
||||
XmlTreeParse(#[from] xmltree::ParseError),
|
||||
#[cfg(feature = "czi")]
|
||||
/// a czi-specific error
|
||||
#[error(transparent)]
|
||||
Czi(#[from] crate::readers::czi::CziError),
|
||||
#[cfg(feature = "movie")]
|
||||
/// an error joining a tokio task
|
||||
#[error(transparent)]
|
||||
TokioJoin(#[from] tokio::task::JoinError),
|
||||
#[cfg(feature = "bioformats_rust")]
|
||||
/// an error from the bioformats rust crate
|
||||
#[error(transparent)]
|
||||
BioFormats(#[from] bioformats::error::BioFormatsError),
|
||||
|
||||
/// the axis string could not be parsed
|
||||
#[error("invalid axis: {0}")]
|
||||
InvalidAxis(String),
|
||||
/// the axis was not found in the axes
|
||||
#[error("axis {0} not found in axes {1}")]
|
||||
AxisNotFound(String, String),
|
||||
/// a conversion error
|
||||
#[error("conversion error: {0}")]
|
||||
TryInto(String),
|
||||
/// the target file already exists
|
||||
#[error("file already exists {0}")]
|
||||
FileAlreadyExists(String),
|
||||
/// could not download ffmpeg
|
||||
#[error("could not download ffmpeg: {0}")]
|
||||
FfmpegDownload(String),
|
||||
/// an ffmpeg error
|
||||
#[error("FFmpeg error: {0}")]
|
||||
Ffmpeg(String),
|
||||
/// the index is out of bounds
|
||||
#[error("index {0} out of bounds {1}")]
|
||||
OutOfBounds(isize, isize),
|
||||
/// the axis was not included in the view
|
||||
#[error("axis {0} has length {1}, but was not included")]
|
||||
OutOfBoundsAxis(String, usize),
|
||||
/// the dimensionality of the data does not match
|
||||
#[error("dimensionality mismatch: {0} != {0}")]
|
||||
DimensionalityMismatch(usize, usize),
|
||||
/// the axis already has an operation
|
||||
#[error("axis {0}: {1} is already operated on!")]
|
||||
AxisAlreadyOperated(usize, String),
|
||||
/// not enough free dimensions
|
||||
#[error("not enough free dimensions")]
|
||||
NotEnoughFreeDimensions,
|
||||
/// cannot cast a pixel value to the requested type
|
||||
#[error("cannot cast {0} to {1}")]
|
||||
Cast(String, String),
|
||||
/// the view is empty
|
||||
#[error("empty view")]
|
||||
EmptyView,
|
||||
/// the color string could not be parsed
|
||||
#[error("invalid color: {0}")]
|
||||
InvalidColor(String),
|
||||
/// no image or pixels found in the metadata
|
||||
#[error("no image or pixels found")]
|
||||
NoImageOrPixels,
|
||||
/// the attenuation value is invalid
|
||||
#[error("invalid attenuation value: {0}")]
|
||||
InvalidAttenuation(String),
|
||||
/// the file name is invalid
|
||||
#[error("not a valid file name")]
|
||||
InvalidFileName,
|
||||
/// the file has no parent directory
|
||||
#[error("file has no parent")]
|
||||
NoParent,
|
||||
/// the pixel type is unknown
|
||||
#[error("unknown pixel type {0}")]
|
||||
UnknownPixelType(String),
|
||||
/// cannot compute the mean of an empty axis
|
||||
#[error("no mean")]
|
||||
NoMean,
|
||||
/// the tiff file lock is poisoned
|
||||
#[error("tiff is locked")]
|
||||
TiffLock,
|
||||
/// this feature is not implemented
|
||||
#[error("not implemented: {0}")]
|
||||
NotImplemented(String),
|
||||
/// a string could not be parsed
|
||||
#[error("cannot parse: {0}")]
|
||||
Parse(String),
|
||||
/// cannot convert the libczi pixel type
|
||||
#[error("cannot convert libczi pixel type: {0}")]
|
||||
Conversion(String),
|
||||
/// no reader could open the file
|
||||
#[error("no reader found for {0}, tried: {1}")]
|
||||
NoReader(String, String),
|
||||
/// the reader cannot open the file
|
||||
#[error("reader {0} cannot open file {1} because {2}")]
|
||||
InvalidReader(String, String, String),
|
||||
/// the file does not exist
|
||||
#[error("file does not exist: {0}")]
|
||||
FileDoesNotExist(String),
|
||||
/// cannot remove axes that have a size != 1
|
||||
#[error("cannot remove axes {0}, size {1} != 1")]
|
||||
SizeMismatch(String, usize),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// the name of the error variant as a static string
|
||||
pub fn variant_name(&self) -> &'static str {
|
||||
self.into()
|
||||
}
|
||||
|
||||
+13
@@ -43,22 +43,34 @@
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
/// axis handling: axis enum, slicing and shape
|
||||
pub mod axes;
|
||||
/// process-wide LRU caches for frames and materialized arrays
|
||||
mod cache;
|
||||
#[cfg(feature = "python")]
|
||||
mod py;
|
||||
/// min/max/sum/mean operations along an axis
|
||||
pub mod stats;
|
||||
/// the main data structure: an on-disk image that can be sliced without loading it fully
|
||||
pub mod view;
|
||||
|
||||
/// named colors and color conversion
|
||||
pub mod colors;
|
||||
/// the error type used throughout the crate
|
||||
pub mod error;
|
||||
/// ome metadata helpers
|
||||
pub mod metadata;
|
||||
#[cfg(feature = "movie")]
|
||||
/// saving views as movies
|
||||
pub mod movie;
|
||||
/// readers for the different supported image formats
|
||||
pub mod readers;
|
||||
#[cfg(feature = "tiffwrite")]
|
||||
/// saving views as tiff files
|
||||
pub mod tiffwrite;
|
||||
mod utils;
|
||||
|
||||
/// main entry point for the application
|
||||
pub mod main {
|
||||
#[cfg(feature = "tiffwrite")]
|
||||
use crate::axes::{Axis, Operation};
|
||||
@@ -172,6 +184,7 @@ pub mod main {
|
||||
}
|
||||
}
|
||||
|
||||
/// the command line interface, run `ndbioimage --help` for an overview of the commands
|
||||
pub fn main(args: Option<Vec<String>>) -> Result<(), Error> {
|
||||
let cli = if let Some(args) = args {
|
||||
Cli::parse_from(args)
|
||||
|
||||
@@ -20,10 +20,14 @@ impl Metadata for Ome {
|
||||
}
|
||||
}
|
||||
|
||||
/// helper trait to extract useful information from ome metadata
|
||||
pub trait Metadata {
|
||||
/// the instrument used to acquire the image
|
||||
fn get_instrument(&self) -> Option<&Instrument>;
|
||||
/// the first image in the ome structure
|
||||
fn get_image(&self) -> Option<&Image>;
|
||||
|
||||
/// the pixels of the image
|
||||
fn get_pixels(&self) -> Option<&Pixels> {
|
||||
if let Some(image) = self.get_image() {
|
||||
Some(&image.pixels)
|
||||
@@ -32,6 +36,7 @@ pub trait Metadata {
|
||||
}
|
||||
}
|
||||
|
||||
/// the objective used to acquire the image
|
||||
fn get_objective(&self) -> Option<&Objective> {
|
||||
let objective_id = self.get_image()?.objective_settings.as_ref()?.id.clone();
|
||||
self.get_instrument()?
|
||||
@@ -40,6 +45,7 @@ pub trait Metadata {
|
||||
.find(|o| o.id == objective_id)
|
||||
}
|
||||
|
||||
/// the tube lens used to acquire the image
|
||||
fn get_tube_lens(&self) -> Option<&Objective> {
|
||||
self.get_instrument()?
|
||||
.objective
|
||||
@@ -160,6 +166,7 @@ pub trait Metadata {
|
||||
)
|
||||
}
|
||||
|
||||
/// the binning of the detector for a channel (1, 2, 4 or 8)
|
||||
fn binning(&self, channel: usize) -> Option<usize> {
|
||||
match self
|
||||
.get_pixels()?
|
||||
@@ -178,6 +185,7 @@ pub trait Metadata {
|
||||
}
|
||||
}
|
||||
|
||||
/// the excitation wavelength of the laser for a channel in nm
|
||||
fn laser_wavelengths(&self, channel: usize) -> Result<Option<f64>, Error> {
|
||||
Ok(
|
||||
if let Some(pixels) = self.get_pixels()
|
||||
@@ -195,6 +203,7 @@ pub trait Metadata {
|
||||
)
|
||||
}
|
||||
|
||||
/// the laser power (fraction of the maximum) for a channel
|
||||
fn laser_powers(&self, channel: usize) -> Result<Option<f64>, Error> {
|
||||
if let Some(pixels) = self.get_pixels()
|
||||
&& let Some(channel) = pixels.channel.get(channel)
|
||||
@@ -211,10 +220,12 @@ pub trait Metadata {
|
||||
}
|
||||
}
|
||||
|
||||
/// the name of the objective
|
||||
fn objective_name(&self) -> Option<String> {
|
||||
Some(self.get_objective()?.model.as_ref()?.clone())
|
||||
}
|
||||
|
||||
/// the total magnification: objective magnification times tube lens magnification
|
||||
fn magnification(&self) -> Option<f64> {
|
||||
Some(
|
||||
(self.get_objective()?.nominal_magnification? as f64)
|
||||
@@ -222,10 +233,12 @@ pub trait Metadata {
|
||||
)
|
||||
}
|
||||
|
||||
/// the name of the tube lens
|
||||
fn tube_lens_name(&self) -> Option<String> {
|
||||
self.get_tube_lens()?.model.clone()
|
||||
}
|
||||
|
||||
/// the name of the filter set for a channel
|
||||
fn filter_set_name(&self, channel: usize) -> Option<String> {
|
||||
let filter_set_id = self
|
||||
.get_pixels()?
|
||||
@@ -244,6 +257,7 @@ pub trait Metadata {
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// the gain of the detector for a channel
|
||||
fn gain(&self, channel: usize) -> Option<f64> {
|
||||
self.get_pixels()
|
||||
.and_then(|p| p.channel.get(channel))
|
||||
@@ -257,18 +271,21 @@ pub trait Metadata {
|
||||
})
|
||||
}
|
||||
|
||||
/// whether the image is a z-stack (more than one z slice)
|
||||
fn is_zstack(&self) -> Result<bool, Error> {
|
||||
self.get_pixels()
|
||||
.map(|p| Ok(p.size_z > 1))
|
||||
.unwrap_or_else(|| Err(Error::NoImageOrPixels))
|
||||
}
|
||||
|
||||
/// whether the image is a time lapse (more than one time point)
|
||||
fn is_time_lapse(&self) -> Result<bool, Error> {
|
||||
self.get_pixels()
|
||||
.map(|p| Ok(p.size_t > 1))
|
||||
.unwrap_or_else(|| Err(Error::NoImageOrPixels))
|
||||
}
|
||||
|
||||
/// a multi-line summary of the most relevant metadata, one field per line
|
||||
fn summary(&self) -> Result<String, Error> {
|
||||
let size_c = if let Some(pixels) = self.get_pixels() {
|
||||
pixels.channel.len()
|
||||
|
||||
@@ -13,6 +13,7 @@ use ordered_float::OrderedFloat;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
/// options for creating a movie from a view
|
||||
pub struct MovieOptions {
|
||||
velocity: f64,
|
||||
brightness: Vec<f64>,
|
||||
@@ -38,6 +39,7 @@ impl Default for MovieOptions {
|
||||
}
|
||||
|
||||
impl MovieOptions {
|
||||
/// create movie options with the given parameters
|
||||
pub fn new(
|
||||
velocity: f64,
|
||||
brightness: Vec<f64>,
|
||||
@@ -67,18 +69,22 @@ impl MovieOptions {
|
||||
})
|
||||
}
|
||||
|
||||
/// set the frames per second of the movie
|
||||
pub fn set_velocity(&mut self, velocity: f64) {
|
||||
self.velocity = velocity;
|
||||
}
|
||||
|
||||
/// set the brightness scale factors for each channel
|
||||
pub fn set_brightness(&mut self, brightness: Vec<f64>) {
|
||||
self.brightness = brightness;
|
||||
}
|
||||
|
||||
/// set the display scale (zoom) factor for the movie
|
||||
pub fn set_scale(&mut self, scale: f64) {
|
||||
self.scale = scale;
|
||||
}
|
||||
|
||||
/// set the color lookup table for the movie
|
||||
pub fn set_colors(&mut self, colors: &[String]) -> Result<(), Error> {
|
||||
let colors = colors
|
||||
.iter()
|
||||
@@ -88,6 +94,7 @@ impl MovieOptions {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// set whether an existing movie file should be overwritten
|
||||
pub fn set_overwrite(&mut self, overwrite: bool) {
|
||||
self.overwrite = overwrite;
|
||||
}
|
||||
@@ -142,6 +149,7 @@ where
|
||||
R: Reader,
|
||||
Self: 'static,
|
||||
{
|
||||
/// save the view as a movie file with the given options
|
||||
pub fn save_as_movie<P>(&self, path: P, options: &MovieOptions) -> Result<(), Error>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
|
||||
@@ -179,7 +179,7 @@ impl PyView {
|
||||
override_type(type_repr="str | pathlib.Path | Imread | bytes", imports=("pathlib"))
|
||||
)]
|
||||
path: Bound<'py, PyAny>,
|
||||
#[gen_stub(override_type(type_repr = "numpy.typing.DTypeLike", imports=("numpy", "numpy.typing")
|
||||
#[gen_stub(override_type(type_repr = "typing.Optional[numpy.typing.DTypeLike]", imports=("typing", "numpy", "numpy.typing")
|
||||
))]
|
||||
dtype: Option<Bound<'py, PyAny>>,
|
||||
axes: &str,
|
||||
@@ -229,6 +229,7 @@ impl PyView {
|
||||
}
|
||||
}
|
||||
|
||||
/// get all available positions (series) in the file
|
||||
#[staticmethod]
|
||||
fn get_positions<'py>(
|
||||
py: Python,
|
||||
@@ -244,16 +245,19 @@ impl PyView {
|
||||
#[staticmethod]
|
||||
fn kill_vm() {}
|
||||
|
||||
/// the name of the reader used to open the file
|
||||
#[getter]
|
||||
fn reader_name(&self) -> String {
|
||||
self.view.reader_name().to_string()
|
||||
}
|
||||
|
||||
/// reshape the view with a new axis order
|
||||
#[allow(unused_variables)]
|
||||
fn reshape<'py>(&self, order: &str, copy: bool) -> PyResult<Bound<'py, PyAny>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// return a new view with transformations applied (channel alignment, drift correction)
|
||||
#[allow(unused_variables)]
|
||||
#[pyo3(signature = (channels = true, drift = false, file = None, bead_files = None))]
|
||||
fn with_transform<'py>(
|
||||
@@ -266,6 +270,7 @@ impl PyView {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// get the transformation matrix (not yet implemented)
|
||||
#[getter]
|
||||
fn get_transform(&self) -> PyResult<()> {
|
||||
todo!()
|
||||
@@ -533,12 +538,13 @@ impl PyView {
|
||||
}
|
||||
}
|
||||
|
||||
/// convert to a numpy array, optionally with a different dtype
|
||||
#[allow(unused_variables)]
|
||||
#[pyo3(signature = (dtype = None, copy = None))]
|
||||
fn __array__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
#[gen_stub(override_type(type_repr = "numpy.typing.DTypeLike", imports=("numpy", "numpy.typing")
|
||||
#[gen_stub(override_type(type_repr = "typing.Optional[numpy.typing.DTypeLike]", imports=("typing", "numpy", "numpy.typing")
|
||||
))]
|
||||
dtype: Option<Bound<'py, PyAny>>,
|
||||
copy: Option<bool>,
|
||||
@@ -550,10 +556,12 @@ impl PyView {
|
||||
}
|
||||
}
|
||||
|
||||
/// check if an item is contained in the view (not implemented)
|
||||
fn __contains__(&self, _item: Bound<PyAny>) -> PyResult<bool> {
|
||||
Err(PyNotImplementedError::new_err("contains not implemented"))
|
||||
}
|
||||
|
||||
/// element-wise less than comparison
|
||||
fn __lt__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -565,6 +573,7 @@ impl PyView {
|
||||
np.getattr("less")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise less than or equal comparison
|
||||
fn __le__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -576,6 +585,7 @@ impl PyView {
|
||||
np.getattr("less_equal")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise equality comparison
|
||||
fn __eq__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -587,6 +597,7 @@ impl PyView {
|
||||
np.getattr("equal")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise not equal comparison
|
||||
fn __ne__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -598,6 +609,7 @@ impl PyView {
|
||||
np.getattr("not_equal")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise greater than comparison
|
||||
fn __gt__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -609,6 +621,7 @@ impl PyView {
|
||||
np.getattr("greater")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise greater than or equal comparison
|
||||
fn __ge__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -620,6 +633,7 @@ impl PyView {
|
||||
np.getattr("greater_equal")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise addition
|
||||
fn __add__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -631,6 +645,7 @@ impl PyView {
|
||||
np.getattr("add")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise addition (reflected)
|
||||
fn __radd__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -642,6 +657,7 @@ impl PyView {
|
||||
np.getattr("add")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise subtraction
|
||||
fn __sub__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -653,6 +669,7 @@ impl PyView {
|
||||
np.getattr("subtract")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise subtraction (reflected)
|
||||
fn __rsub__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -664,6 +681,7 @@ impl PyView {
|
||||
np.getattr("subtract")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise multiplication
|
||||
fn __mul__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -675,6 +693,7 @@ impl PyView {
|
||||
np.getattr("multiply")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise multiplication (reflected)
|
||||
fn __rmul__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -686,6 +705,7 @@ impl PyView {
|
||||
np.getattr("multiply")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise true division
|
||||
fn __truediv__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -697,6 +717,7 @@ impl PyView {
|
||||
np.getattr("true_divide")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise true division (reflected)
|
||||
fn __rtruediv__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -708,6 +729,7 @@ impl PyView {
|
||||
np.getattr("true_divide")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise floor division
|
||||
fn __floordiv__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -719,6 +741,7 @@ impl PyView {
|
||||
np.getattr("floor_divide")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise floor division (reflected)
|
||||
fn __rfloordiv__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -730,6 +753,7 @@ impl PyView {
|
||||
np.getattr("floor_divide")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise modulo
|
||||
fn __mod__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -741,6 +765,7 @@ impl PyView {
|
||||
np.getattr("remainder")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise modulo (reflected)
|
||||
fn __rmod__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -788,6 +813,7 @@ impl PyView {
|
||||
np.getattr("power")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise matrix multiplication
|
||||
fn __matmul__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -799,6 +825,7 @@ impl PyView {
|
||||
np.getattr("matmul")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise matrix multiplication (reflected)
|
||||
fn __rmatmul__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -810,6 +837,7 @@ impl PyView {
|
||||
np.getattr("matmul")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise bitwise AND
|
||||
fn __and__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -821,6 +849,7 @@ impl PyView {
|
||||
np.getattr("bitwise_and")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise bitwise AND (reflected)
|
||||
fn __rand__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -832,6 +861,7 @@ impl PyView {
|
||||
np.getattr("bitwise_and")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise bitwise OR
|
||||
fn __or__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -843,6 +873,7 @@ impl PyView {
|
||||
np.getattr("bitwise_or")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise bitwise OR (reflected)
|
||||
fn __ror__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -854,6 +885,7 @@ impl PyView {
|
||||
np.getattr("bitwise_or")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise bitwise XOR
|
||||
fn __xor__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -865,6 +897,7 @@ impl PyView {
|
||||
np.getattr("bitwise_xor")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise bitwise XOR (reflected)
|
||||
fn __rxor__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -876,6 +909,7 @@ impl PyView {
|
||||
np.getattr("bitwise_xor")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise left shift
|
||||
fn __lshift__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -887,6 +921,7 @@ impl PyView {
|
||||
np.getattr("left_shift")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise left shift (reflected)
|
||||
fn __rlshift__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -898,6 +933,7 @@ impl PyView {
|
||||
np.getattr("left_shift")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise right shift
|
||||
fn __rshift__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -909,6 +945,7 @@ impl PyView {
|
||||
np.getattr("right_shift")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise right shift (reflected)
|
||||
fn __rrshift__<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
@@ -920,34 +957,40 @@ impl PyView {
|
||||
np.getattr("right_shift")?.call1((&a, &b))
|
||||
}
|
||||
|
||||
/// element-wise negation
|
||||
fn __neg__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
|
||||
let np = PyModule::import(py, "numpy")?;
|
||||
let a = self.as_array(py)?;
|
||||
np.getattr("negative")?.call1((&a,))
|
||||
}
|
||||
|
||||
/// element-wise positive
|
||||
fn __pos__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
|
||||
let np = PyModule::import(py, "numpy")?;
|
||||
let a = self.as_array(py)?;
|
||||
np.getattr("positive")?.call1((&a,))
|
||||
}
|
||||
|
||||
/// element-wise absolute value
|
||||
fn __abs__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
|
||||
let np = PyModule::import(py, "numpy")?;
|
||||
let a = self.as_array(py)?;
|
||||
np.getattr("absolute")?.call1((&a,))
|
||||
}
|
||||
|
||||
/// element-wise bitwise inversion
|
||||
fn __invert__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
|
||||
let np = PyModule::import(py, "numpy")?;
|
||||
let a = self.as_array(py)?;
|
||||
np.getattr("invert")?.call1((&a,))
|
||||
}
|
||||
|
||||
/// context manager entry
|
||||
fn __enter__<'py>(slf: PyRef<'py, Self>) -> PyResult<PyRef<'py, Self>> {
|
||||
Ok(slf)
|
||||
}
|
||||
|
||||
/// context manager exit
|
||||
#[allow(unused_variables)]
|
||||
#[pyo3(signature = (exc_type=None, exc_val=None, exc_tb=None))]
|
||||
fn __exit__(
|
||||
@@ -959,10 +1002,12 @@ impl PyView {
|
||||
self.close()
|
||||
}
|
||||
|
||||
/// arguments for pickling
|
||||
pub(crate) fn __getnewargs__(&self) -> PyResult<(Vec<u8>,)> {
|
||||
Ok((to_stdvec(self).map_err(Error::from)?,))
|
||||
}
|
||||
|
||||
/// shallow copy of the view
|
||||
fn __copy__(&self) -> Self {
|
||||
Self {
|
||||
view: self.view.clone(),
|
||||
@@ -972,6 +1017,7 @@ impl PyView {
|
||||
}
|
||||
}
|
||||
|
||||
/// deep copy of the view (same as shallow copy for this type)
|
||||
fn __deepcopy__(&self) -> Self {
|
||||
Self {
|
||||
view: self.view.clone(),
|
||||
@@ -981,6 +1027,7 @@ impl PyView {
|
||||
}
|
||||
}
|
||||
|
||||
/// create a copy of the view
|
||||
fn copy(&self) -> Self {
|
||||
Self {
|
||||
view: self.view.clone(),
|
||||
@@ -990,6 +1037,7 @@ impl PyView {
|
||||
}
|
||||
}
|
||||
|
||||
/// iterate over the first axis
|
||||
fn __iter__(&self) -> Self {
|
||||
Self {
|
||||
view: self.view.clone(),
|
||||
@@ -999,6 +1047,7 @@ impl PyView {
|
||||
}
|
||||
}
|
||||
|
||||
/// get the next item in the iteration
|
||||
fn __next__<'py>(&mut self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyAny>>> {
|
||||
let shape = self.view.shape();
|
||||
if shape.is_empty() || (self.index == shape[0]) {
|
||||
@@ -1030,14 +1079,17 @@ impl PyView {
|
||||
}
|
||||
}
|
||||
|
||||
/// number of elements in the first axis
|
||||
fn __len__(&self) -> PyResult<usize> {
|
||||
Ok(self.view.len())
|
||||
}
|
||||
|
||||
/// string representation with a summary of the image
|
||||
fn __repr__(&self) -> PyResult<String> {
|
||||
Ok(self.view.summary()?)
|
||||
}
|
||||
|
||||
/// the file path as a string
|
||||
fn __str__(&self) -> PyResult<String> {
|
||||
Ok(self.view.path().display().to_string())
|
||||
}
|
||||
@@ -1119,6 +1171,7 @@ impl PyView {
|
||||
})
|
||||
}
|
||||
|
||||
/// flatten the view into a 1D numpy array
|
||||
fn flatten<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
|
||||
Ok(match self.dtype {
|
||||
PixelType::I8 => self.view.flatten::<i8>()?.into_pyarray(py).into_any(),
|
||||
@@ -1137,6 +1190,7 @@ impl PyView {
|
||||
})
|
||||
}
|
||||
|
||||
/// convert the view to bytes
|
||||
fn to_bytes(&self) -> PyResult<Vec<u8>> {
|
||||
Ok(match self.dtype {
|
||||
PixelType::I8 => self.view.to_bytes::<i8>()?,
|
||||
@@ -1155,6 +1209,7 @@ impl PyView {
|
||||
})
|
||||
}
|
||||
|
||||
/// convert the view to bytes (alias for to_bytes)
|
||||
fn tobytes(&self) -> PyResult<Vec<u8>> {
|
||||
self.to_bytes()
|
||||
}
|
||||
@@ -1192,6 +1247,7 @@ impl PyView {
|
||||
}
|
||||
}
|
||||
|
||||
/// the current slice applied to the view
|
||||
#[getter]
|
||||
fn slice(&self) -> PyResult<Vec<String>> {
|
||||
Ok(self
|
||||
@@ -1283,6 +1339,7 @@ impl PyView {
|
||||
})
|
||||
}
|
||||
|
||||
/// transposed view (alias for transpose(None))
|
||||
#[allow(non_snake_case)]
|
||||
#[getter]
|
||||
fn T(&self) -> PyResult<PyView> {
|
||||
@@ -1308,6 +1365,7 @@ impl PyView {
|
||||
})
|
||||
}
|
||||
|
||||
/// the numpy dtype of the view
|
||||
#[gen_stub(override_return_type(type_repr = "numpy.dtype", imports=("numpy")))]
|
||||
#[getter]
|
||||
fn get_dtype<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyArrayDescr>> {
|
||||
@@ -1332,6 +1390,7 @@ impl PyView {
|
||||
}
|
||||
}
|
||||
|
||||
/// set the dtype of the view
|
||||
#[gen_stub(skip)]
|
||||
#[setter]
|
||||
fn set_dtype(&mut self, py: Python, dtype: Bound<'_, PyAny>) -> PyResult<()> {
|
||||
@@ -1451,6 +1510,7 @@ impl PyView {
|
||||
}
|
||||
}
|
||||
|
||||
/// get the mean overall or along a given axis
|
||||
#[gen_stub(skip)]
|
||||
#[pyo3(signature = (axis=None, dtype=None, out=None, keepdims=false, *, r#where=true), text_signature = "axis: str | int"
|
||||
)]
|
||||
@@ -1553,6 +1613,7 @@ impl PyView {
|
||||
}
|
||||
}
|
||||
|
||||
/// whether the view contains a z-stack (more than one z slice)
|
||||
#[getter]
|
||||
fn z_stack(&self) -> PyResult<bool> {
|
||||
if let Some(s) = self.view.size_ax(Axis::Z) {
|
||||
@@ -1572,6 +1633,7 @@ impl PyView {
|
||||
}
|
||||
}
|
||||
|
||||
/// whether the view contains a time series (more than one time point)
|
||||
#[getter]
|
||||
fn time_series(&self) -> PyResult<bool> {
|
||||
if let Some(s) = self.view.size_ax(Axis::T) {
|
||||
@@ -1591,6 +1653,7 @@ impl PyView {
|
||||
}
|
||||
}
|
||||
|
||||
/// the pixel size in micrometers
|
||||
#[getter]
|
||||
fn pixel_size(&self) -> PyResult<Option<f64>> {
|
||||
Ok(self.ome.pixel_size()?)
|
||||
@@ -1608,11 +1671,13 @@ impl PyView {
|
||||
Ok(self.ome.delta_z()?.map(|p| p / 1000.))
|
||||
}
|
||||
|
||||
/// the z-step size in micrometers
|
||||
#[getter]
|
||||
fn delta_z(&self) -> PyResult<Option<f64>> {
|
||||
Ok(self.ome.delta_z()?)
|
||||
}
|
||||
|
||||
/// the time interval between frames in seconds
|
||||
#[getter]
|
||||
fn time_interval(&self) -> PyResult<Option<f64>> {
|
||||
Ok(self.ome.time_interval()?)
|
||||
@@ -1624,6 +1689,7 @@ impl PyView {
|
||||
Ok(self.ome.time_interval()?)
|
||||
}
|
||||
|
||||
/// the exposure time for a given channel in seconds
|
||||
fn exposure_time(&self, channel: usize) -> PyResult<Option<f64>> {
|
||||
Ok(self.ome.exposure_time(channel)?)
|
||||
}
|
||||
@@ -1636,37 +1702,45 @@ impl PyView {
|
||||
.collect::<Result<Vec<_>, Error>>()?)
|
||||
}
|
||||
|
||||
/// the binning for a given channel
|
||||
fn binning(&self, channel: usize) -> Option<usize> {
|
||||
self.ome.binning(channel)
|
||||
}
|
||||
|
||||
/// the laser wavelength for a given channel in nanometers
|
||||
fn laser_wavelengths(&self, channel: usize) -> PyResult<Option<f64>> {
|
||||
Ok(self.ome.laser_wavelengths(channel)?)
|
||||
}
|
||||
|
||||
/// the laser power for a given channel as a fraction
|
||||
fn laser_power(&self, channel: usize) -> PyResult<Option<f64>> {
|
||||
Ok(self.ome.laser_powers(channel)?)
|
||||
}
|
||||
|
||||
/// the name of the objective
|
||||
#[getter]
|
||||
fn objective_name(&self) -> Option<String> {
|
||||
self.ome.objective_name()
|
||||
}
|
||||
|
||||
/// the total magnification (objective × tube lens)
|
||||
#[getter]
|
||||
fn magnification(&self) -> Option<f64> {
|
||||
self.ome.magnification()
|
||||
}
|
||||
|
||||
/// the name of the tube lens
|
||||
#[getter]
|
||||
fn tube_lens_name(&self) -> Option<String> {
|
||||
self.ome.tube_lens_name()
|
||||
}
|
||||
|
||||
/// the name of the filter set for a given channel
|
||||
fn filter_set_name(&self, channel: usize) -> Option<String> {
|
||||
self.ome.filter_set_name(channel)
|
||||
}
|
||||
|
||||
/// the detector gain for a given channel
|
||||
fn gain(&self, channel: usize) -> Option<f64> {
|
||||
self.ome.gain(channel)
|
||||
}
|
||||
@@ -1730,6 +1804,7 @@ impl PyView {
|
||||
)?)
|
||||
}
|
||||
|
||||
/// save the view as a TIFF file
|
||||
#[cfg(feature = "tiffwrite")]
|
||||
#[pyo3(signature = (file, colors = None, overwrite = false, bar = true))]
|
||||
fn save_as_tiff(
|
||||
@@ -1770,6 +1845,7 @@ impl PyView {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// save the view as a movie file (MP4)
|
||||
#[cfg(feature = "movie")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[pyo3(signature = (file, speed = 1.0, brightness = None, scale = 1.0, colors = None, overwrite = false, register = false, no_scaling = false)
|
||||
@@ -1813,21 +1889,22 @@ submit! {
|
||||
def __pow__(other: Imread | numpy.typing.NDArray | int | float, mod: typing.Any = None, /) -> numpy.typing.NDArray: ...
|
||||
def __rpow__(other: Imread | numpy.typing.NDArray | int | float, mod: typing.Any = None, /) -> numpy.typing.NDArray: ...
|
||||
|
||||
def max(axis: int | str = None, dtype: numpy.typing.DTypeLike = None, out: typing.Any = None, keepdims: bool = False, initial: int | float = None, where: bool = True) -> Imread | numpy.typing.NDArray | int | float:
|
||||
def max(axis: int | str = None, dtype: typing.Optional[numpy.typing.DTypeLike] = None, out: typing.Any = None, keepdims: bool = False, initial: int | float = None, where: bool = True) -> Imread | numpy.typing.NDArray | int | float:
|
||||
""" Return the maximum along a given axis. Arguments beyond axis are not implemented """
|
||||
|
||||
def min(axis: int | str = None, dtype: numpy.typing.DTypeLike = None, out: typing.Any = None, keepdims: bool = False, initial: int | float = None, where: bool = True) -> Imread | numpy.typing.NDArray | int | float:
|
||||
def min(axis: int | str = None, dtype: typing.Optional[numpy.typing.DTypeLike] = None, out: typing.Any = None, keepdims: bool = False, initial: int | float = None, where: bool = True) -> Imread | numpy.typing.NDArray | int | float:
|
||||
""" Return the minimum along a given axis. Arguments beyond axis are not implemented """
|
||||
|
||||
def mean(axis: int | str = None, dtype: numpy.typing.DTypeLike = None, out: typing.Any = None, keepdims: bool = False) -> Imread | numpy.typing.NDArray | int | float:
|
||||
def mean(axis: int | str = None, dtype: typing.Optional[numpy.typing.DTypeLike] = None, out: typing.Any = None, keepdims: bool = False) -> Imread | numpy.typing.NDArray | int | float:
|
||||
""" Return the mean along a given axis. Arguments beyond axis are not implemented """
|
||||
|
||||
def sum(axis: int | str = None, dtype: numpy.typing.DTypeLike = None, out: typing.Any = None, keepdims: bool = False, initial: int | float = None, where: bool = True) -> Imread | numpy.typing.NDArray | int | float:
|
||||
def sum(axis: int | str = None, dtype: typing.Optional[numpy.typing.DTypeLike] = None, out: typing.Any = None, keepdims: bool = False, initial: int | float = None, where: bool = True) -> Imread | numpy.typing.NDArray | int | float:
|
||||
""" Return the sum along a given axis. Arguments beyond axis are not implemented """
|
||||
"#
|
||||
}
|
||||
}
|
||||
|
||||
/// batch convert multiple image files to TIFF format
|
||||
#[cfg(feature = "tiffwrite")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[gen_stub_pyfunction(module = "ndbioimage.ndbioimage_rs")]
|
||||
@@ -1852,6 +1929,7 @@ fn batch_to_tiff(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// represents the shape of an image with named dimensions (c, z, t, y, x)
|
||||
#[gen_stub_pyclass]
|
||||
#[pyclass(
|
||||
subclass,
|
||||
@@ -1868,6 +1946,7 @@ struct PyShape {
|
||||
#[gen_stub_pymethods]
|
||||
#[pymethods]
|
||||
impl PyShape {
|
||||
/// create a new shape with the given dimensions
|
||||
#[new]
|
||||
#[pyo3(signature = (order, c = 1, z = 1, t = 1, y = 1, x = 1))]
|
||||
fn new(order: String, c: usize, z: usize, t: usize, y: usize, x: usize) -> PyResult<Self> {
|
||||
@@ -1887,35 +1966,42 @@ impl PyShape {
|
||||
})
|
||||
}
|
||||
|
||||
/// the number of channels
|
||||
#[getter]
|
||||
fn get_c(&self) -> usize {
|
||||
self.inner.c
|
||||
}
|
||||
|
||||
/// the number of z slices
|
||||
#[getter]
|
||||
fn get_z(&self) -> usize {
|
||||
self.inner.z
|
||||
}
|
||||
|
||||
/// the number of time points
|
||||
#[getter]
|
||||
fn get_t(&self) -> usize {
|
||||
self.inner.t
|
||||
}
|
||||
|
||||
/// the number of pixels along y
|
||||
#[getter]
|
||||
fn get_y(&self) -> usize {
|
||||
self.inner.y
|
||||
}
|
||||
|
||||
/// the number of pixels along x
|
||||
#[getter]
|
||||
fn get_x(&self) -> usize {
|
||||
self.inner.x
|
||||
}
|
||||
|
||||
/// string representation
|
||||
fn __str__(&self) -> String {
|
||||
format!("{}", self.inner)
|
||||
}
|
||||
|
||||
/// detailed representation for debugging
|
||||
fn __repr__(&self) -> String {
|
||||
format!(
|
||||
"Shape({}, {}, {}, {}, {})",
|
||||
@@ -1923,6 +2009,7 @@ impl PyShape {
|
||||
)
|
||||
}
|
||||
|
||||
/// arguments for pickling
|
||||
fn __getnewargs__(&self) -> (String, usize, usize, usize, usize, usize) {
|
||||
(
|
||||
self.inner
|
||||
@@ -1939,6 +2026,7 @@ impl PyShape {
|
||||
)
|
||||
}
|
||||
|
||||
/// get dimension size by index or axis name
|
||||
#[gen_stub(override_return_type(type_repr="typing.Optional[int | list[int]]", imports=("typing")
|
||||
))]
|
||||
fn __getitem__<'py>(
|
||||
@@ -2034,10 +2122,12 @@ impl PyShape {
|
||||
}
|
||||
}
|
||||
|
||||
/// number of dimensions in the shape
|
||||
fn __len__(&self) -> usize {
|
||||
self.inner.order.len()
|
||||
}
|
||||
|
||||
/// convert shape to a list of dimension sizes in order
|
||||
fn to_list(&self) -> Vec<usize> {
|
||||
vec![
|
||||
self.inner.c,
|
||||
@@ -2048,6 +2138,7 @@ impl PyShape {
|
||||
]
|
||||
}
|
||||
|
||||
/// the axis order as a string (e.g., "CZTYX")
|
||||
#[getter]
|
||||
fn axes(&self) -> String {
|
||||
self.inner
|
||||
@@ -2081,6 +2172,7 @@ pub fn generate_stub(dest_path: String) -> PyResult<()> {
|
||||
.generate()?)
|
||||
}
|
||||
|
||||
/// main entry point for the command-line interface
|
||||
#[gen_stub_pyfunction(module = "ndbioimage.ndbioimage_rs")]
|
||||
#[pyfunction]
|
||||
fn main() -> PyResult<()> {
|
||||
|
||||
+65
-4
@@ -14,33 +14,45 @@ use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
/// czi file reader
|
||||
#[cfg(feature = "czi")]
|
||||
pub mod czi;
|
||||
|
||||
/// bioformats reader (pure rust)
|
||||
#[cfg(feature = "bioformats_rust")]
|
||||
pub mod bioformats_rust;
|
||||
|
||||
/// bioformats reader (java bindings)
|
||||
#[cfg(feature = "bioformats_java")]
|
||||
pub mod bioformats_java;
|
||||
|
||||
/// tiff sequence reader
|
||||
#[cfg(feature = "tiffseq")]
|
||||
pub mod tiffseq;
|
||||
|
||||
/// single tiff file reader
|
||||
#[cfg(feature = "tiff")]
|
||||
pub mod tiff;
|
||||
|
||||
static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^([CZTSP])\D+(\d+)$").unwrap());
|
||||
|
||||
/// dimensions of the image data
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Dimensions {
|
||||
/// number of channels
|
||||
pub c: Option<usize>,
|
||||
/// number of z slices
|
||||
pub z: Option<usize>,
|
||||
/// number of time points
|
||||
pub t: Option<usize>,
|
||||
/// series index
|
||||
pub s: Option<usize>,
|
||||
/// position index
|
||||
pub p: Option<usize>,
|
||||
}
|
||||
|
||||
impl Dimensions {
|
||||
/// create dimensions with the given series and position
|
||||
pub fn new(series: usize, position: usize) -> Self {
|
||||
Self {
|
||||
c: None,
|
||||
@@ -51,6 +63,7 @@ impl Dimensions {
|
||||
}
|
||||
}
|
||||
|
||||
/// parse a path and extract dimensions from the directory structure
|
||||
pub fn parse_path<P>(path: P) -> Result<(PathBuf, Self), Error>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
@@ -86,26 +99,40 @@ impl Dimensions {
|
||||
}
|
||||
}
|
||||
|
||||
/// Pixel types (u)int(8/16/32) or float(32/64), (u/i)(64/128) are not included in bioformats
|
||||
/// pixel type enum
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum PixelType {
|
||||
/// signed 8-bit integer
|
||||
I8,
|
||||
/// unsigned 8-bit integer
|
||||
U8,
|
||||
/// signed 16-bit integer
|
||||
I16,
|
||||
/// unsigned 16-bit integer
|
||||
U16,
|
||||
/// signed 32-bit integer
|
||||
I32,
|
||||
/// unsigned 32-bit integer
|
||||
U32,
|
||||
/// 32-bit float
|
||||
F32,
|
||||
/// 64-bit float
|
||||
F64,
|
||||
/// signed 64-bit integer
|
||||
I64,
|
||||
/// unsigned 64-bit integer
|
||||
U64,
|
||||
/// signed 128-bit integer
|
||||
I128,
|
||||
/// unsigned 128-bit integer
|
||||
U128,
|
||||
/// 128-bit float (emulated)
|
||||
F128,
|
||||
}
|
||||
|
||||
impl PixelType {
|
||||
/// number of bytes per pixel for this type
|
||||
pub fn bytes_per_pixel(&self) -> usize {
|
||||
match self {
|
||||
PixelType::I8 | PixelType::U8 => 1,
|
||||
@@ -117,37 +144,55 @@ impl PixelType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct containing frame data in one of eight pixel types. Cast to `Array2<T>` using try_into.
|
||||
/// array data with a specific pixel type
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ArrayT<D: Dimension> {
|
||||
/// signed 8-bit integer array
|
||||
I8(Array<i8, D>),
|
||||
/// unsigned 8-bit integer array
|
||||
U8(Array<u8, D>),
|
||||
/// signed 16-bit integer array
|
||||
I16(Array<i16, D>),
|
||||
/// unsigned 16-bit integer array
|
||||
U16(Array<u16, D>),
|
||||
/// signed 32-bit integer array
|
||||
I32(Array<i32, D>),
|
||||
/// unsigned 32-bit integer array
|
||||
U32(Array<u32, D>),
|
||||
/// 32-bit float array
|
||||
F32(Array<f32, D>),
|
||||
/// 64-bit float array
|
||||
F64(Array<f64, D>),
|
||||
/// signed 64-bit integer array
|
||||
I64(Array<i64, D>),
|
||||
/// unsigned 64-bit integer array
|
||||
U64(Array<u64, D>),
|
||||
/// signed 128-bit integer array
|
||||
I128(Array<i128, D>),
|
||||
/// unsigned 128-bit integer array
|
||||
U128(Array<u128, D>),
|
||||
F128(Array<f64, D>), // f128 is nightly
|
||||
/// 128-bit float array (emulated as f64)
|
||||
F128(Array<f64, D>),
|
||||
}
|
||||
|
||||
/// type alias for a single frame (2D image)
|
||||
pub type Frame = ArrayT<Ix2>;
|
||||
|
||||
/// trait for reading image files
|
||||
pub trait Reader: Clone + Sized + Debug + Send + Hash + Into<DynReader> {
|
||||
/// create a new reader for the given path, series, and position
|
||||
fn new<P>(path: P, series: usize, position: usize) -> Result<Self, Error>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
/// the name of the reader type
|
||||
fn reader_name(&self) -> &'static str {
|
||||
type_name::<Self>()
|
||||
}
|
||||
|
||||
// TODO: read from file if present
|
||||
/// get the ome metadata for the image
|
||||
fn metadata(&self) -> Result<Ome, Error>;
|
||||
|
||||
/// get a sliceable view on the image file
|
||||
@@ -161,18 +206,25 @@ pub trait Reader: Clone + Sized + Debug + Send + Hash + Into<DynReader> {
|
||||
)
|
||||
}
|
||||
|
||||
/// Retrieve fame at channel c, slize z and time t.
|
||||
/// retrieve frame at channel c, slice z and time t
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
fn get_frame(&self, c: usize, z: usize, t: usize) -> Result<Frame, Error>;
|
||||
|
||||
/// the path to the image file
|
||||
fn path(&self) -> &Path;
|
||||
/// the series index
|
||||
fn series(&self) -> usize;
|
||||
/// the position index
|
||||
fn position(&self) -> usize;
|
||||
/// the shape of the image data
|
||||
fn shape(&self) -> &Shape;
|
||||
/// the pixel type of the image data
|
||||
fn pixel_type(&self) -> &PixelType;
|
||||
/// get all available positions for a given series
|
||||
fn get_available_positions<P>(path: P, series: usize) -> Result<HashSet<usize>, Error>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
/// get all available series
|
||||
fn get_available_series<P>(path: P) -> Result<HashSet<usize>, Error>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
@@ -365,16 +417,22 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// dynamic reader that can handle multiple file formats
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
|
||||
pub enum DynReader {
|
||||
/// tiff file reader
|
||||
#[cfg(feature = "tiff")]
|
||||
Tiff(tiff::TiffReader),
|
||||
/// tiff sequence reader
|
||||
#[cfg(feature = "tiffseq")]
|
||||
TiffSeq(tiffseq::TiffSeqReader),
|
||||
/// czi file reader
|
||||
#[cfg(feature = "czi")]
|
||||
Czi(czi::CziReader),
|
||||
/// bioformats reader (pure rust)
|
||||
#[cfg(feature = "bioformats_rust")]
|
||||
BioFormatsRust(bioformats_rust::BioFormatsRustReader),
|
||||
/// bioformats reader (java bindings)
|
||||
#[cfg(feature = "bioformats_java")]
|
||||
BioFormatsJava(bioformats_java::BioFormatsJavaReader),
|
||||
}
|
||||
@@ -626,6 +684,7 @@ impl Reader for DynReader {
|
||||
}
|
||||
|
||||
impl DynReader {
|
||||
/// create a dynreader by selecting the reader type explicitly
|
||||
pub fn from_path_select_reader<P, R>(path: P, reader: R) -> Result<DynReader, Error>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
@@ -674,6 +733,7 @@ impl DynReader {
|
||||
Ok(reader)
|
||||
}
|
||||
|
||||
/// get all available positions, optionally selecting a specific reader type
|
||||
pub fn get_available_positions_select_reader<P, R>(
|
||||
path: P,
|
||||
series: usize,
|
||||
@@ -709,6 +769,7 @@ impl DynReader {
|
||||
})
|
||||
}
|
||||
|
||||
/// get all available series, optionally selecting a specific reader type
|
||||
pub fn get_available_series_select_reader<P, R>(
|
||||
path: P,
|
||||
reader: Option<R>,
|
||||
|
||||
@@ -12,6 +12,7 @@ use std::hash::{Hash, Hasher};
|
||||
use std::path::{Path, PathBuf};
|
||||
use thread_local::ThreadLocal;
|
||||
|
||||
/// reader for czi (zeiss) image files
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct CziReader {
|
||||
#[serde(skip)]
|
||||
@@ -121,8 +122,10 @@ impl From<Option<String>> for Version {
|
||||
}
|
||||
}
|
||||
|
||||
/// errors specific to czi file reading
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum CziError {
|
||||
/// czi file has no valid blocks
|
||||
#[error("czi file has no valid blocks")]
|
||||
NoValidBlocks,
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use thread_local::ThreadLocal;
|
||||
use tiff::decoder::{Decoder, DecodingResult};
|
||||
use tiff::tags::Tag;
|
||||
|
||||
/// reader for single tiff image files
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct TiffReader {
|
||||
#[serde(skip)]
|
||||
|
||||
@@ -11,6 +11,7 @@ use std::str::FromStr;
|
||||
use tiff::decoder::{Decoder, DecodingResult};
|
||||
use tiff::tags::Tag;
|
||||
|
||||
/// reader for sequences of tiff files (one file per z/time plane)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TiffSeqReader {
|
||||
path: PathBuf,
|
||||
|
||||
@@ -3,11 +3,16 @@ use ndarray::{Array, ArrayD, ArrayView, Axis, Dimension, RemoveAxis};
|
||||
|
||||
/// a trait to define the min, max, sum and mean operations along an axis
|
||||
pub trait MinMax {
|
||||
/// the type of the result after reducing one axis
|
||||
type Output;
|
||||
|
||||
/// the max of the array along `axis`
|
||||
fn max(self, axis: usize) -> Result<Self::Output, Error>;
|
||||
/// the min of the array along `axis`
|
||||
fn min(self, axis: usize) -> Result<Self::Output, Error>;
|
||||
/// the sum of the array along `axis`
|
||||
fn sum(self, axis: usize) -> Result<Self::Output, Error>;
|
||||
/// the mean of the array along `axis`
|
||||
fn mean(self, axis: usize) -> Result<Self::Output, Error>;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
use tiffwrite::{Bytes, Colors, Compression, IJTiffFile};
|
||||
|
||||
/// options for saving tiff files
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TiffOptions {
|
||||
bar: Option<ProgressBar>,
|
||||
@@ -34,6 +35,7 @@ impl Default for TiffOptions {
|
||||
}
|
||||
|
||||
impl TiffOptions {
|
||||
/// create tiff options with the given parameters
|
||||
pub fn new(
|
||||
bar: Option<ProgressBar>,
|
||||
compression: Option<Compression>,
|
||||
@@ -77,6 +79,7 @@ impl TiffOptions {
|
||||
self.compression = Compression::Deflate
|
||||
}
|
||||
|
||||
/// set the color lookup table for the tiff
|
||||
pub fn set_colors(&mut self, colors: &[String]) -> Result<(), Error> {
|
||||
let colors = colors
|
||||
.iter()
|
||||
@@ -86,6 +89,7 @@ impl TiffOptions {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// set whether an existing tiff file should be overwritten
|
||||
pub fn set_overwrite(&mut self, overwrite: bool) {
|
||||
self.overwrite = overwrite;
|
||||
}
|
||||
@@ -180,6 +184,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// batch convert multiple files to tiff format
|
||||
pub fn batch_to_tiff(
|
||||
files_in: &[PathBuf],
|
||||
files_out: &[PathBuf],
|
||||
|
||||
+14
-319
@@ -1,9 +1,10 @@
|
||||
use crate::axes::{Ax, Axis, Operation, Shape, Slice, SliceInfoElemDef, slice_info};
|
||||
use crate::cache::{ArrayCache, ArrayKeyRef, FrameCache, FrameKeyRef, ReaderKeyRef};
|
||||
use crate::error::Error;
|
||||
use crate::metadata::Metadata;
|
||||
use crate::readers::{Dimensions, DynReader, Frame, Reader};
|
||||
use crate::stats::MinMax;
|
||||
use indexmap::{Equivalent, IndexMap};
|
||||
use indexmap::IndexMap;
|
||||
use itertools::{Itertools, iproduct};
|
||||
use ndarray::{
|
||||
Array, Array0, Array1, Array2, ArrayD, Dimension, IntoDimension, Ix0, Ix1, Ix2, Ix5, IxDyn,
|
||||
@@ -13,15 +14,15 @@ use num::traits::ToBytes;
|
||||
use num::{Bounded, FromPrimitive, ToPrimitive, Zero};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::serde_as;
|
||||
use std::any::{Any, type_name};
|
||||
use std::any::type_name;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::iter::Sum;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::{AddAssign, Deref, Div};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn idx_bnd(idx: isize, bnd: isize) -> Result<isize, Error> {
|
||||
if idx < -bnd {
|
||||
@@ -47,6 +48,7 @@ fn slc_bnd(idx: isize, bnd: isize) -> Result<isize, Error> {
|
||||
}
|
||||
}
|
||||
|
||||
/// a trait for numeric types that can be used as pixel values
|
||||
pub trait Number:
|
||||
'static
|
||||
+ Send
|
||||
@@ -74,318 +76,6 @@ impl<T> Number for T where
|
||||
{
|
||||
}
|
||||
|
||||
/// maximum number of frames held in the cache
|
||||
const DEFAULT_FRAME_CACHE_SIZE: usize = 128;
|
||||
|
||||
/// maximum number of materialized arrays held in the cache
|
||||
const DEFAULT_ARRAY_CACHE_SIZE: usize = 2;
|
||||
|
||||
/// identity of the reader a frame was read from
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
struct ReaderKey {
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
series: usize,
|
||||
position: usize,
|
||||
}
|
||||
|
||||
/// borrowed view of [`ReaderKey`] for cache lookups without allocation.
|
||||
/// hashes byte-identically to [`ReaderKey`] (str/String and Path/PathBuf hash
|
||||
/// the same), so it only matches the same reader identity.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct ReaderKeyRef<'a> {
|
||||
name: &'a str,
|
||||
path: &'a Path,
|
||||
series: usize,
|
||||
position: usize,
|
||||
}
|
||||
|
||||
impl Hash for ReaderKeyRef<'_> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.name.hash(state);
|
||||
self.path.hash(state);
|
||||
self.series.hash(state);
|
||||
self.position.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Equivalent<ReaderKey> for ReaderKeyRef<'_> {
|
||||
fn equivalent(&self, key: &ReaderKey) -> bool {
|
||||
self.name == key.name
|
||||
&& self.path == key.path.as_path()
|
||||
&& self.series == key.series
|
||||
&& self.position == key.position
|
||||
}
|
||||
}
|
||||
|
||||
impl ReaderKeyRef<'_> {
|
||||
fn to_owned(&self) -> ReaderKey {
|
||||
ReaderKey {
|
||||
name: self.name.to_string(),
|
||||
path: self.path.to_path_buf(),
|
||||
series: self.series,
|
||||
position: self.position,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type FrameKey = (ReaderKey, usize, usize, usize);
|
||||
|
||||
/// borrowed view of [`FrameKey`] for cache lookups without allocation.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct FrameKeyRef<'a> {
|
||||
reader: ReaderKeyRef<'a>,
|
||||
c: usize,
|
||||
z: usize,
|
||||
t: usize,
|
||||
}
|
||||
|
||||
impl Hash for FrameKeyRef<'_> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.reader.hash(state);
|
||||
self.c.hash(state);
|
||||
self.z.hash(state);
|
||||
self.t.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Equivalent<FrameKey> for FrameKeyRef<'_> {
|
||||
fn equivalent(&self, key: &FrameKey) -> bool {
|
||||
let (rk, c, z, t) = key;
|
||||
self.reader.equivalent(rk) && self.c == *c && self.z == *z && self.t == *t
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameKeyRef<'_> {
|
||||
fn to_owned(&self) -> FrameKey {
|
||||
(self.reader.to_owned(), self.c, self.z, self.t)
|
||||
}
|
||||
}
|
||||
|
||||
/// identity of a materialized array in the process-wide cache
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
struct ArrayKey {
|
||||
reader: ReaderKey,
|
||||
dtype: &'static str,
|
||||
slice: Vec<SliceInfoElem>,
|
||||
axes: Vec<Axis>,
|
||||
operations: Vec<(Axis, Operation)>,
|
||||
}
|
||||
|
||||
/// borrowed view of [`ArrayKey`] for cache lookups without allocation.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct ArrayKeyRef<'a> {
|
||||
reader: ReaderKeyRef<'a>,
|
||||
dtype: &'static str,
|
||||
slice: &'a [SliceInfoElem],
|
||||
axes: &'a [Axis],
|
||||
operations: &'a IndexMap<Axis, Operation>,
|
||||
}
|
||||
|
||||
impl Hash for ArrayKeyRef<'_> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.reader.hash(state);
|
||||
self.dtype.hash(state);
|
||||
self.slice.hash(state);
|
||||
self.axes.hash(state);
|
||||
self.operations.len().hash(state);
|
||||
for (ax, op) in self.operations.iter() {
|
||||
ax.hash(state);
|
||||
op.hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Equivalent<ArrayKey> for ArrayKeyRef<'_> {
|
||||
fn equivalent(&self, key: &ArrayKey) -> bool {
|
||||
self.reader.equivalent(&key.reader)
|
||||
&& self.dtype == key.dtype
|
||||
&& self.slice == key.slice
|
||||
&& self.axes == key.axes
|
||||
&& self.operations.len() == key.operations.len()
|
||||
&& self
|
||||
.operations
|
||||
.iter()
|
||||
.zip(&key.operations)
|
||||
.all(|((ax, op), (key_ax, key_op))| ax == key_ax && op == key_op)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArrayKeyRef<'_> {
|
||||
fn to_owned(&self) -> ArrayKey {
|
||||
ArrayKey {
|
||||
reader: self.reader.to_owned(),
|
||||
dtype: self.dtype,
|
||||
slice: self.slice.to_vec(),
|
||||
axes: self.axes.to_vec(),
|
||||
operations: self
|
||||
.operations
|
||||
.iter()
|
||||
.map(|(ax, op)| (*ax, op.clone()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// process-wide LRU cache of frames, shared between all views and threads
|
||||
static GLOBAL_FRAME_CACHE: OnceLock<FrameCache> = OnceLock::new();
|
||||
|
||||
/// thread-safe LRU cache of frames read from the underlying reader
|
||||
struct FrameCache {
|
||||
inner: Mutex<FrameCacheInner>,
|
||||
}
|
||||
|
||||
struct FrameCacheInner {
|
||||
map: IndexMap<FrameKey, Arc<Frame>>,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl Default for FrameCache {
|
||||
fn default() -> Self {
|
||||
Self::new(DEFAULT_FRAME_CACHE_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameCache {
|
||||
fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
inner: Mutex::new(FrameCacheInner {
|
||||
map: IndexMap::with_capacity(capacity),
|
||||
capacity,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn global() -> &'static FrameCache {
|
||||
GLOBAL_FRAME_CACHE.get_or_init(FrameCache::default)
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.inner.lock().unwrap().capacity
|
||||
}
|
||||
|
||||
fn set_capacity(&self, capacity: usize) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.capacity = capacity;
|
||||
while inner.map.len() > inner.capacity {
|
||||
inner.map.shift_remove_index(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn get<Q>(&self, key: &Q) -> Option<Arc<Frame>>
|
||||
where
|
||||
Q: ?Sized + Hash + Equivalent<FrameKey>,
|
||||
{
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
if let Some(idx) = inner.map.get_index_of(key) {
|
||||
let (key, frame) = inner.map.shift_remove_index(idx).unwrap();
|
||||
inner.map.insert(key, frame.clone());
|
||||
Some(frame)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(&self, key: FrameKey, frame: Arc<Frame>) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.map.insert(key, frame);
|
||||
while inner.map.len() > inner.capacity {
|
||||
inner.map.shift_remove_index(0);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn len(&self) -> usize {
|
||||
self.inner.lock().unwrap().map.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for FrameCache {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("FrameCache").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
/// process-wide LRU cache of materialized arrays, shared between all views and threads
|
||||
static GLOBAL_ARRAY_CACHE: OnceLock<ArrayCache> = OnceLock::new();
|
||||
|
||||
/// thread-safe LRU cache of materialized arrays produced by `as_array_dyn`
|
||||
struct ArrayCache {
|
||||
inner: Mutex<ArrayCacheInner>,
|
||||
}
|
||||
|
||||
struct ArrayCacheInner {
|
||||
map: IndexMap<ArrayKey, Arc<dyn Any + Send + Sync>>,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl Default for ArrayCache {
|
||||
fn default() -> Self {
|
||||
Self::new(DEFAULT_ARRAY_CACHE_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArrayCache {
|
||||
fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
inner: Mutex::new(ArrayCacheInner {
|
||||
map: IndexMap::with_capacity(capacity),
|
||||
capacity,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn global() -> &'static ArrayCache {
|
||||
GLOBAL_ARRAY_CACHE.get_or_init(ArrayCache::default)
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.inner.lock().unwrap().capacity
|
||||
}
|
||||
|
||||
fn set_capacity(&self, capacity: usize) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.capacity = capacity;
|
||||
while inner.map.len() > inner.capacity {
|
||||
inner.map.shift_remove_index(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn get<T, Q>(&self, key: &Q) -> Option<Arc<ArrayD<T>>>
|
||||
where
|
||||
Q: ?Sized + Hash + Equivalent<ArrayKey>,
|
||||
T: Any + Send + Sync,
|
||||
{
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
if let Some(idx) = inner.map.get_index_of(key) {
|
||||
let (key, array) = inner.map.shift_remove_index(idx).unwrap();
|
||||
inner.map.insert(key, array.clone());
|
||||
array.downcast::<ArrayD<T>>().ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn insert<T: Any + Send + Sync>(&self, key: ArrayKey, array: ArrayD<T>) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.map.insert(key, Arc::new(array));
|
||||
while inner.map.len() > inner.capacity {
|
||||
inner.map.shift_remove_index(0);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn len(&self) -> usize {
|
||||
self.inner.lock().unwrap().map.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ArrayCache {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ArrayCache").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
/// sliceable view on an image file
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@@ -634,6 +324,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
self.shape()[0]
|
||||
}
|
||||
|
||||
/// whether the view has no pixels in the first dimension
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.shape()[0] == 0
|
||||
}
|
||||
@@ -643,6 +334,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
self.shape().into_iter().product()
|
||||
}
|
||||
|
||||
/// the size of the view along an axis, or `None` if the axis is not present
|
||||
pub fn size_ax(&self, ax: Axis) -> Option<usize> {
|
||||
self.axes()
|
||||
.iter()
|
||||
@@ -745,6 +437,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
.with_operations(self.operations.clone()))
|
||||
}
|
||||
|
||||
/// reduce an axis with an operation (max, min, sum or mean), returning a view of lower dimensionality
|
||||
pub fn operate<A: Ax>(
|
||||
&self,
|
||||
axis: A,
|
||||
@@ -1450,6 +1143,7 @@ where
|
||||
|
||||
/// trait to define a function to retrieve the only item in a 0d array
|
||||
pub trait Item {
|
||||
/// the single item in the 0d array, cast to `T`
|
||||
fn item<T>(&self) -> Result<T, Error>
|
||||
where
|
||||
T: Number,
|
||||
@@ -1459,6 +1153,7 @@ pub trait Item {
|
||||
}
|
||||
|
||||
impl<R: Reader> View<Ix5, R> {
|
||||
/// create a view on the image at `path`, parsing series and position from the file name
|
||||
pub fn from_path<P>(path: P) -> Result<Self, Error>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
@@ -1492,6 +1187,7 @@ impl<D: Dimension, R: Reader> Display for View<D, R> {
|
||||
|
||||
/// trait to convert numbers to bytes
|
||||
pub trait ToBytesVec {
|
||||
/// the number as a vector of bytes in native endianness
|
||||
fn to_bytes_vec(&self) -> Vec<u8>;
|
||||
}
|
||||
|
||||
@@ -1516,12 +1212,11 @@ to_bytes_vec_impl!(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::axes::{Axis, Operation};
|
||||
use crate::cache::{ArrayCache, ArrayKey, ArrayKeyRef, FrameCache, ReaderKey, ReaderKeyRef};
|
||||
use crate::error::Error;
|
||||
use crate::readers::{DynReader, Frame, Reader};
|
||||
use crate::stats::MinMax;
|
||||
use crate::view::{
|
||||
ArrayCache, ArrayKey, ArrayKeyRef, FrameCache, Item, ReaderKey, ReaderKeyRef,
|
||||
};
|
||||
use crate::view::Item;
|
||||
use indexmap::IndexMap;
|
||||
use ndarray::{Array, Array4, Array5, NewAxis};
|
||||
use ndarray::{Array2, ArrayD, IxDyn, SliceInfoElem, s};
|
||||
|
||||
Reference in New Issue
Block a user