- add numpy flags
- all python objects pickleble - transforms in python submodule - fix tiffreader pxsize - expose more of transforms to python - loading transforms from yaml
This commit is contained in:
1 parent
6041cda475
commit
41aa841b14
13 files changed
+1202
-283
No files matched your search
+4
-1
@@ -19,7 +19,7 @@ name = "ndbioimage"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
bioformats = { version = "0.1", optional = true }
|
||||
bioformats = { version = "0.1", optional = true, default-features = false, features = ["zarr", "tissuefaxs"] }
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
console = { version = "0.16", optional = true }
|
||||
downloader = { version = "0.2", optional = true, default-features = false, features = ["rustls-tls"] }
|
||||
@@ -83,5 +83,8 @@ features = ["bioformats_java", "bioformats_rust", "czi", "movie", "tiffseq", "ti
|
||||
[profile.test]
|
||||
inherits = "release"
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
||||
[profile.dev.package.backtrace]
|
||||
opt-level = 3
|
||||
+33
-10
@@ -13,8 +13,14 @@ from numpy.typing import ArrayLike
|
||||
os.environ["RUST_BACKTRACE"] = "full"
|
||||
os.environ["COLORBT_SHOW_HIDDEN"] = "1"
|
||||
|
||||
from . import ndbioimage_rs as rs
|
||||
from .ndbioimage_rs import Imread, Transform, Transforms
|
||||
from .ndbioimage_rs import Imread, generate_stub
|
||||
|
||||
try:
|
||||
from .ndbioimage_rs import transforms
|
||||
|
||||
sys.modules["ndbioimage.transforms"] = transforms
|
||||
except (ImportError, ModuleNotFoundError):
|
||||
pass
|
||||
|
||||
try:
|
||||
from .ndbioimage_rs import batch_to_tiff
|
||||
@@ -23,8 +29,12 @@ except ImportError:
|
||||
|
||||
try:
|
||||
import ome_metadata # noqa
|
||||
except ModuleNotFoundError:
|
||||
sys.modules["ome_metadata"] = rs.ome_metadata # noqa
|
||||
except (ImportError, ModuleNotFoundError):
|
||||
from .ndbioimage_rs import ome_metadata # noqa
|
||||
|
||||
sys.modules["ome_metadata"] = ome_metadata
|
||||
import ome_metadata # noqa
|
||||
|
||||
|
||||
try:
|
||||
__version__ = version(Path(__file__).parent.name)
|
||||
@@ -58,13 +68,26 @@ def ndbioimage_generate_stub():
|
||||
path = Path(sys.argv[1]).resolve()
|
||||
else:
|
||||
path = Path.cwd().resolve()
|
||||
if (path / "py" / "ndbioimage" / "__init__.py").exists():
|
||||
rs.generate_stub(str(path)) # noqa
|
||||
nd = path / "py" / "ndbioimage"
|
||||
if (nd / "__init__.pyi").exists():
|
||||
(nd / "__init__.pyi").rename(nd / "ndbioimage.pyi")
|
||||
try:
|
||||
if (nd / "__init__.py").exists():
|
||||
generate_stub(str(path)) # noqa
|
||||
else:
|
||||
raise ModuleNotFoundError(str(path / "py" / "ndbioimage" / "__init__.py"))
|
||||
(path / "py" / "ndbioimage" / "__init__.pyi").rename(
|
||||
path / "py" / "ndbioimage" / "ndbioimage_rs.pyi"
|
||||
)
|
||||
raise ModuleNotFoundError(str(nd / "__init__.py"))
|
||||
rs = nd / "ndbioimage_rs"
|
||||
rs.mkdir(parents=True, exist_ok=True)
|
||||
(nd / "__init__.pyi").rename(rs / "__init__.pyi")
|
||||
for stub in nd.glob("**/__init__.pyi"):
|
||||
if stub.parent != rs:
|
||||
stub.rename((rs / stub.parent.name).with_suffix(".pyi"))
|
||||
stub.parent.rmdir()
|
||||
except Exception as e:
|
||||
raise e
|
||||
finally:
|
||||
if (nd / "ndbioimage.pyi").exists():
|
||||
(nd / "ndbioimage.pyi").rename(nd / "__init__.pyi")
|
||||
|
||||
|
||||
R = TypeVar("R")
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
from .ndbioimage_rs import Imread, Shape
|
||||
|
||||
__all__ = ["Imread", "Shape"]
|
||||
@@ -9,15 +9,50 @@ import typing
|
||||
import numpy
|
||||
import numpy.typing
|
||||
|
||||
from . import transforms
|
||||
|
||||
__all__ = [
|
||||
"Flags",
|
||||
"Imread",
|
||||
"Shape",
|
||||
"Transform",
|
||||
"Transforms",
|
||||
"batch_to_tiff",
|
||||
"generate_stub",
|
||||
"main",
|
||||
"transforms",
|
||||
]
|
||||
|
||||
class Flags:
|
||||
r"""
|
||||
https://numpy.org/doc/stable/reference/generated/numpy.ndarray.flags.html
|
||||
"""
|
||||
@property
|
||||
def c_contiguous(self) -> builtins.bool: ...
|
||||
@property
|
||||
def f_contiguous(self) -> builtins.bool: ...
|
||||
@property
|
||||
def owndata(self) -> builtins.bool: ...
|
||||
@property
|
||||
def writable(self) -> builtins.bool: ...
|
||||
@property
|
||||
def aligned(self) -> builtins.bool: ...
|
||||
@property
|
||||
def writebackifcopy(self) -> builtins.bool: ...
|
||||
@property
|
||||
def fnc(self) -> builtins.bool: ...
|
||||
@property
|
||||
def forc(self) -> builtins.bool: ...
|
||||
@property
|
||||
def behaved(self) -> builtins.bool: ...
|
||||
@property
|
||||
def carray(self) -> builtins.bool: ...
|
||||
@property
|
||||
def farray(self) -> builtins.bool: ...
|
||||
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
|
||||
def __hash__(self) -> builtins.int: ...
|
||||
def __new__(cls) -> Flags: ...
|
||||
def __getnewargs__(self) -> None: ...
|
||||
def __getitem__(self, item: builtins.str) -> builtins.bool: ...
|
||||
|
||||
class Imread:
|
||||
r"""
|
||||
class to read image files, while taking good care of important metadata,
|
||||
@@ -48,16 +83,28 @@ class Imread:
|
||||
|
||||
TODO: argmax, argmin, nanmax, nanmin, nanmean, nansum, nanstd, nanvar, std, var
|
||||
"""
|
||||
|
||||
available_readers: builtins.list[builtins.str]
|
||||
@property
|
||||
def flags(self) -> Flags:
|
||||
r"""
|
||||
https://numpy.org/doc/stable/reference/generated/numpy.ndarray.flags.html
|
||||
"""
|
||||
@property
|
||||
def reader_name(self) -> builtins.str:
|
||||
r"""
|
||||
the name of the reader used to open the file
|
||||
"""
|
||||
@property
|
||||
def transform(self) -> Transforms:
|
||||
def transforms(self) -> transforms.Transforms:
|
||||
r"""
|
||||
get the transformation
|
||||
"""
|
||||
@transforms.setter
|
||||
def transforms(self, value: transforms.Transforms) -> None:
|
||||
r"""
|
||||
set the transformation
|
||||
"""
|
||||
@property
|
||||
def path(self) -> pathlib.Path:
|
||||
r"""
|
||||
@@ -183,6 +230,15 @@ class Imread:
|
||||
r"""
|
||||
new view on a file at path, open series #, open as dtype: (u)int(8/16/32) or float(32/64)
|
||||
"""
|
||||
def set_flags(
|
||||
self,
|
||||
write: typing.Optional[builtins.bool] = None,
|
||||
align: typing.Optional[builtins.bool] = None,
|
||||
uic: typing.Optional[builtins.bool] = None,
|
||||
) -> None:
|
||||
r"""
|
||||
https://numpy.org/doc/stable/reference/generated/numpy.ndarray.setflags.html#numpy.ndarray.setflags
|
||||
"""
|
||||
@staticmethod
|
||||
def get_positions(
|
||||
path: str | pathlib.Path | Imread | bytes,
|
||||
@@ -210,22 +266,31 @@ class Imread:
|
||||
) -> Imread:
|
||||
r"""
|
||||
return a new view with transformations applied (channel alignment, drift correction)
|
||||
backward compatibility
|
||||
"""
|
||||
def set_transform(self, transform: Transforms) -> None:
|
||||
def with_transforms(
|
||||
self,
|
||||
channels: builtins.bool = True,
|
||||
drift: builtins.bool = False,
|
||||
file: typing.Optional[typing.Any] = None,
|
||||
bead_files: typing.Optional[typing.Any] = None,
|
||||
main_channel: typing.Optional[builtins.int] = None,
|
||||
default_transform: typing.Optional[typing.Sequence[builtins.float]] = None,
|
||||
) -> Imread:
|
||||
r"""
|
||||
set the transformation
|
||||
return a new view with transformations applied (channel alignment, drift correction)
|
||||
"""
|
||||
def load_transform_from_yaml(
|
||||
def load_transforms_from_yaml(
|
||||
self, path: builtins.str | os.PathLike | pathlib.Path
|
||||
) -> None: ...
|
||||
def calculate_channel_transforms_2d(
|
||||
self, main_channel: builtins.int
|
||||
) -> builtins.list[Transform]: ...
|
||||
) -> builtins.dict[builtins.str, transforms.Transform]: ...
|
||||
def calculate_channel_transforms_3d(
|
||||
self, main_channel: builtins.int
|
||||
) -> builtins.list[Transform]: ...
|
||||
def calculate_drift_transform_2d(self) -> builtins.list[Transform]: ...
|
||||
def calculate_drift_transform_3d(self) -> builtins.list[Transform]: ...
|
||||
) -> builtins.dict[builtins.str, transforms.Transform]: ...
|
||||
def calculate_drift_transforms_2d(self) -> builtins.list[transforms.Transform]: ...
|
||||
def calculate_drift_transforms_3d(self) -> builtins.list[transforms.Transform]: ...
|
||||
def squeeze(self) -> numpy.ndarray | int | float: ...
|
||||
def close(self) -> None:
|
||||
r"""
|
||||
@@ -678,7 +743,7 @@ class Shape:
|
||||
"""
|
||||
def __getitem__(
|
||||
self, idx: str | int | None | Ellipsis | slice | list[int] | tuple[int]
|
||||
) -> typing.Optional[int | list[int]]:
|
||||
) -> typing.Optional[int | tuple[int]]:
|
||||
r"""
|
||||
get dimension size by index or axis name
|
||||
"""
|
||||
@@ -691,127 +756,6 @@ class Shape:
|
||||
convert shape to a list of dimension sizes in order
|
||||
"""
|
||||
|
||||
class Transform:
|
||||
@property
|
||||
def parameters(self) -> builtins.list[builtins.float]: ...
|
||||
@parameters.setter
|
||||
def parameters(self, value: typing.Sequence[builtins.float]) -> None: ...
|
||||
@property
|
||||
def dparameters(self) -> builtins.list[builtins.float]: ...
|
||||
@dparameters.setter
|
||||
def dparameters(self, value: typing.Sequence[builtins.float]) -> None: ...
|
||||
@property
|
||||
def center(self) -> builtins.list[builtins.float]: ...
|
||||
@center.setter
|
||||
def center(self, value: typing.Sequence[builtins.float]) -> None: ...
|
||||
@property
|
||||
def shape(self) -> builtins.list[builtins.int]: ...
|
||||
@shape.setter
|
||||
def shape(self, value: typing.Sequence[builtins.int]) -> None: ...
|
||||
@property
|
||||
def ndim(self) -> builtins.int: ...
|
||||
@property
|
||||
def matrix(self) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
@matrix.setter
|
||||
def matrix(self, value: numpy.typing.ArrayLike) -> None: ...
|
||||
@property
|
||||
def dmatrix(self) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
@dmatrix.setter
|
||||
def dmatrix(self, value: numpy.typing.ArrayLike) -> None: ...
|
||||
@property
|
||||
def inverse(self) -> Transform: ...
|
||||
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
|
||||
def __new__(
|
||||
cls,
|
||||
parameters: typing.Sequence[builtins.float],
|
||||
shape: typing.Sequence[builtins.int],
|
||||
center: typing.Optional[typing.Sequence[builtins.float]] = None,
|
||||
) -> Transform: ...
|
||||
def __getnewargs__(
|
||||
self,
|
||||
) -> tuple[
|
||||
builtins.list[builtins.float],
|
||||
builtins.list[builtins.int],
|
||||
typing.Optional[builtins.list[builtins.float]],
|
||||
]: ...
|
||||
def __getstate__(self) -> builtins.list[builtins.float]: ...
|
||||
def __setstate__(self, state: typing.Sequence[builtins.float]) -> None: ...
|
||||
def __add__(self, other: Transform) -> Transform: ...
|
||||
def __radd__(self, other: Transform) -> Transform: ...
|
||||
def __sub__(self, other: Transform) -> Transform: ...
|
||||
def __rsub__(self, other: Transform) -> Transform: ...
|
||||
def __mul__(self, other: Transform | float) -> Transform: ...
|
||||
def __rmul__(self, other: Transform | float) -> Transform: ...
|
||||
def __truediv__(self, other: builtins.float) -> Transform: ...
|
||||
def adapt(
|
||||
self,
|
||||
center: typing.Sequence[builtins.float],
|
||||
shape: typing.Sequence[builtins.int],
|
||||
) -> None: ...
|
||||
@staticmethod
|
||||
def from_scaling(scaling: typing.Sequence[builtins.float]) -> Transform: ...
|
||||
@staticmethod
|
||||
def from_translation(translation: typing.Sequence[builtins.float]) -> Transform: ...
|
||||
@staticmethod
|
||||
def from_rotation(
|
||||
theta: builtins.float, center: typing.Sequence[builtins.float]
|
||||
) -> Transform: ...
|
||||
def with_scaling(self, scaling: typing.Sequence[builtins.float]) -> Transform: ...
|
||||
def with_translation(
|
||||
self, translation: typing.Sequence[builtins.float]
|
||||
) -> Transform: ...
|
||||
def with_rotation(
|
||||
self, theta: builtins.float, center: typing.Sequence[builtins.float]
|
||||
) -> Transform: ...
|
||||
def interpolate(
|
||||
self, order: builtins.int, image: numpy.typing.ArrayLike
|
||||
) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
def interpolate_par(
|
||||
self, order: builtins.int, image: numpy.typing.ArrayLike
|
||||
) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
def is_unity(self) -> builtins.bool: ...
|
||||
def transform_point(
|
||||
self, point: numpy.typing.ArrayLike
|
||||
) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
def transform_points(
|
||||
self, points: numpy.typing.ArrayLike
|
||||
) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
@staticmethod
|
||||
def register(
|
||||
fixed: numpy.typing.ArrayLike,
|
||||
moving: numpy.typing.ArrayLike,
|
||||
fixed_mu: typing.Sequence[typing.Optional[builtins.float]],
|
||||
initial_guess: typing.Optional[typing.Sequence[builtins.float]] = None,
|
||||
) -> Transform: ...
|
||||
@staticmethod
|
||||
def register_affine(
|
||||
fixed: numpy.typing.ArrayLike, moving: numpy.typing.ArrayLike
|
||||
) -> Transform: ...
|
||||
@staticmethod
|
||||
def register_translation(
|
||||
fixed: numpy.typing.ArrayLike, moving: numpy.typing.ArrayLike
|
||||
) -> Transform: ...
|
||||
|
||||
class Transforms:
|
||||
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
|
||||
def __getstate__(self) -> builtins.list[builtins.int]: ...
|
||||
def __setstate__(self, state: typing.Sequence[builtins.int]) -> None: ...
|
||||
@staticmethod
|
||||
def load(path: builtins.str | os.PathLike | pathlib.Path) -> Transforms: ...
|
||||
def save(self, path: builtins.str | os.PathLike | pathlib.Path) -> None: ...
|
||||
@staticmethod
|
||||
def calculate_channel_transforms_2d(
|
||||
bead_files: typing.Sequence[builtins.str | os.PathLike | pathlib.Path],
|
||||
main_channel: builtins.int,
|
||||
default_transform: typing.Optional[Transform],
|
||||
) -> builtins.list[Transform]: ...
|
||||
@staticmethod
|
||||
def calculate_channel_transforms_3d(
|
||||
bead_files: typing.Sequence[builtins.str | os.PathLike | pathlib.Path],
|
||||
main_channel: builtins.int,
|
||||
default_transform: typing.Optional[Transform],
|
||||
) -> builtins.list[Transform]: ...
|
||||
|
||||
def batch_to_tiff(
|
||||
files_in: typing.Sequence[builtins.str | os.PathLike | pathlib.Path],
|
||||
files_out: typing.Sequence[builtins.str | os.PathLike | pathlib.Path],
|
||||
@@ -827,6 +771,11 @@ def batch_to_tiff(
|
||||
batch convert multiple image files to TIFF format
|
||||
"""
|
||||
|
||||
def generate_stub(dest_path: builtins.str) -> None:
|
||||
r"""
|
||||
generates ndbioimage/__init__.pyi
|
||||
"""
|
||||
|
||||
def main() -> None:
|
||||
r"""
|
||||
main entry point for the command-line interface
|
||||
@@ -0,0 +1,244 @@
|
||||
# This file is automatically generated by pyo3_stub_gen
|
||||
# ruff: noqa: E501, F401, F403, F405
|
||||
|
||||
import builtins
|
||||
import os
|
||||
import pathlib
|
||||
import typing
|
||||
|
||||
import numpy
|
||||
import numpy.typing
|
||||
|
||||
__all__ = [
|
||||
"Optimizer",
|
||||
"RegistrationStep",
|
||||
"SamplingArg",
|
||||
"Sigma",
|
||||
"Transform",
|
||||
"Transforms",
|
||||
]
|
||||
|
||||
class Optimizer:
|
||||
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
|
||||
def __new__(cls, optimizer: builtins.str) -> Optimizer: ...
|
||||
def __getnewargs__(self) -> tuple[builtins.str]: ...
|
||||
@staticmethod
|
||||
def lbfgs() -> Optimizer: ...
|
||||
@staticmethod
|
||||
def asgd() -> Optimizer: ...
|
||||
|
||||
class RegistrationStep:
|
||||
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
|
||||
def __new__(
|
||||
cls,
|
||||
sigma: Sigma,
|
||||
sample: SamplingArg,
|
||||
n_bins: builtins.int,
|
||||
tolerance: builtins.float,
|
||||
edge: builtins.float,
|
||||
max_iterations: builtins.int,
|
||||
learning_rate: builtins.float,
|
||||
optimizer: Optimizer,
|
||||
) -> RegistrationStep: ...
|
||||
def __getnewargs__(
|
||||
self,
|
||||
) -> tuple[
|
||||
Sigma,
|
||||
SamplingArg,
|
||||
builtins.int,
|
||||
builtins.float,
|
||||
builtins.float,
|
||||
builtins.int,
|
||||
builtins.float,
|
||||
Optimizer,
|
||||
]: ...
|
||||
@staticmethod
|
||||
def default_steps(
|
||||
ndim: builtins.int, n: builtins.int
|
||||
) -> builtins.list[RegistrationStep]: ...
|
||||
|
||||
class SamplingArg:
|
||||
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
|
||||
def __new__(
|
||||
cls,
|
||||
fixed: typing.Optional[builtins.int] = None,
|
||||
fixed_at: typing.Optional[
|
||||
typing.Sequence[typing.Sequence[builtins.float]]
|
||||
] = None,
|
||||
random: typing.Optional[builtins.int] = None,
|
||||
) -> SamplingArg: ...
|
||||
def __getnewargs__(
|
||||
self,
|
||||
) -> tuple[
|
||||
typing.Optional[builtins.int],
|
||||
typing.Optional[builtins.list[builtins.list[builtins.float]]],
|
||||
typing.Optional[builtins.int],
|
||||
]: ...
|
||||
@staticmethod
|
||||
def fixed(size: builtins.int) -> SamplingArg: ...
|
||||
@staticmethod
|
||||
def fixed_at(
|
||||
f: typing.Sequence[typing.Sequence[builtins.float]],
|
||||
) -> SamplingArg: ...
|
||||
@staticmethod
|
||||
def random(size: builtins.int) -> SamplingArg: ...
|
||||
|
||||
class Sigma:
|
||||
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
|
||||
def __new__(
|
||||
cls,
|
||||
absolute: typing.Optional[typing.Sequence[builtins.float]] = None,
|
||||
relative: typing.Optional[typing.Sequence[builtins.float]] = None,
|
||||
) -> Sigma: ...
|
||||
def __getnewargs__(
|
||||
self,
|
||||
) -> tuple[builtins.str, typing.Optional[builtins.list[builtins.float]]]: ...
|
||||
@staticmethod
|
||||
def none() -> Sigma: ...
|
||||
@staticmethod
|
||||
def absolute(sigma: typing.Sequence[builtins.float]) -> Sigma: ...
|
||||
@staticmethod
|
||||
def relative(sigma: typing.Sequence[builtins.float]) -> Sigma: ...
|
||||
def smooth(
|
||||
self, array: numpy.typing.ArrayLike
|
||||
) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
def sigma(
|
||||
self, shape: typing.Sequence[builtins.int]
|
||||
) -> typing.Optional[builtins.list[builtins.float]]: ...
|
||||
|
||||
class Transform:
|
||||
@property
|
||||
def parameters(self) -> builtins.list[builtins.float]: ...
|
||||
@parameters.setter
|
||||
def parameters(self, value: typing.Sequence[builtins.float]) -> None: ...
|
||||
@property
|
||||
def dparameters(self) -> builtins.list[builtins.float]: ...
|
||||
@dparameters.setter
|
||||
def dparameters(self, value: typing.Sequence[builtins.float]) -> None: ...
|
||||
@property
|
||||
def center(self) -> builtins.list[builtins.float]: ...
|
||||
@center.setter
|
||||
def center(self, value: typing.Sequence[builtins.float]) -> None: ...
|
||||
@property
|
||||
def shape(self) -> builtins.list[builtins.int]: ...
|
||||
@shape.setter
|
||||
def shape(self, value: typing.Sequence[builtins.int]) -> None: ...
|
||||
@property
|
||||
def ndim(self) -> builtins.int: ...
|
||||
@property
|
||||
def matrix(self) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
@matrix.setter
|
||||
def matrix(self, value: numpy.typing.ArrayLike) -> None: ...
|
||||
@property
|
||||
def dmatrix(self) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
@dmatrix.setter
|
||||
def dmatrix(self, value: numpy.typing.ArrayLike) -> None: ...
|
||||
@property
|
||||
def inverse(self) -> Transform: ...
|
||||
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
|
||||
def __new__(
|
||||
cls,
|
||||
parameters: typing.Optional[typing.Sequence[builtins.float]] = None,
|
||||
shape: typing.Optional[typing.Sequence[builtins.int]] = None,
|
||||
center: typing.Optional[typing.Sequence[builtins.float]] = None,
|
||||
) -> Transform: ...
|
||||
def __getnewargs__(
|
||||
self,
|
||||
) -> tuple[
|
||||
builtins.list[builtins.float],
|
||||
builtins.list[builtins.int],
|
||||
typing.Optional[builtins.list[builtins.float]],
|
||||
]: ...
|
||||
def __getstate__(self) -> builtins.list[builtins.float]: ...
|
||||
def __setstate__(self, state: typing.Sequence[builtins.float]) -> None: ...
|
||||
def __add__(self, other: Transform) -> Transform: ...
|
||||
def __radd__(self, other: Transform) -> Transform: ...
|
||||
def __sub__(self, other: Transform) -> Transform: ...
|
||||
def __rsub__(self, other: Transform) -> Transform: ...
|
||||
def __mul__(self, other: Transform | float) -> Transform: ...
|
||||
def __rmul__(self, other: Transform | float) -> Transform: ...
|
||||
def __truediv__(self, other: builtins.float) -> Transform: ...
|
||||
def adapt(
|
||||
self,
|
||||
center: typing.Sequence[builtins.float],
|
||||
shape: typing.Sequence[builtins.int],
|
||||
) -> None: ...
|
||||
@staticmethod
|
||||
def from_scaling(scaling: typing.Sequence[builtins.float]) -> Transform: ...
|
||||
@staticmethod
|
||||
def from_translation(translation: typing.Sequence[builtins.float]) -> Transform: ...
|
||||
@staticmethod
|
||||
def from_rotation(
|
||||
theta: builtins.float, center: typing.Sequence[builtins.float]
|
||||
) -> Transform: ...
|
||||
def with_scaling(self, scaling: typing.Sequence[builtins.float]) -> Transform: ...
|
||||
def with_translation(
|
||||
self, translation: typing.Sequence[builtins.float]
|
||||
) -> Transform: ...
|
||||
def with_rotation(
|
||||
self, theta: builtins.float, center: typing.Sequence[builtins.float]
|
||||
) -> Transform: ...
|
||||
def interpolate(
|
||||
self, order: builtins.int, image: numpy.typing.ArrayLike
|
||||
) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
def interpolate_par(
|
||||
self, order: builtins.int, image: numpy.typing.ArrayLike
|
||||
) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
def is_unity(self) -> builtins.bool: ...
|
||||
def transform_point(
|
||||
self, point: numpy.typing.ArrayLike
|
||||
) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
def transform_points(
|
||||
self, points: numpy.typing.ArrayLike
|
||||
) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
@staticmethod
|
||||
def register(
|
||||
fixed: numpy.typing.ArrayLike,
|
||||
moving: numpy.typing.ArrayLike,
|
||||
fixed_mu: typing.Sequence[typing.Optional[builtins.float]],
|
||||
initial_guess: typing.Optional[typing.Sequence[builtins.float]] = None,
|
||||
) -> Transform: ...
|
||||
@staticmethod
|
||||
def register_affine(
|
||||
fixed: numpy.typing.ArrayLike, moving: numpy.typing.ArrayLike
|
||||
) -> Transform: ...
|
||||
@staticmethod
|
||||
def register_translation(
|
||||
fixed: numpy.typing.ArrayLike, moving: numpy.typing.ArrayLike
|
||||
) -> Transform: ...
|
||||
|
||||
class Transforms:
|
||||
@property
|
||||
def channel_transforms(self) -> builtins.dict[builtins.str, Transform]: ...
|
||||
@channel_transforms.setter
|
||||
def channel_transforms(
|
||||
self, value: typing.Mapping[builtins.str, Transform]
|
||||
) -> None: ...
|
||||
@property
|
||||
def drift_transforms(self) -> builtins.list[Transform]: ...
|
||||
@drift_transforms.setter
|
||||
def drift_transforms(self, value: typing.Sequence[Transform]) -> None: ...
|
||||
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
|
||||
def __new__(
|
||||
cls,
|
||||
channel: typing.Mapping[builtins.str, Transform] = {},
|
||||
drift: typing.Sequence[Transform] = [],
|
||||
) -> Transforms: ...
|
||||
def __getnewargs__(
|
||||
self,
|
||||
) -> tuple[builtins.dict[builtins.str, Transform], builtins.list[Transform]]: ...
|
||||
@staticmethod
|
||||
def load(path: builtins.str | os.PathLike | pathlib.Path) -> Transforms: ...
|
||||
def save(self, path: builtins.str | os.PathLike | pathlib.Path) -> None: ...
|
||||
@staticmethod
|
||||
def calculate_channel_transforms_2d(
|
||||
bead_files: typing.Sequence[builtins.str | os.PathLike | pathlib.Path],
|
||||
main_channel: builtins.int,
|
||||
default_transform: typing.Optional[Transform],
|
||||
) -> builtins.dict[builtins.str, Transform]: ...
|
||||
@staticmethod
|
||||
def calculate_channel_transforms_3d(
|
||||
bead_files: typing.Sequence[builtins.str | os.PathLike | pathlib.Path],
|
||||
main_channel: builtins.int,
|
||||
default_transform: typing.Optional[Transform],
|
||||
) -> builtins.dict[builtins.str, Transform]: ...
|
||||
@@ -0,0 +1,17 @@
|
||||
from .ndbioimage_rs.transforms import (
|
||||
Optimizer,
|
||||
RegistrationStep,
|
||||
SamplingArg,
|
||||
Sigma,
|
||||
Transform,
|
||||
Transforms,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Optimizer",
|
||||
"RegistrationStep",
|
||||
"SamplingArg",
|
||||
"Sigma",
|
||||
"Transform",
|
||||
"Transforms",
|
||||
]
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "ndbioimage"
|
||||
version = "2027.0.4"
|
||||
version = "2027.0.5"
|
||||
requires-python = ">=3.10"
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: MIT License",
|
||||
|
||||
@@ -41,6 +41,7 @@ pub(crate) fn ndbioimage_file() -> PathBuf {
|
||||
}
|
||||
|
||||
/// generates ndbioimage/__init__.pyi
|
||||
#[gen_stub_pyfunction]
|
||||
#[pyfunction]
|
||||
pub fn generate_stub(dest_path: String) -> PyResult<()> {
|
||||
Ok(StubInfo::from_project_root(
|
||||
@@ -84,14 +85,31 @@ mod ndbioimage_rs {
|
||||
#[pymodule_export]
|
||||
use super::imread::PyShape;
|
||||
|
||||
#[pymodule_export]
|
||||
use super::imread::PyFlags;
|
||||
|
||||
#[pymodule_export]
|
||||
use ome_metadata::py::ome_metadata;
|
||||
|
||||
#[cfg(feature = "transforms")]
|
||||
#[pymodule]
|
||||
mod transforms {
|
||||
#[pymodule_export]
|
||||
use super::transforms::PyTransform;
|
||||
use super::super::transforms::PyTransform;
|
||||
|
||||
#[cfg(feature = "transforms")]
|
||||
#[pymodule_export]
|
||||
use super::transforms::PyTransforms;
|
||||
use super::super::transforms::PyTransforms;
|
||||
|
||||
#[pymodule_export]
|
||||
use super::super::transforms::PyRegistrationStep;
|
||||
|
||||
#[pymodule_export]
|
||||
use super::super::transforms::PySigma;
|
||||
|
||||
#[pymodule_export]
|
||||
use super::super::transforms::PySamplingArg;
|
||||
|
||||
#[pymodule_export]
|
||||
use super::super::transforms::PyOptimizer;
|
||||
}
|
||||
}
|
||||
+178
-19
@@ -15,7 +15,9 @@ use numpy::{
|
||||
};
|
||||
use ome_metadata::Ome;
|
||||
use postcard::{from_bytes, to_stdvec};
|
||||
use pyo3::exceptions::{PyIndexError, PyNotImplementedError, PyTypeError, PyValueError};
|
||||
use pyo3::exceptions::{
|
||||
PyIndexError, PyKeyError, PyNotImplementedError, PyTypeError, PyValueError,
|
||||
};
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyBytes, PyEllipsis, PyInt, PyList, PyNone, PySlice, PyString, PyTuple};
|
||||
pub(crate) use pyo3::{
|
||||
@@ -25,7 +27,7 @@ pub(crate) use pyo3::{
|
||||
use pyo3_stub_gen::derive::*;
|
||||
use pyo3_stub_gen::inventory::submit;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// class to read image files, while taking good care of important metadata,
|
||||
@@ -204,6 +206,42 @@ impl PyView {
|
||||
}
|
||||
}
|
||||
|
||||
/// https://numpy.org/doc/stable/reference/generated/numpy.ndarray.flags.html
|
||||
#[getter]
|
||||
fn flags(&self) -> PyFlags {
|
||||
PyFlags::new()
|
||||
}
|
||||
|
||||
/// https://numpy.org/doc/stable/reference/generated/numpy.ndarray.setflags.html#numpy.ndarray.setflags
|
||||
#[pyo3(signature = (write = None, align = None, uic = None))]
|
||||
fn set_flags(
|
||||
&self,
|
||||
write: Option<bool>,
|
||||
align: Option<bool>,
|
||||
uic: Option<bool>,
|
||||
) -> PyResult<()> {
|
||||
if let Some(write) = write
|
||||
&& write
|
||||
{
|
||||
Err(PyValueError::new_err("write cannot be set True"))
|
||||
} else if let Some(align) = align
|
||||
&& align
|
||||
{
|
||||
Err(PyValueError::new_err("align cannot be set True"))
|
||||
} else if let Some(uic) = uic
|
||||
&& uic
|
||||
{
|
||||
Err(PyValueError::new_err("uic cannot be set True"))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[classattr]
|
||||
fn available_readers() -> Vec<String> {
|
||||
DynReader::available_readers()
|
||||
}
|
||||
|
||||
/// get all available positions (series) in the file
|
||||
#[staticmethod]
|
||||
fn get_positions<'py>(
|
||||
@@ -232,11 +270,34 @@ impl PyView {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// return a new view with transformations applied (channel alignment, drift correction)
|
||||
/// backward compatibility
|
||||
#[cfg(feature = "transforms")]
|
||||
#[pyo3(signature = (channels = true, drift = false, file = None, bead_files = None, main_channel = None, default_transform = None))]
|
||||
fn with_transform<'py>(
|
||||
&self,
|
||||
channels: bool,
|
||||
drift: bool,
|
||||
file: Option<Bound<'py, PyAny>>,
|
||||
bead_files: Option<Bound<'py, PyAny>>,
|
||||
main_channel: Option<usize>,
|
||||
default_transform: Option<Vec<f64>>,
|
||||
) -> PyResult<PyView> {
|
||||
self.with_transforms(
|
||||
channels,
|
||||
drift,
|
||||
file,
|
||||
bead_files,
|
||||
main_channel,
|
||||
default_transform,
|
||||
)
|
||||
}
|
||||
|
||||
/// return a new view with transformations applied (channel alignment, drift correction)
|
||||
#[cfg(feature = "transforms")]
|
||||
#[expect(unused_variables)]
|
||||
#[pyo3(signature = (channels = true, drift = false, file = None, bead_files = None, main_channel = None, default_transform = None))]
|
||||
fn with_transform<'py>(
|
||||
fn with_transforms<'py>(
|
||||
&self,
|
||||
channels: bool,
|
||||
drift: bool,
|
||||
@@ -251,21 +312,22 @@ impl PyView {
|
||||
/// get the transformation
|
||||
#[cfg(feature = "transforms")]
|
||||
#[getter]
|
||||
fn get_transform(&self) -> PyResult<PyTransforms> {
|
||||
fn get_transforms(&self) -> PyResult<PyTransforms> {
|
||||
Ok(PyTransforms {
|
||||
inner: self.view.transforms.clone(),
|
||||
inner: self.view.get_transforms().clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// set the transformation
|
||||
#[cfg(feature = "transforms")]
|
||||
fn set_transform(&mut self, transform: PyTransforms) {
|
||||
self.view.transforms = transform.inner;
|
||||
#[setter]
|
||||
fn set_transforms(&mut self, transform: PyTransforms) {
|
||||
self.view.set_transforms(transform.inner);
|
||||
}
|
||||
|
||||
#[cfg(feature = "transforms")]
|
||||
fn load_transform_from_yaml(&mut self, path: PathBuf) -> PyResult<()> {
|
||||
self.view.load_transform_from_yaml(path.as_path())?;
|
||||
fn load_transforms_from_yaml(&mut self, path: PathBuf) -> PyResult<()> {
|
||||
self.view.load_transforms_from_yaml(path.as_path())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -274,14 +336,19 @@ impl PyView {
|
||||
&self,
|
||||
py: Python,
|
||||
main_channel: usize,
|
||||
) -> PyResult<Vec<PyTransform>> {
|
||||
) -> PyResult<HashMap<String, PyTransform>> {
|
||||
py.detach(|| {
|
||||
Ok(self
|
||||
.view
|
||||
.calculate_channel_transforms_2d(main_channel)?
|
||||
.into_iter()
|
||||
.map(|t| PyTransform {
|
||||
.map(|(n, t)| {
|
||||
(
|
||||
n,
|
||||
PyTransform {
|
||||
inner: t.into_dyn(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect())
|
||||
})
|
||||
@@ -292,25 +359,30 @@ impl PyView {
|
||||
&self,
|
||||
py: Python,
|
||||
main_channel: usize,
|
||||
) -> PyResult<Vec<PyTransform>> {
|
||||
) -> PyResult<HashMap<String, PyTransform>> {
|
||||
py.detach(|| {
|
||||
Ok(self
|
||||
.view
|
||||
.calculate_channel_transforms_3d(main_channel)?
|
||||
.into_iter()
|
||||
.map(|t| PyTransform {
|
||||
.map(|(n, t)| {
|
||||
(
|
||||
n,
|
||||
PyTransform {
|
||||
inner: t.into_dyn(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect())
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "transforms")]
|
||||
fn calculate_drift_transform_2d(&self, py: Python) -> PyResult<Vec<PyTransform>> {
|
||||
fn calculate_drift_transforms_2d(&self, py: Python) -> PyResult<Vec<PyTransform>> {
|
||||
py.detach(|| {
|
||||
Ok(self
|
||||
.view
|
||||
.calculate_drift_transform_2d()?
|
||||
.calculate_drift_transforms_2d()?
|
||||
.into_iter()
|
||||
.map(|t| PyTransform {
|
||||
inner: t.into_dyn(),
|
||||
@@ -320,11 +392,11 @@ impl PyView {
|
||||
}
|
||||
|
||||
#[cfg(feature = "transforms")]
|
||||
fn calculate_drift_transform_3d(&self, py: Python) -> PyResult<Vec<PyTransform>> {
|
||||
fn calculate_drift_transforms_3d(&self, py: Python) -> PyResult<Vec<PyTransform>> {
|
||||
py.detach(|| {
|
||||
Ok(self
|
||||
.view
|
||||
.calculate_drift_transform_3d()?
|
||||
.calculate_drift_transforms_3d()?
|
||||
.into_iter()
|
||||
.map(|t| PyTransform {
|
||||
inner: t.into_dyn(),
|
||||
@@ -2094,7 +2166,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")
|
||||
#[gen_stub(override_return_type(type_repr="typing.Optional[int | tuple[int]]", imports=("typing")
|
||||
))]
|
||||
fn __getitem__<'py>(
|
||||
&self,
|
||||
@@ -2185,7 +2257,7 @@ impl PyShape {
|
||||
} else if shape.len() == 1 {
|
||||
Ok(shape[0].into_bound_py_any(py)?)
|
||||
} else {
|
||||
Ok(shape.into_bound_py_any(py)?)
|
||||
Ok(PyTuple::new(py, shape)?.into_bound_py_any(py)?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2215,3 +2287,90 @@ impl PyShape {
|
||||
.collect::<String>()
|
||||
}
|
||||
}
|
||||
|
||||
/// https://numpy.org/doc/stable/reference/generated/numpy.ndarray.flags.html
|
||||
#[gen_stub_pyclass]
|
||||
#[pyclass(
|
||||
subclass,
|
||||
from_py_object,
|
||||
frozen,
|
||||
eq,
|
||||
hash,
|
||||
name = "Flags",
|
||||
module = "ndbioimage"
|
||||
)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)]
|
||||
pub(crate) struct PyFlags {
|
||||
#[pyo3(get)]
|
||||
c_contiguous: bool,
|
||||
#[pyo3(get)]
|
||||
f_contiguous: bool,
|
||||
#[pyo3(get)]
|
||||
owndata: bool,
|
||||
#[pyo3(get)]
|
||||
writable: bool,
|
||||
#[pyo3(get)]
|
||||
aligned: bool,
|
||||
#[pyo3(get)]
|
||||
writebackifcopy: bool,
|
||||
}
|
||||
|
||||
#[gen_stub_pymethods]
|
||||
#[pymethods]
|
||||
impl PyFlags {
|
||||
#[new]
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
c_contiguous: false,
|
||||
f_contiguous: false,
|
||||
owndata: false,
|
||||
writable: false,
|
||||
aligned: false,
|
||||
writebackifcopy: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn __getnewargs__(&self) {}
|
||||
|
||||
fn __getitem__(&self, item: String) -> PyResult<bool> {
|
||||
match item.to_lowercase().as_str() {
|
||||
"c_contiguous" | "c" => Ok(self.c_contiguous),
|
||||
"f_contiguous" | "f" => Ok(self.f_contiguous),
|
||||
"owndata" | "o" => Ok(self.owndata),
|
||||
"writable" | "w" => Ok(self.writable),
|
||||
"aligned" | "a" => Ok(self.aligned),
|
||||
"writebackifcopy" | "x" => Ok(self.writebackifcopy),
|
||||
"fnc" => Ok(self.fnc()),
|
||||
"forc" => Ok(self.forc()),
|
||||
"behaved" | "b" => Ok(self.behaved()),
|
||||
"carray" | "ca" => Ok(self.carray()),
|
||||
"farray" | "fa" => Ok(self.farray()),
|
||||
_ => Err(PyKeyError::new_err(format!("no such key: {}", item))),
|
||||
}
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn fnc(&self) -> bool {
|
||||
self.c_contiguous && !self.f_contiguous
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn forc(&self) -> bool {
|
||||
self.c_contiguous || self.f_contiguous
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn behaved(&self) -> bool {
|
||||
self.aligned && self.writable
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn carray(&self) -> bool {
|
||||
self.behaved() && self.c_contiguous
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn farray(&self) -> bool {
|
||||
self.behaved() && self.f_contiguous && !self.c_contiguous
|
||||
}
|
||||
}
|
||||
+354
-13
@@ -1,16 +1,18 @@
|
||||
use crate::error::Error;
|
||||
use crate::transforms::Transforms;
|
||||
use image_registration::metric::{SamplingArg, Sigma};
|
||||
use image_registration::register::{Optimizer, RegistrationStep};
|
||||
use image_registration::transform::Transform;
|
||||
use ndarray::{Ix0, Ix1, Ix2, Ix3, Ix4, Ix5, IxDyn};
|
||||
use numpy::{
|
||||
AllowTypeChange, IntoPyArray, PyArray, PyArray1, PyArray2, PyArrayDyn, PyArrayLike1,
|
||||
PyArrayLike2, PyArrayLikeDyn,
|
||||
};
|
||||
use postcard::{from_bytes, to_stdvec};
|
||||
use pyo3::exceptions::{PyNotImplementedError, PyValueError};
|
||||
use pyo3::prelude::*;
|
||||
use pyo3_stub_gen::derive::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[gen_stub_pyclass]
|
||||
@@ -19,7 +21,7 @@ use std::path::PathBuf;
|
||||
from_py_object,
|
||||
eq,
|
||||
name = "Transform",
|
||||
module = "ndbioimage"
|
||||
module = "ndbioimage.transforms"
|
||||
)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub(crate) struct PyTransform {
|
||||
@@ -30,8 +32,14 @@ pub(crate) struct PyTransform {
|
||||
#[pymethods]
|
||||
impl PyTransform {
|
||||
#[new]
|
||||
#[pyo3(signature = (parameters, shape, center = None))]
|
||||
fn new(parameters: Vec<f64>, shape: Vec<usize>, center: Option<Vec<f64>>) -> Self {
|
||||
#[pyo3(signature = (parameters = None, shape = None, center = None))]
|
||||
fn new(
|
||||
parameters: Option<Vec<f64>>,
|
||||
shape: Option<Vec<usize>>,
|
||||
center: Option<Vec<f64>>,
|
||||
) -> Self {
|
||||
let parameters = parameters.unwrap_or_else(|| vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
|
||||
let shape = shape.unwrap_or_else(|| vec![512, 512]);
|
||||
if let Some(center) = center {
|
||||
Self {
|
||||
inner: Transform::new_with_center(parameters, center, shape),
|
||||
@@ -598,7 +606,7 @@ impl PyTransform {
|
||||
from_py_object,
|
||||
eq,
|
||||
name = "Transforms",
|
||||
module = "ndbioimage"
|
||||
module = "ndbioimage.transforms"
|
||||
)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub(crate) struct PyTransforms {
|
||||
@@ -608,12 +616,51 @@ pub(crate) struct PyTransforms {
|
||||
#[gen_stub_pymethods]
|
||||
#[pymethods]
|
||||
impl PyTransforms {
|
||||
pub(crate) fn __getstate__(&self) -> PyResult<Vec<u8>> {
|
||||
Ok(to_stdvec(self).map_err(Error::from)?)
|
||||
#[new]
|
||||
#[pyo3(signature = (channel = HashMap::new(), drift = Vec::new()))]
|
||||
fn new(
|
||||
channel: HashMap<String, PyTransform>,
|
||||
drift: Vec<PyTransform>,
|
||||
) -> PyResult<PyTransforms> {
|
||||
let channel = channel.into_iter().map(|(n, t)| (n, t.inner)).collect();
|
||||
let drift = drift.into_iter().map(|t| t.inner).collect();
|
||||
Ok(PyTransforms {
|
||||
inner: Transforms { channel, drift },
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn __setstate__(&mut self, state: Vec<u8>) -> PyResult<()> {
|
||||
Ok(from_bytes(&state).map_err(Error::from)?)
|
||||
#[getter]
|
||||
fn get_channel_transforms(&self) -> HashMap<String, PyTransform> {
|
||||
self.inner
|
||||
.channel
|
||||
.iter()
|
||||
.map(|(n, t)| (n.clone(), PyTransform { inner: t.clone() }))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[setter]
|
||||
fn set_channel_transforms(&mut self, transforms: HashMap<String, PyTransform>) -> PyResult<()> {
|
||||
self.inner.channel = transforms.into_iter().map(|(n, t)| (n, t.inner)).collect();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn get_drift_transforms(&self) -> Vec<PyTransform> {
|
||||
self.inner
|
||||
.drift
|
||||
.iter()
|
||||
.map(|t| PyTransform { inner: t.clone() })
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[setter]
|
||||
fn set_drift_transforms(&mut self, transforms: Vec<PyTransform>) -> PyResult<()> {
|
||||
self.inner.drift = transforms.into_iter().map(|t| t.inner).collect();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn __getnewargs__(&self) -> (HashMap<String, PyTransform>, Vec<PyTransform>) {
|
||||
(self.get_channel_transforms(), self.get_drift_transforms())
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
@@ -632,7 +679,7 @@ impl PyTransforms {
|
||||
bead_files: Vec<PathBuf>,
|
||||
main_channel: usize,
|
||||
default_transform: Option<PyTransform>,
|
||||
) -> PyResult<Vec<PyTransform>> {
|
||||
) -> PyResult<HashMap<String, PyTransform>> {
|
||||
Ok(Transforms::calculate_channel_transforms_2d(
|
||||
&bead_files
|
||||
.iter()
|
||||
@@ -644,8 +691,13 @@ impl PyTransforms {
|
||||
.transpose()?,
|
||||
)?
|
||||
.into_iter()
|
||||
.map(|t| PyTransform {
|
||||
.map(|(n, t)| {
|
||||
(
|
||||
n,
|
||||
PyTransform {
|
||||
inner: t.into_dyn(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
@@ -655,7 +707,7 @@ impl PyTransforms {
|
||||
bead_files: Vec<PathBuf>,
|
||||
main_channel: usize,
|
||||
default_transform: Option<PyTransform>,
|
||||
) -> PyResult<Vec<PyTransform>> {
|
||||
) -> PyResult<HashMap<String, PyTransform>> {
|
||||
Ok(Transforms::calculate_channel_transforms_3d(
|
||||
&bead_files
|
||||
.iter()
|
||||
@@ -667,9 +719,298 @@ impl PyTransforms {
|
||||
.transpose()?,
|
||||
)?
|
||||
.into_iter()
|
||||
.map(|t| PyTransform {
|
||||
.map(|(n, t)| {
|
||||
(
|
||||
n,
|
||||
PyTransform {
|
||||
inner: t.into_dyn(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[gen_stub_pyclass]
|
||||
#[pyclass(
|
||||
subclass,
|
||||
from_py_object,
|
||||
eq,
|
||||
name = "RegistrationStep",
|
||||
module = "ndbioimage.transforms"
|
||||
)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub(crate) struct PyRegistrationStep {
|
||||
pub(crate) inner: RegistrationStep,
|
||||
}
|
||||
|
||||
#[gen_stub_pymethods]
|
||||
#[pymethods]
|
||||
impl PyRegistrationStep {
|
||||
#[new]
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
sigma: PySigma,
|
||||
sample: PySamplingArg,
|
||||
n_bins: usize,
|
||||
tolerance: f64,
|
||||
edge: f64,
|
||||
max_iterations: usize,
|
||||
learning_rate: f64,
|
||||
optimizer: PyOptimizer,
|
||||
) -> PyResult<PyRegistrationStep> {
|
||||
Ok(PyRegistrationStep {
|
||||
inner: RegistrationStep::new(
|
||||
sigma.inner,
|
||||
sample.inner,
|
||||
n_bins,
|
||||
tolerance,
|
||||
edge,
|
||||
max_iterations,
|
||||
learning_rate,
|
||||
optimizer.inner,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
fn __getnewargs__(
|
||||
&self,
|
||||
) -> (
|
||||
PySigma,
|
||||
PySamplingArg,
|
||||
usize,
|
||||
f64,
|
||||
f64,
|
||||
usize,
|
||||
f64,
|
||||
PyOptimizer,
|
||||
) {
|
||||
(
|
||||
PySigma {
|
||||
inner: self.inner.sigma.clone(),
|
||||
},
|
||||
PySamplingArg {
|
||||
inner: self.inner.samples.clone(),
|
||||
},
|
||||
self.inner.n_bins,
|
||||
self.inner.tolerance,
|
||||
self.inner.edge,
|
||||
self.inner.max_iterations,
|
||||
self.inner.learning_rate,
|
||||
PyOptimizer {
|
||||
inner: self.inner.optimizer.clone(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn default_steps(ndim: usize, n: usize) -> Vec<PyRegistrationStep> {
|
||||
RegistrationStep::default_steps(ndim, n)
|
||||
.into_iter()
|
||||
.map(|s| PyRegistrationStep { inner: s })
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[gen_stub_pyclass]
|
||||
#[pyclass(
|
||||
subclass,
|
||||
from_py_object,
|
||||
eq,
|
||||
name = "Sigma",
|
||||
module = "ndbioimage.transforms"
|
||||
)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub(crate) struct PySigma {
|
||||
pub(crate) inner: Sigma,
|
||||
}
|
||||
|
||||
#[gen_stub_pymethods]
|
||||
#[pymethods]
|
||||
impl PySigma {
|
||||
#[new]
|
||||
#[pyo3(signature = (absolute = None, relative = None))]
|
||||
fn new(absolute: Option<Vec<f64>>, relative: Option<Vec<f64>>) -> Self {
|
||||
if let Some(absolute) = absolute {
|
||||
Self {
|
||||
inner: Sigma::Absolute(absolute),
|
||||
}
|
||||
} else if let Some(relative) = relative {
|
||||
Self {
|
||||
inner: Sigma::Relative(relative),
|
||||
}
|
||||
} else {
|
||||
Self { inner: Sigma::None }
|
||||
}
|
||||
}
|
||||
|
||||
fn __getnewargs__(&self) -> (String, Option<Vec<f64>>) {
|
||||
match &self.inner {
|
||||
Sigma::None => ("none".to_string(), None),
|
||||
Sigma::Absolute(sigma) => ("absolute".to_string(), Some(sigma.clone())),
|
||||
Sigma::Relative(sigma) => ("relative".to_string(), Some(sigma.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn none() -> PySigma {
|
||||
PySigma { inner: Sigma::None }
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn absolute(sigma: Vec<f64>) -> PySigma {
|
||||
PySigma {
|
||||
inner: Sigma::Absolute(sigma),
|
||||
}
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn relative(sigma: Vec<f64>) -> PySigma {
|
||||
PySigma {
|
||||
inner: Sigma::Relative(sigma),
|
||||
}
|
||||
}
|
||||
|
||||
fn smooth<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||
array: PyArrayLikeDyn<f64, AllowTypeChange>,
|
||||
) -> PyResult<Bound<'py, PyArrayDyn<f64>>> {
|
||||
let array = array.as_array();
|
||||
Ok(py
|
||||
.detach(|| self.inner.smooth(array))
|
||||
.map_err(Error::from)?
|
||||
.into_pyarray(py))
|
||||
}
|
||||
|
||||
fn sigma(&self, shape: Vec<usize>) -> Option<Vec<f64>> {
|
||||
self.inner.sigma(shape.as_slice())
|
||||
}
|
||||
}
|
||||
|
||||
#[gen_stub_pyclass]
|
||||
#[pyclass(
|
||||
subclass,
|
||||
from_py_object,
|
||||
eq,
|
||||
name = "SamplingArg",
|
||||
module = "ndbioimage.transforms"
|
||||
)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub(crate) struct PySamplingArg {
|
||||
pub(crate) inner: SamplingArg,
|
||||
}
|
||||
|
||||
#[gen_stub_pymethods]
|
||||
#[pymethods]
|
||||
impl PySamplingArg {
|
||||
#[new]
|
||||
#[pyo3(signature = (fixed = None, fixed_at = None, random = None))]
|
||||
fn new(
|
||||
fixed: Option<usize>,
|
||||
fixed_at: Option<Vec<Vec<f64>>>,
|
||||
random: Option<usize>,
|
||||
) -> PyResult<Self> {
|
||||
if let Some(fixed) = fixed {
|
||||
Ok(Self {
|
||||
inner: SamplingArg::Fixed(fixed),
|
||||
})
|
||||
} else if let Some(fixed_at) = fixed_at {
|
||||
Ok(Self {
|
||||
inner: SamplingArg::FixedAt(fixed_at),
|
||||
})
|
||||
} else if let Some(random) = random {
|
||||
Ok(Self {
|
||||
inner: SamplingArg::Random(random),
|
||||
})
|
||||
} else {
|
||||
Err(PyValueError::new_err(
|
||||
"missing argument 'fixed', 'fixed_at' or 'random'",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn __getnewargs__(&self) -> (Option<usize>, Option<Vec<Vec<f64>>>, Option<usize>) {
|
||||
match &self.inner {
|
||||
SamplingArg::Fixed(fixed) => (Some(*fixed), None, None),
|
||||
SamplingArg::FixedAt(fixed_at) => (None, Some(fixed_at.clone()), None),
|
||||
SamplingArg::Random(usize) => (None, None, Some(*usize)),
|
||||
}
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn fixed(size: usize) -> PySamplingArg {
|
||||
PySamplingArg {
|
||||
inner: SamplingArg::Fixed(size),
|
||||
}
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn fixed_at(f: Vec<Vec<f64>>) -> PySamplingArg {
|
||||
PySamplingArg {
|
||||
inner: SamplingArg::FixedAt(f),
|
||||
}
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn random(size: usize) -> PySamplingArg {
|
||||
PySamplingArg {
|
||||
inner: SamplingArg::Random(size),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[gen_stub_pyclass]
|
||||
#[pyclass(
|
||||
subclass,
|
||||
from_py_object,
|
||||
eq,
|
||||
name = "Optimizer",
|
||||
module = "ndbioimage.transforms"
|
||||
)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub(crate) struct PyOptimizer {
|
||||
pub(crate) inner: Optimizer,
|
||||
}
|
||||
|
||||
#[gen_stub_pymethods]
|
||||
#[pymethods]
|
||||
impl PyOptimizer {
|
||||
#[new]
|
||||
fn new(optimizer: String) -> PyResult<Self> {
|
||||
match optimizer.to_lowercase().as_str() {
|
||||
"lbfgs" => Ok(Self {
|
||||
inner: Optimizer::LBFGS,
|
||||
}),
|
||||
"asgd" => Ok(Self {
|
||||
inner: Optimizer::ASGD,
|
||||
}),
|
||||
_ => Err(PyValueError::new_err(format!(
|
||||
"invalid optimizer: {}",
|
||||
optimizer
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn __getnewargs__(&self) -> (String,) {
|
||||
(match &self.inner {
|
||||
Optimizer::LBFGS => "lbfgs".to_string(),
|
||||
Optimizer::ASGD => "asgd".to_string(),
|
||||
},)
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn lbfgs() -> PyOptimizer {
|
||||
PyOptimizer {
|
||||
inner: Optimizer::LBFGS,
|
||||
}
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn asgd() -> PyOptimizer {
|
||||
PyOptimizer {
|
||||
inner: Optimizer::ASGD,
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
-3
@@ -229,6 +229,7 @@ pub trait Reader: Clone + Sized + Debug + Send + Hash + Into<DynReader> {
|
||||
P: AsRef<Path>;
|
||||
}
|
||||
|
||||
/// Bioformats numbering
|
||||
impl TryFrom<i32> for PixelType {
|
||||
type Error = Error;
|
||||
|
||||
@@ -257,6 +258,7 @@ impl FromStr for PixelType {
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"bool" | "bit" => Ok(PixelType::Bool),
|
||||
"int8" | "i8" => Ok(PixelType::I8),
|
||||
"uint8" | "u8" => Ok(PixelType::U8),
|
||||
"int16" | "i16" => Ok(PixelType::I16),
|
||||
@@ -683,6 +685,22 @@ impl Reader for DynReader {
|
||||
}
|
||||
|
||||
impl DynReader {
|
||||
/// list of enabled readers
|
||||
pub fn available_readers() -> Vec<String> {
|
||||
vec![
|
||||
#[cfg(feature = "tiff")]
|
||||
"tiff".to_string(),
|
||||
#[cfg(feature = "tiffseq")]
|
||||
"tiffseq".to_string(),
|
||||
#[cfg(feature = "czi")]
|
||||
"czi".to_string(),
|
||||
#[cfg(feature = "bioformats_rust")]
|
||||
"bioformats_rust".to_string(),
|
||||
#[cfg(feature = "bioformats_java")]
|
||||
"bioformats_java".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
/// 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
|
||||
@@ -694,7 +712,7 @@ impl DynReader {
|
||||
let reader = reader.as_ref();
|
||||
let reader = match reader.to_lowercase().as_str() {
|
||||
#[cfg(feature = "tiff")]
|
||||
"tif" => Ok(DynReader::Tiff(tiff::TiffReader::new(
|
||||
"tiff" => Ok(DynReader::Tiff(tiff::TiffReader::new(
|
||||
path,
|
||||
dimensions.s.unwrap_or(0),
|
||||
dimensions.p.unwrap_or(0),
|
||||
@@ -746,7 +764,7 @@ impl DynReader {
|
||||
let reader = reader.as_ref();
|
||||
match reader.to_lowercase().as_str() {
|
||||
#[cfg(feature = "tiff")]
|
||||
"tif" => Ok(tiff::TiffReader::get_available_positions(path, series)?),
|
||||
"tiff" => Ok(tiff::TiffReader::get_available_positions(path, series)?),
|
||||
#[cfg(feature = "tiffseq")]
|
||||
"tiffseq" => Ok(tiffseq::TiffSeqReader::get_available_positions(
|
||||
path, series,
|
||||
@@ -781,7 +799,7 @@ impl DynReader {
|
||||
let reader = reader.as_ref();
|
||||
match reader.to_lowercase().as_str() {
|
||||
#[cfg(feature = "tiff")]
|
||||
"tif" => Ok(tiff::TiffReader::get_available_series(path)?),
|
||||
"tiff" => Ok(tiff::TiffReader::get_available_series(path)?),
|
||||
#[cfg(feature = "tiffseq")]
|
||||
"tiffseq" => Ok(tiffseq::TiffSeqReader::get_available_series(path)?),
|
||||
#[cfg(feature = "czi")]
|
||||
|
||||
+54
-41
@@ -9,6 +9,7 @@ use std::fs::File;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::{Path, PathBuf};
|
||||
use thread_local::ThreadLocal;
|
||||
use tiff::decoder::ifd::Value;
|
||||
use tiff::decoder::{Decoder, DecodingResult};
|
||||
use tiff::tags::Tag;
|
||||
|
||||
@@ -25,8 +26,6 @@ pub struct TiffReader {
|
||||
n_samples: usize,
|
||||
p_ndim: u8,
|
||||
planar: bool,
|
||||
interval_t: f64,
|
||||
pixel_size: Option<f64>,
|
||||
}
|
||||
|
||||
impl From<TiffReader> for DynReader {
|
||||
@@ -55,8 +54,6 @@ impl Clone for TiffReader {
|
||||
n_samples: self.n_samples,
|
||||
p_ndim: self.p_ndim,
|
||||
planar: self.planar,
|
||||
interval_t: self.interval_t,
|
||||
pixel_size: self.pixel_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,8 +125,6 @@ impl Reader for TiffReader {
|
||||
n_samples: 0,
|
||||
p_ndim: 0,
|
||||
planar: true,
|
||||
interval_t: 0.0,
|
||||
pixel_size: None,
|
||||
};
|
||||
|
||||
let mut reader = new.get_reader()?;
|
||||
@@ -157,7 +152,7 @@ impl Reader for TiffReader {
|
||||
.get_tag(Tag::Unknown(50839))
|
||||
.or_else(|_| reader.get_tag(Tag::ImageDescription))?;
|
||||
let imagej_bytes = match imagej_bytes {
|
||||
tiff::decoder::ifd::Value::Ascii(s) => s.into_bytes(),
|
||||
Value::Ascii(s) => s.into_bytes(),
|
||||
other => other.into_u8_vec()?,
|
||||
};
|
||||
let metadata_str = String::from_utf8_lossy(&imagej_bytes);
|
||||
@@ -179,30 +174,6 @@ impl Reader for TiffReader {
|
||||
.get("frames")
|
||||
.and_then(|v| v.parse::<usize>().ok())
|
||||
.unwrap_or(1);
|
||||
let interval_t = metadata_map
|
||||
.get("interval")
|
||||
.and_then(|v| v.parse::<f64>().ok())
|
||||
.unwrap_or(0.0);
|
||||
let ru = reader
|
||||
.get_tag(Tag::ResolutionUnit)
|
||||
.ok()
|
||||
.and_then(|i| i.into_u16().ok())
|
||||
.unwrap_or(2);
|
||||
let pixel_size = reader
|
||||
.get_tag(Tag::XResolution)
|
||||
.ok()
|
||||
.and_then(|i| i.into_f64().ok())
|
||||
.and_then(|i| {
|
||||
if i <= 0.0 {
|
||||
None
|
||||
} else if ru == 3 {
|
||||
Some(1e4 / i)
|
||||
} else if ru == 2 {
|
||||
Some(25400.0 / i)
|
||||
} else {
|
||||
Some(1.0 / i)
|
||||
}
|
||||
});
|
||||
|
||||
let mult = if p_ndim == 3 { n_samples } else { 1 };
|
||||
let expected = size_c * size_z * size_t / mult;
|
||||
@@ -231,8 +202,6 @@ impl Reader for TiffReader {
|
||||
new.pixel_type = pixtype;
|
||||
new.n_samples = n_samples;
|
||||
new.p_ndim = p_ndim;
|
||||
new.interval_t = interval_t;
|
||||
new.pixel_size = pixel_size;
|
||||
new.planar = planar;
|
||||
|
||||
// test reading first frame, if error another reader (bioformats) can take over
|
||||
@@ -241,6 +210,48 @@ impl Reader for TiffReader {
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Result<Ome, Error> {
|
||||
let mut reader = self.get_reader()?;
|
||||
let imagej_bytes = reader
|
||||
.get_tag(Tag::Unknown(50839))
|
||||
.or_else(|_| reader.get_tag(Tag::ImageDescription))?;
|
||||
let imagej_bytes = match imagej_bytes {
|
||||
Value::Ascii(s) => s.into_bytes(),
|
||||
other => other.into_u8_vec()?,
|
||||
};
|
||||
let metadata_str = String::from_utf8_lossy(&imagej_bytes);
|
||||
let metadata_map = parse_imagej_metadata(&metadata_str);
|
||||
|
||||
let interval_t = metadata_map
|
||||
.get("interval")
|
||||
.and_then(|v| v.parse::<f64>().ok());
|
||||
let ru = reader
|
||||
.get_tag(Tag::ResolutionUnit)
|
||||
.ok()
|
||||
.and_then(|i| i.into_u16().ok())
|
||||
.unwrap_or(2);
|
||||
let pixel_size = reader
|
||||
.get_tag(Tag::XResolution)
|
||||
.ok()
|
||||
.and_then(|i| i.into_u64_vec().ok())
|
||||
.and_then(|i| {
|
||||
i.first()
|
||||
.and_then(|&n| i.get(1).map(|&d| (n as f64) / (d as f64)))
|
||||
})
|
||||
.and_then(|i| {
|
||||
if i <= 0.0 {
|
||||
None
|
||||
} else if ru == 3 {
|
||||
Some(1e4 / i)
|
||||
} else if ru == 2 {
|
||||
Some(25400.0 / i)
|
||||
} else {
|
||||
Some(1.0 / i)
|
||||
}
|
||||
});
|
||||
let interval_z = metadata_map
|
||||
.get("spacing")
|
||||
.and_then(|v| v.parse::<f64>().ok());
|
||||
|
||||
let ome_pixel_type = match self.pixel_type {
|
||||
PixelType::I8 => ome::PixelType::Int8,
|
||||
PixelType::U8 => ome::PixelType::Uint8,
|
||||
@@ -262,13 +273,13 @@ impl Reader for TiffReader {
|
||||
size_t: self.shape.t as i32,
|
||||
dimension_order: ome::PixelsDimensionOrderType::Xyczt,
|
||||
r#type: ome_pixel_type,
|
||||
physical_size_x: self.pixel_size.map(|v| v as f32),
|
||||
physical_size_x: pixel_size.map(|v| v as f32),
|
||||
physical_size_x_unit: ome::UnitsLength::um,
|
||||
physical_size_y: self.pixel_size.map(|v| v as f32),
|
||||
physical_size_y: pixel_size.map(|v| v as f32),
|
||||
physical_size_y_unit: ome::UnitsLength::um,
|
||||
physical_size_z: None,
|
||||
physical_size_z: interval_z.map(|v| v as f32),
|
||||
physical_size_z_unit: ome::UnitsLength::um,
|
||||
time_increment: None,
|
||||
time_increment: interval_t.map(|v| v as f32),
|
||||
time_increment_unit: ome::UnitsTime::s,
|
||||
significant_bits: None,
|
||||
interleaved: None,
|
||||
@@ -295,11 +306,13 @@ impl Reader for TiffReader {
|
||||
the_c: Some(c as i32),
|
||||
the_z: Some(z as i32),
|
||||
the_t: Some(t as i32),
|
||||
delta_t: if self.interval_t > 0.0 {
|
||||
Some(t as f32 * self.interval_t as f32)
|
||||
delta_t: interval_t.and_then(|v| {
|
||||
if v > 0.0 {
|
||||
Some((t as f64 * v) as f32)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
@@ -341,7 +354,7 @@ impl Reader for TiffReader {
|
||||
reader.seek_to_image(page_idx)?;
|
||||
|
||||
let data: DecodingResult = if self.planar {
|
||||
let mut result = DecodingResult::U16(vec![]);
|
||||
let mut result = DecodingResult::U16(Vec::new());
|
||||
reader.read_image_to_buffer(&mut result)?;
|
||||
result
|
||||
} else {
|
||||
|
||||
+191
-60
@@ -3,30 +3,73 @@ use crate::error::Error;
|
||||
use crate::readers::{DynReader, Reader};
|
||||
use crate::view::View;
|
||||
pub use image_registration::transform::Transform;
|
||||
use ndarray::{Dimension, Ix2, Ix3, s};
|
||||
use ndarray::{Array2, Dimension, Ix2, Ix3, IxDyn, s};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::io::Seek;
|
||||
use std::path::Path;
|
||||
|
||||
#[expect(clippy::upper_case_acronyms)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum TransformD {
|
||||
YX(Transform<Ix2>),
|
||||
ZYX(Transform<Ix3>),
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
struct TransformOld {
|
||||
#[serde(alias = "CenterOfRotationPoint")]
|
||||
center_of_rotation_point: Vec<f64>,
|
||||
#[serde(alias = "Size")]
|
||||
size: Vec<f64>,
|
||||
#[serde(alias = "TransformParameters")]
|
||||
transform_parameters: Vec<f64>,
|
||||
#[serde(alias = "dTransformParameters")]
|
||||
dtransform_parameters: Vec<f64>,
|
||||
}
|
||||
|
||||
impl TryFrom<HashMap<String, TransformOld>> for Transforms {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(t: HashMap<String, TransformOld>) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
channel: t
|
||||
.into_iter()
|
||||
.map(|(n, i)| {
|
||||
Ok((
|
||||
n,
|
||||
Transform::new_with_center(
|
||||
i.transform_parameters,
|
||||
i.center_of_rotation_point,
|
||||
i.size.into_iter().map(|j| j as usize).collect(),
|
||||
)
|
||||
.with_dparameters(i.dtransform_parameters)
|
||||
.inverse()?,
|
||||
))
|
||||
})
|
||||
.collect::<Result<_, Error>>()?,
|
||||
drift: Vec::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Transforms {
|
||||
pub channel: Vec<TransformD>,
|
||||
pub drift: Vec<TransformD>,
|
||||
#[serde(skip_serializing_if = "HashMap::is_empty", default = "HashMap::new")]
|
||||
pub channel: HashMap<String, Transform<IxDyn>>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")]
|
||||
pub drift: Vec<Transform<IxDyn>>,
|
||||
}
|
||||
|
||||
impl Transforms {
|
||||
pub fn load(path: &Path) -> Result<Self, Error> {
|
||||
let file = std::fs::File::open(path)?;
|
||||
Ok(yaml_serde::from_reader(file)?)
|
||||
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
||||
let mut file = std::fs::File::open(&path)?;
|
||||
Ok(
|
||||
if let Ok(t) = yaml_serde::from_reader::<_, Self>(&file)
|
||||
&& !(t.channel.is_empty() && t.drift.is_empty())
|
||||
{
|
||||
t
|
||||
} else {
|
||||
file.seek(std::io::SeekFrom::Start(0))?;
|
||||
yaml_serde::from_reader::<_, HashMap<String, TransformOld>>(&file)?.try_into()?
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn save(&self, path: &Path) -> Result<(), Error> {
|
||||
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
|
||||
let file = std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
@@ -39,30 +82,43 @@ impl Transforms {
|
||||
bead_files: &[&Path],
|
||||
main_channel: usize,
|
||||
default_transform: Option<Transform<Ix2>>,
|
||||
) -> Result<Vec<Transform<Ix2>>, Error> {
|
||||
) -> Result<HashMap<String, Transform<Ix2>>, Error> {
|
||||
let mut transforms = Vec::new();
|
||||
let default_transform = default_transform.unwrap_or_default();
|
||||
for file in bead_files {
|
||||
let view = View::<_, DynReader>::from_path(file)?;
|
||||
transforms.push(view.calculate_channel_transforms_2d(main_channel)?)
|
||||
}
|
||||
let n_channels = transforms.iter().map(|t| t.len()).max().unwrap();
|
||||
let mut average_transforms = Vec::new();
|
||||
for channel in 0..n_channels {
|
||||
let matrix = transforms
|
||||
let channels = transforms
|
||||
.iter()
|
||||
.map(|t| (&t[channel] * &default_transform).matrix())
|
||||
.reduce(|a, b| a + b)
|
||||
.unwrap()
|
||||
/ n_channels as f64;
|
||||
let dmatrix = transforms
|
||||
.flat_map(|t| t.keys())
|
||||
.collect::<HashSet<_>>();
|
||||
let mut average_transforms = HashMap::new();
|
||||
for channel in channels {
|
||||
let (s, n) = transforms
|
||||
.iter()
|
||||
.map(|t| ((&t[channel] * &default_transform).matrix() - &matrix).powi(2))
|
||||
.reduce(|a, b| a + b)
|
||||
.unwrap()
|
||||
.sqrt()
|
||||
/ (n_channels as f64).sqrt();
|
||||
average_transforms.push(
|
||||
.filter_map(|t| {
|
||||
if t.contains_key(channel) {
|
||||
Some((&t[channel] * &default_transform).matrix())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.fold((Array2::zeros((3, 3)), 0), |(s, n), t| (s + t, n + 1));
|
||||
let matrix = s / (n as f64);
|
||||
let (s, n) = transforms
|
||||
.iter()
|
||||
.filter_map(|t| {
|
||||
if t.contains_key(channel) {
|
||||
Some(((&t[channel] * &default_transform).matrix() - &matrix).powi(2))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.fold((Array2::zeros((3, 3)), 0), |(s, n), t| (s + t, n + 1));
|
||||
let dmatrix = (s / n as f64).sqrt();
|
||||
average_transforms.insert(
|
||||
channel.clone(),
|
||||
Transform::default()
|
||||
.with_matrix(matrix.view())
|
||||
.with_dmatrix(dmatrix.view()),
|
||||
@@ -75,30 +131,43 @@ impl Transforms {
|
||||
bead_files: &[&Path],
|
||||
main_channel: usize,
|
||||
default_transform: Option<Transform<Ix3>>,
|
||||
) -> Result<Vec<Transform<Ix3>>, Error> {
|
||||
) -> Result<HashMap<String, Transform<Ix3>>, Error> {
|
||||
let mut transforms = Vec::new();
|
||||
let default_transform = default_transform.unwrap_or_default();
|
||||
for file in bead_files {
|
||||
let view = View::<_, DynReader>::from_path(file)?;
|
||||
transforms.push(view.calculate_channel_transforms_3d(main_channel)?)
|
||||
}
|
||||
let n_channels = transforms.iter().map(|t| t.len()).max().unwrap();
|
||||
let mut average_transforms = Vec::new();
|
||||
for channel in 0..n_channels {
|
||||
let matrix = transforms
|
||||
let channels = transforms
|
||||
.iter()
|
||||
.map(|t| (&t[channel] * &default_transform).matrix())
|
||||
.reduce(|a, b| a + b)
|
||||
.unwrap()
|
||||
/ n_channels as f64;
|
||||
let dmatrix = transforms
|
||||
.flat_map(|t| t.keys())
|
||||
.collect::<HashSet<_>>();
|
||||
let mut average_transforms = HashMap::new();
|
||||
for channel in channels {
|
||||
let (s, n) = transforms
|
||||
.iter()
|
||||
.map(|t| ((&t[channel] * &default_transform).matrix() - &matrix).powi(2))
|
||||
.reduce(|a, b| a + b)
|
||||
.unwrap()
|
||||
.sqrt()
|
||||
/ (n_channels as f64).sqrt();
|
||||
average_transforms.push(
|
||||
.filter_map(|t| {
|
||||
if t.contains_key(channel) {
|
||||
Some((&t[channel] * &default_transform).matrix())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.fold((Array2::zeros((4, 4)), 0), |(s, n), t| (s + t, n + 1));
|
||||
let matrix = s / (n as f64);
|
||||
let (s, n) = transforms
|
||||
.iter()
|
||||
.filter_map(|t| {
|
||||
if t.contains_key(channel) {
|
||||
Some(((&t[channel] * &default_transform).matrix() - &matrix).powi(2))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.fold((Array2::zeros((4, 4)), 0), |(s, n), t| (s + t, n + 1));
|
||||
let dmatrix = (s / n as f64).sqrt();
|
||||
average_transforms.insert(
|
||||
channel.clone(),
|
||||
Transform::default()
|
||||
.with_matrix(matrix.view())
|
||||
.with_dmatrix(dmatrix.view()),
|
||||
@@ -109,34 +178,61 @@ impl Transforms {
|
||||
}
|
||||
|
||||
impl<D: Dimension, R: Reader> View<D, R> {
|
||||
pub fn with_transform_from_yaml(mut self, path: &Path) -> Result<Self, Error> {
|
||||
self.transforms = Transforms::load(path)?;
|
||||
#[cfg(feature = "transforms")]
|
||||
pub(crate) fn get_transforms(&self) -> &Transforms {
|
||||
&self.transforms
|
||||
}
|
||||
|
||||
#[cfg(feature = "transforms")]
|
||||
pub(crate) fn set_transforms(&mut self, transforms: Transforms) {
|
||||
self.transforms = transforms;
|
||||
}
|
||||
|
||||
pub fn with_transforms_from_yaml(mut self, path: &Path) -> Result<Self, Error> {
|
||||
self.set_transforms(Transforms::load(path)?);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn load_transform_from_yaml(&mut self, path: &Path) -> Result<(), Error> {
|
||||
self.transforms = Transforms::load(path)?;
|
||||
pub fn load_transforms_from_yaml(&mut self, path: &Path) -> Result<(), Error> {
|
||||
self.set_transforms(Transforms::load(path)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn calculate_channel_transforms_2d(
|
||||
&self,
|
||||
main_channel: usize,
|
||||
) -> Result<Vec<Transform<Ix2>>, Error> {
|
||||
) -> Result<HashMap<String, Transform<Ix2>>, Error> {
|
||||
let main_max = self
|
||||
.slice_cztyx(s![main_channel, .., 0, .., ..])?
|
||||
.max_proj(Axis::Z)?
|
||||
.as_array::<f64>()?;
|
||||
let mut transforms = Vec::new();
|
||||
for channel in 0..self.shape().c {
|
||||
let mut transforms = HashMap::new();
|
||||
let shape = vec![self.shape().y, self.shape().x];
|
||||
let center = shape
|
||||
.iter()
|
||||
.map(|&i| (i as f64 - 1.0) / 2.0)
|
||||
.collect::<Vec<_>>();
|
||||
for (channel, metadata) in self.metadata()?.image[0].pixels.channel.iter().enumerate() {
|
||||
let name = metadata
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| format!("channel{}", channel));
|
||||
if channel == main_channel {
|
||||
transforms.push(Transform::default());
|
||||
transforms.insert(
|
||||
name,
|
||||
Transform::default()
|
||||
.with_shape(shape.clone())
|
||||
.with_center(center.clone()),
|
||||
);
|
||||
} else {
|
||||
let max = self
|
||||
.slice_cztyx(s![channel, .., 0, .., ..])?
|
||||
.max_proj(Axis::Z)?
|
||||
.as_array::<f64>()?;
|
||||
transforms.push(Transform::register_affine(main_max.view(), max.view())?);
|
||||
transforms.insert(
|
||||
name,
|
||||
Transform::register_affine(main_max.view(), max.view())?,
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(transforms)
|
||||
@@ -145,29 +241,46 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
pub fn calculate_channel_transforms_3d(
|
||||
&self,
|
||||
main_channel: usize,
|
||||
) -> Result<Vec<Transform<Ix3>>, Error> {
|
||||
) -> Result<HashMap<String, Transform<Ix3>>, Error> {
|
||||
let main_max = self
|
||||
.slice_cztyx(s![main_channel, .., 0, .., ..])?
|
||||
.as_array::<f64>()?;
|
||||
let mut transforms = Vec::new();
|
||||
for channel in 0..self.shape().c {
|
||||
let mut transforms = HashMap::new();
|
||||
let shape = vec![self.shape().z, self.shape().y, self.shape().x];
|
||||
let center = shape
|
||||
.iter()
|
||||
.map(|&i| (i as f64 - 1.0) / 2.0)
|
||||
.collect::<Vec<_>>();
|
||||
for (channel, metadata) in self.metadata()?.image[0].pixels.channel.iter().enumerate() {
|
||||
let name = metadata
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| format!("channel{}", channel));
|
||||
if channel == main_channel {
|
||||
transforms.push(Transform::default());
|
||||
transforms.insert(
|
||||
name,
|
||||
Transform::default()
|
||||
.with_shape(shape.clone())
|
||||
.with_center(center.clone()),
|
||||
);
|
||||
} else {
|
||||
let max = self
|
||||
.slice_cztyx(s![channel, .., 0, .., ..])?
|
||||
.as_array::<f64>()?;
|
||||
transforms.push(Transform::register_affine(main_max.view(), max.view())?);
|
||||
transforms.insert(
|
||||
name,
|
||||
Transform::register_affine(main_max.view(), max.view())?,
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(transforms)
|
||||
}
|
||||
|
||||
pub fn calculate_drift_transform_2d(&self) -> Result<Vec<Transform<Ix2>>, Error> {
|
||||
pub fn calculate_drift_transforms_2d(&self) -> Result<Vec<Transform<Ix2>>, Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn calculate_drift_transform_3d(&self) -> Result<Vec<Transform<Ix3>>, Error> {
|
||||
pub fn calculate_drift_transforms_3d(&self) -> Result<Vec<Transform<Ix3>>, Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -182,4 +295,22 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_transforms() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let t = Transforms::load("tests/files/transforms.yml")?;
|
||||
println!("t: {:?}", t);
|
||||
assert_eq!(t.channel["TV1"].ndim(), 2);
|
||||
assert_eq!(t.channel["TV2"].ndim(), 2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_old_transforms() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let t = Transforms::load("tests/files/transform_red.yml")?;
|
||||
println!("t: {:?}", t);
|
||||
assert_eq!(t.channel["TV1"].ndim(), 2);
|
||||
assert_eq!(t.channel["TV2"].ndim(), 2);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user