- more transforms
This commit is contained in:
+5
-6
@@ -21,7 +21,6 @@ crate-type = ["cdylib", "rlib"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
bioformats = { version = "0.1", optional = true }
|
bioformats = { version = "0.1", optional = true }
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
color-eyre = { version = "0.6", optional = true }
|
|
||||||
console = { version = "0.16", optional = true }
|
console = { version = "0.16", optional = true }
|
||||||
downloader = { version = "0.2", optional = true, default-features = false, features = ["rustls-tls"] }
|
downloader = { version = "0.2", optional = true, default-features = false, features = ["rustls-tls"] }
|
||||||
ffmpeg-sidecar = { version = "2", optional = true }
|
ffmpeg-sidecar = { version = "2", optional = true }
|
||||||
@@ -38,12 +37,11 @@ ome-metadata = "0.5"
|
|||||||
ordered-float = { version = "5", optional = true }
|
ordered-float = { version = "5", optional = true }
|
||||||
phf = { version = "0.14", features = ["macros"] }
|
phf = { version = "0.14", features = ["macros"] }
|
||||||
postcard = { version = "1", features = ["use-std"], optional = true }
|
postcard = { version = "1", features = ["use-std"], optional = true }
|
||||||
pyo3 = { version = "0.29", features = ["abi3-py310", "eyre", "anyhow", "generate-import-lib"], optional = true }
|
pyo3 = { version = "0.29", features = ["abi3-py310", "anyhow", "generate-import-lib"], optional = true }
|
||||||
pyo3-stub-gen = { version = "0.23", optional = true }
|
pyo3-stub-gen = { version = "0.23", optional = true }
|
||||||
rayon = { version = "1", optional = true }
|
rayon = { version = "1", optional = true }
|
||||||
regex = "1"
|
regex = "1"
|
||||||
serde = { version = "1", features = ["rc", "derive"] }
|
serde = { version = "1", features = ["rc", "derive"] }
|
||||||
serde_yaml = { version = "0.9", optional = true }
|
|
||||||
serde_with = "3"
|
serde_with = "3"
|
||||||
strum = { version = "0.28", features = ["derive"] }
|
strum = { version = "0.28", features = ["derive"] }
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
@@ -52,6 +50,7 @@ tiffwrite = { version = "2026.6.0", optional = true }
|
|||||||
tokio = { version = "1", features = ["rt", "rt-multi-thread"], optional = true }
|
tokio = { version = "1", features = ["rt", "rt-multi-thread"], optional = true }
|
||||||
thread_local = { version = "1", optional = true }
|
thread_local = { version = "1", optional = true }
|
||||||
xmltree = { version = "0.12", optional = true }
|
xmltree = { version = "0.12", optional = true }
|
||||||
|
yaml_serde = { version = "0.10", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rayon = "1"
|
rayon = "1"
|
||||||
@@ -67,15 +66,15 @@ toml = "1"
|
|||||||
default = ["bioformats_java", "gpl-formats", "czi", "tiff", "tiffseq", "movie", "tiffwrite"]
|
default = ["bioformats_java", "gpl-formats", "czi", "tiff", "tiffseq", "movie", "tiffwrite"]
|
||||||
all = ["bioformats_java", "bioformats_rust", "czi", "gpl-formats", "movie", "tiffseq", "tiffwrite", "tiff", "transforms"]
|
all = ["bioformats_java", "bioformats_rust", "czi", "gpl-formats", "movie", "tiffseq", "tiffwrite", "tiff", "transforms"]
|
||||||
gpl-formats = []
|
gpl-formats = []
|
||||||
python = ["dep:pyo3", "dep:numpy", "dep:color-eyre", "dep:pyo3-stub-gen", "dep:postcard", "ome-metadata/python"]
|
python = ["dep:pyo3", "dep:numpy", "dep:pyo3-stub-gen", "dep:postcard", "ome-metadata/python"]
|
||||||
czi = ["dep:libczirw-sys", "dep:xmltree", "dep:thread_local"]
|
czi = ["dep:libczirw-sys", "dep:xmltree", "dep:thread_local"]
|
||||||
bioformats_rust = ["dep:bioformats", "dep:thread_local"]
|
bioformats_rust = ["dep:bioformats", "dep:thread_local"]
|
||||||
bioformats_java = ["dep:j4rs", "dep:thread_local", "dep:downloader"]
|
bioformats_java = ["dep:j4rs", "dep:thread_local", "dep:downloader"]
|
||||||
tiffwrite = ["dep:tiffwrite", "dep:indicatif", "dep:console", "dep:rayon"]
|
tiffwrite = ["dep:tiffwrite", "dep:indicatif", "dep:console", "dep:rayon"]
|
||||||
tiffseq = ["dep:tiff", "dep:serde_yaml"]
|
tiffseq = ["dep:tiff", "dep:yaml_serde"]
|
||||||
tiff = ["dep:tiff", "dep:thread_local"]
|
tiff = ["dep:tiff", "dep:thread_local"]
|
||||||
movie = ["dep:ffmpeg-sidecar", "dep:tokio", "dep:ordered-float", "dep:indicatif", "dep:console"]
|
movie = ["dep:ffmpeg-sidecar", "dep:tokio", "dep:ordered-float", "dep:indicatif", "dep:console"]
|
||||||
transforms = ["dep:image-registration"]
|
transforms = ["dep:image-registration", "dep:yaml_serde"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
no-default-features = true
|
no-default-features = true
|
||||||
|
|||||||
@@ -13,9 +13,8 @@ from numpy.typing import ArrayLike
|
|||||||
os.environ["RUST_BACKTRACE"] = "full"
|
os.environ["RUST_BACKTRACE"] = "full"
|
||||||
os.environ["COLORBT_SHOW_HIDDEN"] = "1"
|
os.environ["COLORBT_SHOW_HIDDEN"] = "1"
|
||||||
|
|
||||||
from . import ndbioimage_rs as rs # noqa
|
from . import ndbioimage_rs as rs
|
||||||
from .ndbioimage_rs import Imread
|
from .ndbioimage_rs import Imread, Transform, Transforms
|
||||||
from .transforms import Transform, Transforms # noqa: F401
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .ndbioimage_rs import batch_to_tiff
|
from .ndbioimage_rs import batch_to_tiff
|
||||||
@@ -63,11 +62,9 @@ def ndbioimage_generate_stub():
|
|||||||
rs.generate_stub(str(path)) # noqa
|
rs.generate_stub(str(path)) # noqa
|
||||||
else:
|
else:
|
||||||
raise ModuleNotFoundError(str(path / "py" / "ndbioimage" / "__init__.py"))
|
raise ModuleNotFoundError(str(path / "py" / "ndbioimage" / "__init__.py"))
|
||||||
(path / "py" / "ndbioimage" / "__init__.pyi").unlink(missing_ok=True)
|
(path / "py" / "ndbioimage" / "__init__.pyi").rename(
|
||||||
(path / "py" / "ndbioimage" / "ndbioimage_rs" / "__init__.pyi").rename(
|
|
||||||
path / "py" / "ndbioimage" / "ndbioimage_rs.pyi"
|
path / "py" / "ndbioimage" / "ndbioimage_rs.pyi"
|
||||||
)
|
)
|
||||||
(path / "py" / "ndbioimage" / "ndbioimage_rs").rmdir()
|
|
||||||
|
|
||||||
|
|
||||||
R = TypeVar("R")
|
R = TypeVar("R")
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import numpy.typing
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
"Imread",
|
"Imread",
|
||||||
"Shape",
|
"Shape",
|
||||||
|
"Transform",
|
||||||
|
"Transforms",
|
||||||
"batch_to_tiff",
|
"batch_to_tiff",
|
||||||
"main",
|
"main",
|
||||||
]
|
]
|
||||||
@@ -52,9 +54,9 @@ class Imread:
|
|||||||
the name of the reader used to open the file
|
the name of the reader used to open the file
|
||||||
"""
|
"""
|
||||||
@property
|
@property
|
||||||
def transform(self) -> None:
|
def transform(self) -> Transforms:
|
||||||
r"""
|
r"""
|
||||||
get the transformation matrix (not yet implemented)
|
get the transformation
|
||||||
"""
|
"""
|
||||||
@property
|
@property
|
||||||
def path(self) -> pathlib.Path:
|
def path(self) -> pathlib.Path:
|
||||||
@@ -203,10 +205,27 @@ class Imread:
|
|||||||
drift: builtins.bool = False,
|
drift: builtins.bool = False,
|
||||||
file: typing.Optional[typing.Any] = None,
|
file: typing.Optional[typing.Any] = None,
|
||||||
bead_files: 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:
|
) -> Imread:
|
||||||
r"""
|
r"""
|
||||||
return a new view with transformations applied (channel alignment, drift correction)
|
return a new view with transformations applied (channel alignment, drift correction)
|
||||||
"""
|
"""
|
||||||
|
def set_transform(self, transform: Transforms) -> None:
|
||||||
|
r"""
|
||||||
|
set the transformation
|
||||||
|
"""
|
||||||
|
def load_transform_from_yaml(
|
||||||
|
self, path: builtins.str | os.PathLike | pathlib.Path
|
||||||
|
) -> None: ...
|
||||||
|
def calculate_channel_transforms_2d(
|
||||||
|
self, main_channel: builtins.int
|
||||||
|
) -> builtins.list[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]: ...
|
||||||
def squeeze(self) -> numpy.ndarray | int | float: ...
|
def squeeze(self) -> numpy.ndarray | int | float: ...
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
r"""
|
r"""
|
||||||
@@ -672,6 +691,127 @@ class Shape:
|
|||||||
convert shape to a list of dimension sizes in order
|
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(
|
def batch_to_tiff(
|
||||||
files_in: typing.Sequence[builtins.str | os.PathLike | pathlib.Path],
|
files_in: typing.Sequence[builtins.str | os.PathLike | pathlib.Path],
|
||||||
files_out: typing.Sequence[builtins.str | os.PathLike | pathlib.Path],
|
files_out: typing.Sequence[builtins.str | os.PathLike | pathlib.Path],
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
#Insight Transform File V1.0
|
|
||||||
#Transform 0
|
|
||||||
Transform: CompositeTransform_double_2_2
|
|
||||||
#Transform 1
|
|
||||||
Transform: AffineTransform_double_2_2
|
|
||||||
Parameters: 1 0 0 1 0 0
|
|
||||||
FixedParameters: 255.5 255.5
|
|
||||||
@@ -1,572 +0,0 @@
|
|||||||
import warnings
|
|
||||||
from copy import deepcopy
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
import yaml
|
|
||||||
from parfor import Chunks, pmap
|
|
||||||
from skimage import filters
|
|
||||||
from tiffwrite import IJTiffFile
|
|
||||||
from tqdm.auto import tqdm
|
|
||||||
|
|
||||||
try:
|
|
||||||
# best if SimpleElastix is installed: https://simpleelastix.readthedocs.io/GettingStarted.html
|
|
||||||
import SimpleITK as sitk # noqa
|
|
||||||
except ImportError:
|
|
||||||
sitk = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
from pandas import DataFrame, Series, concat
|
|
||||||
except ImportError:
|
|
||||||
DataFrame, Series, concat = None, None, None
|
|
||||||
|
|
||||||
|
|
||||||
if hasattr(yaml, "full_load"):
|
|
||||||
yamlload = yaml.full_load
|
|
||||||
else:
|
|
||||||
yamlload = yaml.load
|
|
||||||
|
|
||||||
|
|
||||||
class Transforms(dict):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.default = Transform()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_file(cls, file, C=True, T=True):
|
|
||||||
with open(Path(file).with_suffix(".yml")) as f:
|
|
||||||
return cls.from_dict(yamlload(f), C, T)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls, d, C=True, T=True):
|
|
||||||
new = cls()
|
|
||||||
for key, value in d.items():
|
|
||||||
if isinstance(key, str) and C:
|
|
||||||
new[key.replace(r"\:", ":").replace("\\\\", "\\")] = (
|
|
||||||
Transform.from_dict(value)
|
|
||||||
)
|
|
||||||
elif T:
|
|
||||||
new[key] = Transform.from_dict(value)
|
|
||||||
return new
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_shifts(cls, shifts):
|
|
||||||
new = cls()
|
|
||||||
for key, shift in shifts.items():
|
|
||||||
new[key] = Transform.from_shift(shift)
|
|
||||||
return new
|
|
||||||
|
|
||||||
def __mul__(self, other):
|
|
||||||
new = Transforms()
|
|
||||||
if isinstance(other, Transforms):
|
|
||||||
for key0, value0 in self.items():
|
|
||||||
for key1, value1 in other.items():
|
|
||||||
new[key0 + key1] = value0 * value1
|
|
||||||
return new
|
|
||||||
elif other is None:
|
|
||||||
return self
|
|
||||||
else:
|
|
||||||
for key in self.keys():
|
|
||||||
new[key] = self[key] * other
|
|
||||||
return new
|
|
||||||
|
|
||||||
def asdict(self):
|
|
||||||
return {
|
|
||||||
key.replace("\\", "\\\\").replace(":", r"\:")
|
|
||||||
if isinstance(key, str)
|
|
||||||
else key: value.asdict()
|
|
||||||
for key, value in self.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
def __getitem__(self, item):
|
|
||||||
return (
|
|
||||||
np.prod([self[i] for i in item[::-1]])
|
|
||||||
if isinstance(item, tuple)
|
|
||||||
else super().__getitem__(item)
|
|
||||||
)
|
|
||||||
|
|
||||||
def __missing__(self, key):
|
|
||||||
return self.default
|
|
||||||
|
|
||||||
def __getstate__(self):
|
|
||||||
return self.__dict__
|
|
||||||
|
|
||||||
def __setstate__(self, state):
|
|
||||||
self.__dict__.update(state)
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash(frozenset((*self.__dict__.items(), *self.items())))
|
|
||||||
|
|
||||||
def save(self, file):
|
|
||||||
with open(Path(file).with_suffix(".yml"), "w") as f:
|
|
||||||
yaml.safe_dump(self.asdict(), f, default_flow_style=None)
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
return deepcopy(self)
|
|
||||||
|
|
||||||
def adapt(self, origin, shape, channel_names):
|
|
||||||
def key_map(a, b):
|
|
||||||
def fun(b, key_a):
|
|
||||||
for key_b in b:
|
|
||||||
if key_b in key_a or key_a in key_b:
|
|
||||||
return key_a, key_b
|
|
||||||
|
|
||||||
return {n[0]: n[1] for key_a in a if (n := fun(b, key_a))}
|
|
||||||
|
|
||||||
for value in self.values():
|
|
||||||
value.adapt(origin, shape)
|
|
||||||
self.default.adapt(origin, shape)
|
|
||||||
transform_channels = {key for key in self.keys() if isinstance(key, str)}
|
|
||||||
if set(channel_names) - transform_channels:
|
|
||||||
mapping = key_map(channel_names, transform_channels)
|
|
||||||
warnings.warn(
|
|
||||||
f"The image file and the transform do not have the same channels,"
|
|
||||||
f" creating a mapping: {mapping}"
|
|
||||||
)
|
|
||||||
for key_im, key_t in mapping.items():
|
|
||||||
self[key_im] = self[key_t]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inverse(self):
|
|
||||||
# TODO: check for C@T
|
|
||||||
inverse = self.copy()
|
|
||||||
for key, value in self.items():
|
|
||||||
inverse[key] = value.inverse
|
|
||||||
return inverse
|
|
||||||
|
|
||||||
def coords_pandas(self, array, channel_names, columns=None):
|
|
||||||
if isinstance(array, DataFrame):
|
|
||||||
return concat(
|
|
||||||
[
|
|
||||||
self.coords_pandas(row, channel_names, columns)
|
|
||||||
for _, row in array.iterrows()
|
|
||||||
],
|
|
||||||
axis=1,
|
|
||||||
).T
|
|
||||||
elif isinstance(array, Series):
|
|
||||||
key = []
|
|
||||||
if "C" in array:
|
|
||||||
key.append(channel_names[int(array["C"])])
|
|
||||||
if "T" in array:
|
|
||||||
key.append(int(array["T"]))
|
|
||||||
return self[tuple(key)].coords(array, columns)
|
|
||||||
else:
|
|
||||||
raise TypeError("Not a pandas DataFrame or Series.")
|
|
||||||
|
|
||||||
def with_beads(self, cyllens, bead_files):
|
|
||||||
assert len(bead_files) > 0, (
|
|
||||||
"At least one file is needed to calculate the registration."
|
|
||||||
)
|
|
||||||
transforms = [
|
|
||||||
self.calculate_channel_transforms(file, cyllens) for file in bead_files
|
|
||||||
]
|
|
||||||
for key in {key for transform in transforms for key in transform.keys()}:
|
|
||||||
new_transforms = [
|
|
||||||
transform[key] for transform in transforms if key in transform
|
|
||||||
]
|
|
||||||
if len(new_transforms) == 1:
|
|
||||||
self[key] = new_transforms[0]
|
|
||||||
else:
|
|
||||||
self[key] = Transform()
|
|
||||||
self[key].parameters = np.mean(
|
|
||||||
[t.parameters for t in new_transforms], 0
|
|
||||||
)
|
|
||||||
self[key].dparameters = (
|
|
||||||
np.std([t.parameters for t in new_transforms], 0)
|
|
||||||
/ np.sqrt(len(new_transforms))
|
|
||||||
).tolist()
|
|
||||||
return self
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_bead_files(path):
|
|
||||||
from . import Imread
|
|
||||||
|
|
||||||
files = []
|
|
||||||
for file in path.iterdir():
|
|
||||||
if file.name.lower().startswith("beads"):
|
|
||||||
try:
|
|
||||||
with Imread(file):
|
|
||||||
files.append(file)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
files = sorted(files)
|
|
||||||
if not files:
|
|
||||||
raise Exception("No bead file found!")
|
|
||||||
checked_files = []
|
|
||||||
for file in files:
|
|
||||||
try:
|
|
||||||
if file.is_dir():
|
|
||||||
file /= "Pos0"
|
|
||||||
with Imread(file): # check for errors opening the file
|
|
||||||
checked_files.append(file)
|
|
||||||
except (Exception,):
|
|
||||||
continue
|
|
||||||
if not checked_files:
|
|
||||||
raise Exception("No bead file found!")
|
|
||||||
return checked_files
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def calculate_channel_transforms(bead_file, cyllens):
|
|
||||||
"""When no channel is not transformed by a cylindrical lens, assume that the image is scaled by a factor 1.162
|
|
||||||
in the horizontal direction"""
|
|
||||||
from . import Imread
|
|
||||||
|
|
||||||
with Imread(bead_file, axes="zcyx") as im: # noqa
|
|
||||||
max_ims = im.max("z")
|
|
||||||
goodch = [c for c, max_im in enumerate(max_ims) if not im.is_noise(max_im)]
|
|
||||||
if not goodch:
|
|
||||||
goodch = list(range(len(max_ims)))
|
|
||||||
untransformed = [
|
|
||||||
c
|
|
||||||
for c in range(im.shape["c"])
|
|
||||||
if cyllens[im.detector[c]].lower() == "none"
|
|
||||||
]
|
|
||||||
|
|
||||||
good_and_untrans = sorted(set(goodch) & set(untransformed))
|
|
||||||
if good_and_untrans:
|
|
||||||
masterch = good_and_untrans[0]
|
|
||||||
else:
|
|
||||||
masterch = goodch[0]
|
|
||||||
transform = Transform()
|
|
||||||
if not good_and_untrans:
|
|
||||||
matrix = transform.matrix
|
|
||||||
matrix[0, 0] = 0.86
|
|
||||||
transform.matrix = matrix
|
|
||||||
transforms = Transforms()
|
|
||||||
for c in tqdm(goodch, desc="Calculating channel transforms"): # noqa
|
|
||||||
if c == masterch:
|
|
||||||
transforms[im.channel_names[c]] = transform
|
|
||||||
else:
|
|
||||||
transforms[im.channel_names[c]] = (
|
|
||||||
Transform.register(max_ims[masterch], max_ims[c]) * transform
|
|
||||||
)
|
|
||||||
return transforms
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def save_channel_transform_tiff(bead_files, tiffile):
|
|
||||||
from . import Imread
|
|
||||||
|
|
||||||
n_channels = 0
|
|
||||||
for file in bead_files:
|
|
||||||
with Imread(file) as im:
|
|
||||||
n_channels = max(n_channels, im.shape["c"])
|
|
||||||
with IJTiffFile(tiffile) as tif:
|
|
||||||
for t, file in enumerate(bead_files):
|
|
||||||
with Imread(file) as im:
|
|
||||||
with Imread(file).with_transform() as jm:
|
|
||||||
for c in range(im.shape["c"]):
|
|
||||||
tif.save(
|
|
||||||
np.hstack(
|
|
||||||
(im(c=c, t=0).max("z"), jm(c=c, t=0).max("z"))
|
|
||||||
),
|
|
||||||
c,
|
|
||||||
0,
|
|
||||||
t,
|
|
||||||
)
|
|
||||||
|
|
||||||
def with_drift(self, im):
|
|
||||||
"""Calculate shifts relative to the first frame
|
|
||||||
divide the sequence into groups,
|
|
||||||
compare each frame to the frame in the middle of the group and compare these middle frames to each other
|
|
||||||
"""
|
|
||||||
im = im.transpose("tzycx")
|
|
||||||
t_groups = [
|
|
||||||
list(chunk)
|
|
||||||
for chunk in Chunks(
|
|
||||||
range(im.shape["t"]), size=round(np.sqrt(im.shape["t"]))
|
|
||||||
)
|
|
||||||
]
|
|
||||||
t_keys = [int(np.round(np.mean(t_group))) for t_group in t_groups]
|
|
||||||
t_pairs = [
|
|
||||||
(int(np.round(np.mean(t_group))), frame)
|
|
||||||
for t_group in t_groups
|
|
||||||
for frame in t_group
|
|
||||||
]
|
|
||||||
t_pairs.extend(zip(t_keys, t_keys[1:]))
|
|
||||||
fmaxz_keys = {
|
|
||||||
t_key: filters.gaussian(im[t_key].max("z"), 5) for t_key in t_keys
|
|
||||||
}
|
|
||||||
|
|
||||||
def fun(t_key_t, im, fmaxz_keys):
|
|
||||||
t_key, t = t_key_t
|
|
||||||
if t_key == t:
|
|
||||||
return 0, 0
|
|
||||||
else:
|
|
||||||
fmaxz = filters.gaussian(im[t].max("z"), 5)
|
|
||||||
return Transform.register(
|
|
||||||
fmaxz_keys[t_key], fmaxz, "translation"
|
|
||||||
).parameters[4:]
|
|
||||||
|
|
||||||
shifts = np.array(
|
|
||||||
pmap(fun, t_pairs, (im, fmaxz_keys), desc="Calculating image shifts.")
|
|
||||||
)
|
|
||||||
shift_keys_cum = np.zeros(2)
|
|
||||||
for shift_keys, t_group in zip(
|
|
||||||
np.vstack((-shifts[0], shifts[im.shape["t"] :])), t_groups
|
|
||||||
):
|
|
||||||
shift_keys_cum += shift_keys
|
|
||||||
shifts[t_group] += shift_keys_cum
|
|
||||||
|
|
||||||
for i, shift in enumerate(shifts[: im.shape["t"]]):
|
|
||||||
self[i] = Transform.from_shift(shift)
|
|
||||||
return self
|
|
||||||
|
|
||||||
|
|
||||||
class Transform:
|
|
||||||
def __init__(self):
|
|
||||||
if sitk is None:
|
|
||||||
self.transform = None
|
|
||||||
else:
|
|
||||||
self.transform = sitk.ReadTransform(
|
|
||||||
str(Path(__file__).parent / "transform.txt")
|
|
||||||
)
|
|
||||||
self.dparameters = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
|
||||||
self.shape = [512.0, 512.0]
|
|
||||||
self.origin = [255.5, 255.5]
|
|
||||||
self._last, self._inverse = None, None
|
|
||||||
|
|
||||||
def __reduce__(self):
|
|
||||||
return self.from_dict, (self.asdict(),)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return self.asdict().__repr__()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.asdict().__str__()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def register(cls, fix, mov, kind=None):
|
|
||||||
"""kind: 'affine', 'translation', 'rigid'"""
|
|
||||||
if sitk is None:
|
|
||||||
raise ImportError(
|
|
||||||
"SimpleElastix is not installed: "
|
|
||||||
"https://simpleelastix.readthedocs.io/GettingStarted.html"
|
|
||||||
)
|
|
||||||
new = cls()
|
|
||||||
kind = kind or "affine"
|
|
||||||
new.shape = fix.shape
|
|
||||||
fix, mov = new.cast_image(fix), new.cast_image(mov)
|
|
||||||
# TODO: implement RigidTransform
|
|
||||||
tfilter = sitk.ElastixImageFilter()
|
|
||||||
tfilter.LogToConsoleOff()
|
|
||||||
tfilter.SetFixedImage(fix)
|
|
||||||
tfilter.SetMovingImage(mov)
|
|
||||||
tfilter.SetParameterMap(sitk.GetDefaultParameterMap(kind))
|
|
||||||
tfilter.Execute()
|
|
||||||
transform = tfilter.GetTransformParameterMap()[0]
|
|
||||||
if kind == "affine":
|
|
||||||
new.parameters = [float(t) for t in transform["TransformParameters"]]
|
|
||||||
new.shape = [float(t) for t in transform["Size"]]
|
|
||||||
new.origin = [float(t) for t in transform["CenterOfRotationPoint"]]
|
|
||||||
elif kind == "translation":
|
|
||||||
new.parameters = [1.0, 0.0, 0.0, 1.0] + [
|
|
||||||
float(t) for t in transform["TransformParameters"]
|
|
||||||
]
|
|
||||||
new.shape = [float(t) for t in transform["Size"]]
|
|
||||||
new.origin = [(t - 1) / 2 for t in new.shape]
|
|
||||||
else:
|
|
||||||
raise NotImplementedError(f"{kind} tranforms not implemented (yet)")
|
|
||||||
new.dparameters = 6 * [np.nan]
|
|
||||||
return new
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_shift(cls, shift):
|
|
||||||
return cls.from_array(np.array(((1, 0, shift[0]), (0, 1, shift[1]), (0, 0, 1))))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_array(cls, array):
|
|
||||||
new = cls()
|
|
||||||
new.matrix = array
|
|
||||||
return new
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_file(cls, file):
|
|
||||||
with open(Path(file).with_suffix(".yml")) as f:
|
|
||||||
return cls.from_dict(yamlload(f))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls, d):
|
|
||||||
new = cls()
|
|
||||||
new.origin = (
|
|
||||||
None
|
|
||||||
if d["CenterOfRotationPoint"] is None
|
|
||||||
else [float(i) for i in d["CenterOfRotationPoint"]]
|
|
||||||
)
|
|
||||||
new.parameters = (
|
|
||||||
(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
|
|
||||||
if d["TransformParameters"] is None
|
|
||||||
else [float(i) for i in d["TransformParameters"]]
|
|
||||||
)
|
|
||||||
new.dparameters = (
|
|
||||||
[
|
|
||||||
(0.0, 0.0, 0.0, 0.0, 0.0, 0.0) if i is None else float(i)
|
|
||||||
for i in d["dTransformParameters"]
|
|
||||||
]
|
|
||||||
if "dTransformParameters" in d
|
|
||||||
else 6 * [np.nan] and d["dTransformParameters"] is not None
|
|
||||||
)
|
|
||||||
new.shape = (
|
|
||||||
None
|
|
||||||
if d["Size"] is None
|
|
||||||
else [None if i is None else float(i) for i in d["Size"]]
|
|
||||||
)
|
|
||||||
return new
|
|
||||||
|
|
||||||
def __mul__(self, other): # TODO: take care of dmatrix
|
|
||||||
result = self.copy()
|
|
||||||
if isinstance(other, Transform):
|
|
||||||
result.matrix = self.matrix @ other.matrix
|
|
||||||
result.dmatrix = self.dmatrix @ other.matrix + self.matrix @ other.dmatrix
|
|
||||||
else:
|
|
||||||
result.matrix = self.matrix @ other
|
|
||||||
result.dmatrix = self.dmatrix @ other
|
|
||||||
return result
|
|
||||||
|
|
||||||
def is_unity(self):
|
|
||||||
return self.parameters == [1, 0, 0, 1, 0, 0]
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
return deepcopy(self)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def cast_image(im):
|
|
||||||
if not isinstance(im, sitk.Image):
|
|
||||||
im = sitk.GetImageFromArray(np.asarray(im))
|
|
||||||
return im
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def cast_array(im):
|
|
||||||
if isinstance(im, sitk.Image):
|
|
||||||
im = sitk.GetArrayFromImage(im)
|
|
||||||
return im
|
|
||||||
|
|
||||||
@property
|
|
||||||
def matrix(self):
|
|
||||||
return np.array(
|
|
||||||
(
|
|
||||||
(*self.parameters[:2], self.parameters[4]),
|
|
||||||
(*self.parameters[2:4], self.parameters[5]),
|
|
||||||
(0, 0, 1),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@matrix.setter
|
|
||||||
def matrix(self, value):
|
|
||||||
value = np.asarray(value)
|
|
||||||
self.parameters = [*value[0, :2], *value[1, :2], *value[:2, 2]]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def dmatrix(self):
|
|
||||||
return np.array(
|
|
||||||
(
|
|
||||||
(*self.dparameters[:2], self.dparameters[4]),
|
|
||||||
(*self.dparameters[2:4], self.dparameters[5]),
|
|
||||||
(0, 0, 0),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@dmatrix.setter
|
|
||||||
def dmatrix(self, value):
|
|
||||||
value = np.asarray(value)
|
|
||||||
self.dparameters = [*value[0, :2], *value[1, :2], *value[:2, 2]]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def parameters(self):
|
|
||||||
if self.transform is not None:
|
|
||||||
return list(self.transform.GetParameters())
|
|
||||||
else:
|
|
||||||
return [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]
|
|
||||||
|
|
||||||
@parameters.setter
|
|
||||||
def parameters(self, value):
|
|
||||||
if self.transform is not None:
|
|
||||||
value = np.asarray(value)
|
|
||||||
self.transform.SetParameters(value.tolist())
|
|
||||||
|
|
||||||
@property
|
|
||||||
def origin(self):
|
|
||||||
if self.transform is not None:
|
|
||||||
return self.transform.GetFixedParameters()
|
|
||||||
|
|
||||||
@origin.setter
|
|
||||||
def origin(self, value):
|
|
||||||
if self.transform is not None:
|
|
||||||
value = np.asarray(value)
|
|
||||||
self.transform.SetFixedParameters(value.tolist())
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inverse(self):
|
|
||||||
if self.is_unity():
|
|
||||||
return self
|
|
||||||
if self._last is None or self._last != self.asdict():
|
|
||||||
self._last = self.asdict()
|
|
||||||
self._inverse = Transform.from_dict(self.asdict())
|
|
||||||
self._inverse.transform = self._inverse.transform.GetInverse()
|
|
||||||
self._inverse._last = self._inverse.asdict()
|
|
||||||
self._inverse._inverse = self
|
|
||||||
return self._inverse
|
|
||||||
|
|
||||||
def adapt(self, origin, shape):
|
|
||||||
self.origin -= np.array(origin) + (self.shape - np.array(shape)[:2]) / 2
|
|
||||||
self.shape = shape[:2]
|
|
||||||
|
|
||||||
def asdict(self):
|
|
||||||
return {
|
|
||||||
"CenterOfRotationPoint": self.origin,
|
|
||||||
"Size": self.shape,
|
|
||||||
"TransformParameters": self.parameters,
|
|
||||||
"dTransformParameters": np.nan_to_num(self.dparameters, nan=1e99).tolist(),
|
|
||||||
}
|
|
||||||
|
|
||||||
def frame(self, im, default=0):
|
|
||||||
if self.is_unity():
|
|
||||||
return im
|
|
||||||
else:
|
|
||||||
if sitk is None:
|
|
||||||
raise ImportError(
|
|
||||||
"SimpleElastix is not installed: "
|
|
||||||
"https://simpleelastix.readthedocs.io/GettingStarted.html"
|
|
||||||
)
|
|
||||||
dtype = im.dtype
|
|
||||||
im = im.astype("float")
|
|
||||||
intp = (
|
|
||||||
sitk.sitkBSpline
|
|
||||||
if np.issubdtype(dtype, np.floating)
|
|
||||||
else sitk.sitkNearestNeighbor
|
|
||||||
)
|
|
||||||
return self.cast_array(
|
|
||||||
sitk.Resample(self.cast_image(im), self.transform, intp, default)
|
|
||||||
).astype(dtype)
|
|
||||||
|
|
||||||
def coords(self, array, columns=None):
|
|
||||||
"""Transform coordinates in 2 column numpy array,
|
|
||||||
or in pandas DataFrame or Series objects in columns ['x', 'y']
|
|
||||||
"""
|
|
||||||
if self.is_unity():
|
|
||||||
return array.copy()
|
|
||||||
elif DataFrame is not None and isinstance(array, (DataFrame, Series)):
|
|
||||||
columns = columns or ["x", "y"]
|
|
||||||
array = array.copy()
|
|
||||||
if isinstance(array, DataFrame):
|
|
||||||
array[columns] = self.coords(np.atleast_2d(array[columns].to_numpy()))
|
|
||||||
elif isinstance(array, Series):
|
|
||||||
array[columns] = self.coords(np.atleast_2d(array[columns].to_numpy()))[
|
|
||||||
0
|
|
||||||
]
|
|
||||||
return array
|
|
||||||
else: # somehow we need to use the inverse here to get the same effect as when using self.frame
|
|
||||||
return np.array(
|
|
||||||
[
|
|
||||||
self.inverse.transform.TransformPoint(i.tolist())
|
|
||||||
for i in np.asarray(array)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
def save(self, file):
|
|
||||||
"""save the parameters of the transform calculated
|
|
||||||
with affine_registration to a yaml file
|
|
||||||
"""
|
|
||||||
if not file[-3:] == "yml":
|
|
||||||
file += ".yml"
|
|
||||||
with open(file, "w") as f:
|
|
||||||
yaml.safe_dump(self.asdict(), f, default_flow_style=None)
|
|
||||||
+2
-2
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "ndbioimage"
|
name = "ndbioimage"
|
||||||
version = "2027.0.3"
|
version = "2027.0.4"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
@@ -35,7 +35,7 @@ ndbioimage_generate_stub = "ndbioimage:ndbioimage_generate_stub"
|
|||||||
|
|
||||||
[tool.maturin]
|
[tool.maturin]
|
||||||
python-source = "py"
|
python-source = "py"
|
||||||
features = ["python", "bioformats_java", "gpl-formats", "czi", "tiff", "tiffseq", "tiffwrite", "movie"]
|
features = ["python", "bioformats_java", "gpl-formats", "czi", "tiff", "tiffseq", "tiffwrite", "movie", "transforms"]
|
||||||
no-default-features = true
|
no-default-features = true
|
||||||
module-name = "ndbioimage.ndbioimage_rs"
|
module-name = "ndbioimage.ndbioimage_rs"
|
||||||
include = ["py/ndbioimage/jassets/j4rs*", "py/ndbioimage/deps/libj4rs*"]
|
include = ["py/ndbioimage/jassets/j4rs*", "py/ndbioimage/deps/libj4rs*"]
|
||||||
|
|||||||
+24
-14
@@ -10,8 +10,8 @@ pub enum Error {
|
|||||||
/// an ndarray shape error
|
/// an ndarray shape error
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Shape(#[from] ndarray::ShapeError),
|
Shape(#[from] ndarray::ShapeError),
|
||||||
#[cfg(feature = "bioformats_java")]
|
|
||||||
/// an error from the j4rs java bridge
|
/// an error from the j4rs java bridge
|
||||||
|
#[cfg(feature = "bioformats_java")]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
J4rs(#[from] j4rs::errors::J4RsError),
|
J4rs(#[from] j4rs::errors::J4RsError),
|
||||||
/// an infallible conversion
|
/// an infallible conversion
|
||||||
@@ -23,60 +23,64 @@ pub enum Error {
|
|||||||
/// an ome metadata error
|
/// an ome metadata error
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Ome(#[from] ome_metadata::error::Error),
|
Ome(#[from] ome_metadata::error::Error),
|
||||||
#[cfg(feature = "bioformats_java")]
|
|
||||||
/// an error while downloading (e.g. the bioformats jar)
|
/// an error while downloading (e.g. the bioformats jar)
|
||||||
|
#[cfg(feature = "bioformats_java")]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Downloader(#[from] downloader::Error),
|
Downloader(#[from] downloader::Error),
|
||||||
/// an error parsing an enum string with strum
|
/// an error parsing an enum string with strum
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Strum(#[from] strum::ParseError),
|
Strum(#[from] strum::ParseError),
|
||||||
#[cfg(feature = "tiffwrite")]
|
|
||||||
/// an indicatif progress bar template error
|
/// an indicatif progress bar template error
|
||||||
|
#[cfg(feature = "tiffwrite")]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
TemplateError(#[from] indicatif::style::TemplateError),
|
TemplateError(#[from] indicatif::style::TemplateError),
|
||||||
#[cfg(feature = "tiffwrite")]
|
|
||||||
/// an error from the tiffwrite crate
|
/// an error from the tiffwrite crate
|
||||||
|
#[cfg(feature = "tiffwrite")]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
TiffWrite(#[from] tiffwrite::error::Error),
|
TiffWrite(#[from] tiffwrite::error::Error),
|
||||||
#[cfg(feature = "tiffseq")]
|
|
||||||
/// a yaml (de)serialization error
|
/// a yaml (de)serialization error
|
||||||
|
#[cfg(feature = "tiffseq")]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
SerdeYaml(#[from] serde_yaml::Error),
|
SerdeYaml(#[from] yaml_serde::Error),
|
||||||
#[cfg(any(feature = "tiffseq", feature = "tiff"))]
|
|
||||||
/// an error from the tiff crate
|
/// an error from the tiff crate
|
||||||
|
#[cfg(any(feature = "tiffseq", feature = "tiff"))]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Tiff(#[from] tiff::TiffError),
|
Tiff(#[from] tiff::TiffError),
|
||||||
#[cfg(feature = "python")]
|
|
||||||
/// a postcard (de)serialization error
|
/// a postcard (de)serialization error
|
||||||
|
#[cfg(feature = "python")]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
PostCard(#[from] postcard::Error),
|
PostCard(#[from] postcard::Error),
|
||||||
#[cfg(feature = "czi")]
|
|
||||||
/// an error from the libczi binding
|
/// an error from the libczi binding
|
||||||
|
#[cfg(feature = "czi")]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
LibCzi(#[from] libczirw_sys::error::Error),
|
LibCzi(#[from] libczirw_sys::error::Error),
|
||||||
/// a regex error
|
/// a regex error
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
RegexError(#[from] regex::Error),
|
RegexError(#[from] regex::Error),
|
||||||
#[cfg(feature = "czi")]
|
|
||||||
/// an xmltree error
|
/// an xmltree error
|
||||||
|
#[cfg(feature = "czi")]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
XmlTree(#[from] xmltree::Error),
|
XmlTree(#[from] xmltree::Error),
|
||||||
#[cfg(feature = "czi")]
|
|
||||||
/// an xmltree parse error
|
/// an xmltree parse error
|
||||||
|
#[cfg(feature = "czi")]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
XmlTreeParse(#[from] xmltree::ParseError),
|
XmlTreeParse(#[from] xmltree::ParseError),
|
||||||
#[cfg(feature = "czi")]
|
|
||||||
/// a czi-specific error
|
/// a czi-specific error
|
||||||
|
#[cfg(feature = "czi")]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Czi(#[from] crate::readers::czi::CziError),
|
Czi(#[from] crate::readers::czi::CziError),
|
||||||
#[cfg(feature = "movie")]
|
|
||||||
/// an error joining a tokio task
|
/// an error joining a tokio task
|
||||||
|
#[cfg(feature = "movie")]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
TokioJoin(#[from] tokio::task::JoinError),
|
TokioJoin(#[from] tokio::task::JoinError),
|
||||||
#[cfg(feature = "bioformats_rust")]
|
|
||||||
/// an error from the bioformats rust crate
|
/// an error from the bioformats rust crate
|
||||||
|
#[cfg(feature = "bioformats_rust")]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
BioFormats(#[from] bioformats::error::BioFormatsError),
|
BioFormats(#[from] bioformats::error::BioFormatsError),
|
||||||
|
/// an image registration / transforms error
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
#[error(transparent)]
|
||||||
|
ImageRegistration(#[from] image_registration::error::Error),
|
||||||
|
|
||||||
/// the axis string could not be parsed
|
/// the axis string could not be parsed
|
||||||
#[error("invalid axis: {0}")]
|
#[error("invalid axis: {0}")]
|
||||||
@@ -162,6 +166,12 @@ pub enum Error {
|
|||||||
/// cannot remove axes that have a size != 1
|
/// cannot remove axes that have a size != 1
|
||||||
#[error("cannot remove axes {0}, size {1} != 1")]
|
#[error("cannot remove axes {0}, size {1} != 1")]
|
||||||
SizeMismatch(String, usize),
|
SizeMismatch(String, usize),
|
||||||
|
/// shape mismatch
|
||||||
|
#[error("shape mismatch: {0:?}, {1:?}")]
|
||||||
|
ShapeMismatch(Vec<usize>, Vec<usize>),
|
||||||
|
/// file mismatch
|
||||||
|
#[error("{0} do not match in {1} and {2}")]
|
||||||
|
FileMismatch(String, String, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
|
|||||||
+2217
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,675 @@
|
|||||||
|
use crate::error::Error;
|
||||||
|
use crate::transforms::Transforms;
|
||||||
|
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::path::PathBuf;
|
||||||
|
|
||||||
|
#[gen_stub_pyclass]
|
||||||
|
#[pyclass(
|
||||||
|
subclass,
|
||||||
|
from_py_object,
|
||||||
|
eq,
|
||||||
|
name = "Transform",
|
||||||
|
module = "ndbioimage"
|
||||||
|
)]
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub(crate) struct PyTransform {
|
||||||
|
pub(crate) inner: Transform<IxDyn>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gen_stub_pymethods]
|
||||||
|
#[pymethods]
|
||||||
|
impl PyTransform {
|
||||||
|
#[new]
|
||||||
|
#[pyo3(signature = (parameters, shape, center = None))]
|
||||||
|
fn new(parameters: Vec<f64>, shape: Vec<usize>, center: Option<Vec<f64>>) -> Self {
|
||||||
|
if let Some(center) = center {
|
||||||
|
Self {
|
||||||
|
inner: Transform::new_with_center(parameters, center, shape),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Self {
|
||||||
|
inner: Transform::new(parameters, shape),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __getnewargs__(&self) -> (Vec<f64>, Vec<usize>, Option<Vec<f64>>) {
|
||||||
|
(
|
||||||
|
self.inner.parameters.clone(),
|
||||||
|
self.inner.shape.clone(),
|
||||||
|
Some(self.inner.center.clone()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __getstate__(&self) -> Vec<f64> {
|
||||||
|
self.inner.dparameters.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __setstate__(&mut self, state: Vec<f64>) {
|
||||||
|
self.inner.dparameters = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __add__(&self, other: &PyTransform) -> PyTransform {
|
||||||
|
PyTransform {
|
||||||
|
inner: &self.inner + &other.inner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __radd__(&self, other: &PyTransform) -> PyTransform {
|
||||||
|
PyTransform {
|
||||||
|
inner: &other.inner + &self.inner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __sub__(&self, other: &PyTransform) -> PyTransform {
|
||||||
|
PyTransform {
|
||||||
|
inner: &self.inner - &other.inner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __rsub__(&self, other: &PyTransform) -> PyTransform {
|
||||||
|
PyTransform {
|
||||||
|
inner: &other.inner - &self.inner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __mul__(
|
||||||
|
&self,
|
||||||
|
py: Python,
|
||||||
|
#[gen_stub(override_type(type_repr = "Transform | float"))] other: &Bound<PyAny>,
|
||||||
|
) -> PyResult<PyTransform> {
|
||||||
|
if other.is_instance_of::<PyTransform>() {
|
||||||
|
Ok(PyTransform {
|
||||||
|
inner: &self.inner * &other.extract::<PyTransform>()?.inner,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let builtins = PyModule::import(py, "builtins")?;
|
||||||
|
let other = builtins.getattr("float")?.call1((&other,))?;
|
||||||
|
Ok(PyTransform {
|
||||||
|
inner: &self.inner * other.extract::<f64>()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __rmul__(
|
||||||
|
&self,
|
||||||
|
py: Python,
|
||||||
|
#[gen_stub(override_type(type_repr = "Transform | float"))] other: &Bound<PyAny>,
|
||||||
|
) -> PyResult<PyTransform> {
|
||||||
|
if other.is_instance_of::<PyTransform>() {
|
||||||
|
Ok(PyTransform {
|
||||||
|
inner: &other.extract::<PyTransform>()?.inner * &self.inner,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let builtins = PyModule::import(py, "builtins")?;
|
||||||
|
let other = builtins.getattr("float")?.call1((&other,))?;
|
||||||
|
Ok(PyTransform {
|
||||||
|
inner: other.extract::<f64>()? * &self.inner,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __truediv__(&self, py: Python, other: f64) -> PyResult<PyTransform> {
|
||||||
|
let builtins = PyModule::import(py, "builtins")?;
|
||||||
|
let other = builtins.getattr("float")?.call1((&other,))?;
|
||||||
|
Ok(PyTransform {
|
||||||
|
inner: &self.inner / other.extract::<f64>()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn get_parameters(&self) -> Vec<f64> {
|
||||||
|
self.inner.parameters.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[setter]
|
||||||
|
fn set_parameters(&mut self, parameters: Vec<f64>) {
|
||||||
|
self.inner.parameters = parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn get_dparameters(&self) -> Vec<f64> {
|
||||||
|
self.inner.dparameters.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[setter]
|
||||||
|
fn set_dparameters(&mut self, dparameters: Vec<f64>) {
|
||||||
|
self.inner.dparameters = dparameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn get_center(&self) -> Vec<f64> {
|
||||||
|
self.inner.center.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[setter]
|
||||||
|
fn set_center(&mut self, center: Vec<f64>) {
|
||||||
|
self.inner.center = center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn get_shape(&self) -> Vec<usize> {
|
||||||
|
self.inner.shape.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[setter]
|
||||||
|
fn set_shape(&mut self, shape: Vec<usize>) {
|
||||||
|
self.inner.shape = shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn get_ndim(&self) -> usize {
|
||||||
|
self.inner.ndim()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn get_matrix<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray<f64, Ix2>> {
|
||||||
|
self.inner.matrix().into_pyarray(py)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[setter]
|
||||||
|
fn set_matrix(
|
||||||
|
&mut self,
|
||||||
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
|
matrix: PyArrayLike2<f64, AllowTypeChange>,
|
||||||
|
) {
|
||||||
|
let matrix = matrix.as_array();
|
||||||
|
self.inner.set_matrix(matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn get_dmatrix<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray<f64, Ix2>> {
|
||||||
|
self.inner.dmatrix().into_pyarray(py)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[setter]
|
||||||
|
fn set_dmatrix(
|
||||||
|
&mut self,
|
||||||
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
|
dmatrix: PyArrayLike2<f64, AllowTypeChange>,
|
||||||
|
) {
|
||||||
|
let dmatrix = dmatrix.as_array();
|
||||||
|
self.inner.set_dmatrix(dmatrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn inverse(&self) -> PyResult<PyTransform> {
|
||||||
|
Ok(PyTransform {
|
||||||
|
inner: self.inner.inverse().map_err(Error::from)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn adapt(&mut self, center: Vec<f64>, shape: Vec<usize>) {
|
||||||
|
self.inner.adapt(center.as_slice(), shape.as_slice());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
fn from_scaling(scaling: Vec<f64>) -> PyTransform {
|
||||||
|
PyTransform {
|
||||||
|
inner: Transform::from_scaling(scaling.as_slice()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
fn from_translation(translation: Vec<f64>) -> PyTransform {
|
||||||
|
PyTransform {
|
||||||
|
inner: Transform::from_translation(translation.as_slice()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
fn from_rotation(theta: f64, center: Vec<f64>) -> PyTransform {
|
||||||
|
PyTransform {
|
||||||
|
inner: Transform::from_rotation(theta, center.as_slice()).into_dyn(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_scaling(&self, scaling: Vec<f64>) -> PyTransform {
|
||||||
|
PyTransform {
|
||||||
|
inner: Transform::from_scaling(scaling.as_slice()) * self.inner.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_translation(&self, translation: Vec<f64>) -> PyTransform {
|
||||||
|
PyTransform {
|
||||||
|
inner: Transform::from_translation(translation.as_slice()) * self.inner.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_rotation(&self, theta: f64, center: Vec<f64>) -> PyTransform {
|
||||||
|
PyTransform {
|
||||||
|
inner: Transform::from_rotation(theta, center.as_slice()).into_dyn()
|
||||||
|
* self.inner.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interpolate<'py>(
|
||||||
|
&self,
|
||||||
|
py: Python<'py>,
|
||||||
|
order: usize,
|
||||||
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
|
image: PyArrayLikeDyn<f64, AllowTypeChange>,
|
||||||
|
) -> PyResult<Bound<'py, PyArrayDyn<f64>>> {
|
||||||
|
let image = image.as_array();
|
||||||
|
Ok(py
|
||||||
|
.detach(|| match order {
|
||||||
|
0 => Ok(self
|
||||||
|
.inner
|
||||||
|
.interpolate::<0, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||||
|
1 => Ok(self
|
||||||
|
.inner
|
||||||
|
.interpolate::<1, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||||
|
2 => Ok(self
|
||||||
|
.inner
|
||||||
|
.interpolate::<2, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||||
|
3 => Ok(self
|
||||||
|
.inner
|
||||||
|
.interpolate::<3, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||||
|
4 => Ok(self
|
||||||
|
.inner
|
||||||
|
.interpolate::<4, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||||
|
5 => Ok(self
|
||||||
|
.inner
|
||||||
|
.interpolate::<5, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||||
|
_ => Err(PyValueError::new_err("order must be 0 <= order < 6")),
|
||||||
|
})?
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_pyarray(py))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interpolate_par<'py>(
|
||||||
|
&self,
|
||||||
|
py: Python<'py>,
|
||||||
|
order: usize,
|
||||||
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
|
image: PyArrayLikeDyn<f64, AllowTypeChange>,
|
||||||
|
) -> PyResult<Bound<'py, PyArrayDyn<f64>>> {
|
||||||
|
let image = image.as_array();
|
||||||
|
Ok(py
|
||||||
|
.detach(|| match order {
|
||||||
|
0 => Ok(self
|
||||||
|
.inner
|
||||||
|
.interpolate_par::<0, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||||
|
1 => Ok(self
|
||||||
|
.inner
|
||||||
|
.interpolate_par::<1, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||||
|
2 => Ok(self
|
||||||
|
.inner
|
||||||
|
.interpolate_par::<2, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||||
|
3 => Ok(self
|
||||||
|
.inner
|
||||||
|
.interpolate_par::<3, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||||
|
4 => Ok(self
|
||||||
|
.inner
|
||||||
|
.interpolate_par::<4, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||||
|
5 => Ok(self
|
||||||
|
.inner
|
||||||
|
.interpolate_par::<5, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||||
|
_ => Err(PyValueError::new_err("order must be 0 <= order < 6")),
|
||||||
|
})?
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_pyarray(py))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_unity(&self) -> bool {
|
||||||
|
self.inner.is_unity()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_point<'py>(
|
||||||
|
&self,
|
||||||
|
py: Python<'py>,
|
||||||
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
|
point: PyArrayLike1<f64, AllowTypeChange>,
|
||||||
|
) -> Bound<'py, PyArray1<f64>> {
|
||||||
|
let point = point.as_array();
|
||||||
|
if let Some(slice) = point.as_slice() {
|
||||||
|
self.inner.transform_point(slice)
|
||||||
|
} else {
|
||||||
|
let point = point.to_vec();
|
||||||
|
self.inner.transform_point(&point)
|
||||||
|
}
|
||||||
|
.into_pyarray(py)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_points<'py>(
|
||||||
|
&self,
|
||||||
|
py: Python<'py>,
|
||||||
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
|
points: PyArrayLike2<f64, AllowTypeChange>,
|
||||||
|
) -> PyResult<Bound<'py, PyArray2<f64>>> {
|
||||||
|
let points = points.as_array();
|
||||||
|
Ok(py
|
||||||
|
.detach(|| self.inner.transform_points(points))
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_pyarray(py))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
#[pyo3(signature = (fixed, moving, fixed_mu, initial_guess = None))]
|
||||||
|
fn register<'py>(
|
||||||
|
py: Python<'py>,
|
||||||
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
|
fixed: PyArrayLikeDyn<f64, AllowTypeChange>,
|
||||||
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
|
moving: PyArrayLikeDyn<f64, AllowTypeChange>,
|
||||||
|
fixed_mu: Vec<Option<f64>>,
|
||||||
|
initial_guess: Option<Vec<f64>>,
|
||||||
|
) -> PyResult<PyTransform> {
|
||||||
|
let fixed = fixed.as_array();
|
||||||
|
let moving = moving.as_array();
|
||||||
|
if fixed.shape() != moving.shape() {
|
||||||
|
return Err(PyErr::from(Error::ShapeMismatch(
|
||||||
|
fixed.shape().to_vec(),
|
||||||
|
moving.shape().to_vec(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
py.detach(|| match fixed.ndim() {
|
||||||
|
0 => Ok(PyTransform {
|
||||||
|
inner: Transform::register(
|
||||||
|
fixed.into_dimensionality::<Ix0>().map_err(Error::from)?,
|
||||||
|
moving.into_dimensionality::<Ix0>().map_err(Error::from)?,
|
||||||
|
fixed_mu,
|
||||||
|
None,
|
||||||
|
initial_guess,
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_dyn(),
|
||||||
|
}),
|
||||||
|
1 => Ok(PyTransform {
|
||||||
|
inner: Transform::register(
|
||||||
|
fixed.into_dimensionality::<Ix1>().map_err(Error::from)?,
|
||||||
|
moving.into_dimensionality::<Ix1>().map_err(Error::from)?,
|
||||||
|
fixed_mu,
|
||||||
|
None,
|
||||||
|
initial_guess,
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_dyn(),
|
||||||
|
}),
|
||||||
|
2 => Ok(PyTransform {
|
||||||
|
inner: Transform::register(
|
||||||
|
fixed.into_dimensionality::<Ix2>().map_err(Error::from)?,
|
||||||
|
moving.into_dimensionality::<Ix2>().map_err(Error::from)?,
|
||||||
|
fixed_mu,
|
||||||
|
None,
|
||||||
|
initial_guess,
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_dyn(),
|
||||||
|
}),
|
||||||
|
3 => Ok(PyTransform {
|
||||||
|
inner: Transform::register(
|
||||||
|
fixed.into_dimensionality::<Ix3>().map_err(Error::from)?,
|
||||||
|
moving.into_dimensionality::<Ix3>().map_err(Error::from)?,
|
||||||
|
fixed_mu,
|
||||||
|
None,
|
||||||
|
initial_guess,
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_dyn(),
|
||||||
|
}),
|
||||||
|
4 => Ok(PyTransform {
|
||||||
|
inner: Transform::register(
|
||||||
|
fixed.into_dimensionality::<Ix4>().map_err(Error::from)?,
|
||||||
|
moving.into_dimensionality::<Ix4>().map_err(Error::from)?,
|
||||||
|
fixed_mu,
|
||||||
|
None,
|
||||||
|
initial_guess,
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_dyn(),
|
||||||
|
}),
|
||||||
|
5 => Ok(PyTransform {
|
||||||
|
inner: Transform::register(
|
||||||
|
fixed.into_dimensionality::<Ix5>().map_err(Error::from)?,
|
||||||
|
moving.into_dimensionality::<Ix5>().map_err(Error::from)?,
|
||||||
|
fixed_mu,
|
||||||
|
None,
|
||||||
|
initial_guess,
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_dyn(),
|
||||||
|
}),
|
||||||
|
_ => Err(PyNotImplementedError::new_err(format!(
|
||||||
|
"registration in {} dimensions is not implemented",
|
||||||
|
fixed.ndim()
|
||||||
|
))),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
fn register_affine<'py>(
|
||||||
|
py: Python<'py>,
|
||||||
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
|
fixed: PyArrayLikeDyn<f64, AllowTypeChange>,
|
||||||
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
|
moving: PyArrayLikeDyn<f64, AllowTypeChange>,
|
||||||
|
) -> PyResult<PyTransform> {
|
||||||
|
let fixed = fixed.as_array();
|
||||||
|
let moving = moving.as_array();
|
||||||
|
if fixed.shape() != moving.shape() {
|
||||||
|
return Err(PyErr::from(Error::ShapeMismatch(
|
||||||
|
fixed.shape().to_vec(),
|
||||||
|
moving.shape().to_vec(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
py.detach(|| match fixed.ndim() {
|
||||||
|
0 => Ok(PyTransform {
|
||||||
|
inner: Transform::register_affine(
|
||||||
|
fixed.into_dimensionality::<Ix0>().map_err(Error::from)?,
|
||||||
|
moving.into_dimensionality::<Ix0>().map_err(Error::from)?,
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_dyn(),
|
||||||
|
}),
|
||||||
|
1 => Ok(PyTransform {
|
||||||
|
inner: Transform::register_affine(
|
||||||
|
fixed.into_dimensionality::<Ix1>().map_err(Error::from)?,
|
||||||
|
moving.into_dimensionality::<Ix1>().map_err(Error::from)?,
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_dyn(),
|
||||||
|
}),
|
||||||
|
2 => Ok(PyTransform {
|
||||||
|
inner: Transform::register_affine(
|
||||||
|
fixed.into_dimensionality::<Ix2>().map_err(Error::from)?,
|
||||||
|
moving.into_dimensionality::<Ix2>().map_err(Error::from)?,
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_dyn(),
|
||||||
|
}),
|
||||||
|
3 => Ok(PyTransform {
|
||||||
|
inner: Transform::register_affine(
|
||||||
|
fixed.into_dimensionality::<Ix3>().map_err(Error::from)?,
|
||||||
|
moving.into_dimensionality::<Ix3>().map_err(Error::from)?,
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_dyn(),
|
||||||
|
}),
|
||||||
|
4 => Ok(PyTransform {
|
||||||
|
inner: Transform::register_affine(
|
||||||
|
fixed.into_dimensionality::<Ix4>().map_err(Error::from)?,
|
||||||
|
moving.into_dimensionality::<Ix4>().map_err(Error::from)?,
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_dyn(),
|
||||||
|
}),
|
||||||
|
5 => Ok(PyTransform {
|
||||||
|
inner: Transform::register_affine(
|
||||||
|
fixed.into_dimensionality::<Ix5>().map_err(Error::from)?,
|
||||||
|
moving.into_dimensionality::<Ix5>().map_err(Error::from)?,
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_dyn(),
|
||||||
|
}),
|
||||||
|
_ => Err(PyNotImplementedError::new_err(format!(
|
||||||
|
"registration in {} dimensions is not implemented",
|
||||||
|
fixed.ndim()
|
||||||
|
))),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
fn register_translation<'py>(
|
||||||
|
py: Python<'py>,
|
||||||
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
|
fixed: PyArrayLikeDyn<f64, AllowTypeChange>,
|
||||||
|
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||||
|
moving: PyArrayLikeDyn<f64, AllowTypeChange>,
|
||||||
|
) -> PyResult<PyTransform> {
|
||||||
|
let fixed = fixed.as_array();
|
||||||
|
let moving = moving.as_array();
|
||||||
|
if fixed.shape() != moving.shape() {
|
||||||
|
return Err(PyErr::from(Error::ShapeMismatch(
|
||||||
|
fixed.shape().to_vec(),
|
||||||
|
moving.shape().to_vec(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
py.detach(|| match fixed.ndim() {
|
||||||
|
0 => Ok(PyTransform {
|
||||||
|
inner: Transform::register_translation(
|
||||||
|
fixed.into_dimensionality::<Ix0>().map_err(Error::from)?,
|
||||||
|
moving.into_dimensionality::<Ix0>().map_err(Error::from)?,
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_dyn(),
|
||||||
|
}),
|
||||||
|
1 => Ok(PyTransform {
|
||||||
|
inner: Transform::register_translation(
|
||||||
|
fixed.into_dimensionality::<Ix1>().map_err(Error::from)?,
|
||||||
|
moving.into_dimensionality::<Ix1>().map_err(Error::from)?,
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_dyn(),
|
||||||
|
}),
|
||||||
|
2 => Ok(PyTransform {
|
||||||
|
inner: Transform::register_translation(
|
||||||
|
fixed.into_dimensionality::<Ix2>().map_err(Error::from)?,
|
||||||
|
moving.into_dimensionality::<Ix2>().map_err(Error::from)?,
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_dyn(),
|
||||||
|
}),
|
||||||
|
3 => Ok(PyTransform {
|
||||||
|
inner: Transform::register_translation(
|
||||||
|
fixed.into_dimensionality::<Ix3>().map_err(Error::from)?,
|
||||||
|
moving.into_dimensionality::<Ix3>().map_err(Error::from)?,
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_dyn(),
|
||||||
|
}),
|
||||||
|
4 => Ok(PyTransform {
|
||||||
|
inner: Transform::register_translation(
|
||||||
|
fixed.into_dimensionality::<Ix4>().map_err(Error::from)?,
|
||||||
|
moving.into_dimensionality::<Ix4>().map_err(Error::from)?,
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_dyn(),
|
||||||
|
}),
|
||||||
|
5 => Ok(PyTransform {
|
||||||
|
inner: Transform::register_translation(
|
||||||
|
fixed.into_dimensionality::<Ix5>().map_err(Error::from)?,
|
||||||
|
moving.into_dimensionality::<Ix5>().map_err(Error::from)?,
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_dyn(),
|
||||||
|
}),
|
||||||
|
_ => Err(PyNotImplementedError::new_err(format!(
|
||||||
|
"registration in {} dimensions is not implemented",
|
||||||
|
fixed.ndim()
|
||||||
|
))),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gen_stub_pyclass]
|
||||||
|
#[pyclass(
|
||||||
|
subclass,
|
||||||
|
from_py_object,
|
||||||
|
eq,
|
||||||
|
name = "Transforms",
|
||||||
|
module = "ndbioimage"
|
||||||
|
)]
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub(crate) struct PyTransforms {
|
||||||
|
pub(crate) inner: Transforms,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gen_stub_pymethods]
|
||||||
|
#[pymethods]
|
||||||
|
impl PyTransforms {
|
||||||
|
pub(crate) fn __getstate__(&self) -> PyResult<Vec<u8>> {
|
||||||
|
Ok(to_stdvec(self).map_err(Error::from)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn __setstate__(&mut self, state: Vec<u8>) -> PyResult<()> {
|
||||||
|
Ok(from_bytes(&state).map_err(Error::from)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
pub(crate) fn load(path: PathBuf) -> PyResult<PyTransforms> {
|
||||||
|
Ok(PyTransforms {
|
||||||
|
inner: Transforms::load(&path)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn save(&self, path: PathBuf) -> PyResult<()> {
|
||||||
|
Ok(self.inner.save(&path)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
fn calculate_channel_transforms_2d(
|
||||||
|
bead_files: Vec<PathBuf>,
|
||||||
|
main_channel: usize,
|
||||||
|
default_transform: Option<PyTransform>,
|
||||||
|
) -> PyResult<Vec<PyTransform>> {
|
||||||
|
Ok(Transforms::calculate_channel_transforms_2d(
|
||||||
|
&bead_files
|
||||||
|
.iter()
|
||||||
|
.map(|file| file.as_path())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
main_channel,
|
||||||
|
default_transform
|
||||||
|
.map(|d| d.inner.into_dimensionality().map_err(Error::from))
|
||||||
|
.transpose()?,
|
||||||
|
)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|t| PyTransform {
|
||||||
|
inner: t.into_dyn(),
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[staticmethod]
|
||||||
|
fn calculate_channel_transforms_3d(
|
||||||
|
bead_files: Vec<PathBuf>,
|
||||||
|
main_channel: usize,
|
||||||
|
default_transform: Option<PyTransform>,
|
||||||
|
) -> PyResult<Vec<PyTransform>> {
|
||||||
|
Ok(Transforms::calculate_channel_transforms_3d(
|
||||||
|
&bead_files
|
||||||
|
.iter()
|
||||||
|
.map(|file| file.as_path())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
main_channel,
|
||||||
|
default_transform
|
||||||
|
.map(|d| d.inner.into_dimensionality().map_err(Error::from))
|
||||||
|
.transpose()?,
|
||||||
|
)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|t| PyTransform {
|
||||||
|
inner: t.into_dyn(),
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-12
@@ -100,9 +100,10 @@ impl Dimensions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// pixel type enum
|
/// pixel type enum
|
||||||
#[allow(clippy::upper_case_acronyms)]
|
|
||||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub enum PixelType {
|
pub enum PixelType {
|
||||||
|
/// true / false
|
||||||
|
Bool,
|
||||||
/// signed 8-bit integer
|
/// signed 8-bit integer
|
||||||
I8,
|
I8,
|
||||||
/// unsigned 8-bit integer
|
/// unsigned 8-bit integer
|
||||||
@@ -135,7 +136,7 @@ impl PixelType {
|
|||||||
/// number of bytes per pixel for this type
|
/// number of bytes per pixel for this type
|
||||||
pub fn bytes_per_pixel(&self) -> usize {
|
pub fn bytes_per_pixel(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
PixelType::I8 | PixelType::U8 => 1,
|
PixelType::Bool | PixelType::I8 | PixelType::U8 => 1,
|
||||||
PixelType::I16 | PixelType::U16 => 2,
|
PixelType::I16 | PixelType::U16 => 2,
|
||||||
PixelType::I32 | PixelType::U32 | PixelType::F32 => 4,
|
PixelType::I32 | PixelType::U32 | PixelType::F32 => 4,
|
||||||
PixelType::I64 | PixelType::U64 | PixelType::F64 => 8,
|
PixelType::I64 | PixelType::U64 | PixelType::F64 => 8,
|
||||||
@@ -145,7 +146,6 @@ impl PixelType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// array data with a specific pixel type
|
/// array data with a specific pixel type
|
||||||
#[allow(clippy::upper_case_acronyms)]
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum ArrayT<D: Dimension> {
|
pub enum ArrayT<D: Dimension> {
|
||||||
/// signed 8-bit integer array
|
/// signed 8-bit integer array
|
||||||
@@ -207,7 +207,6 @@ pub trait Reader: Clone + Sized + Debug + Send + Hash + Into<DynReader> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// retrieve frame at channel c, slice 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>;
|
fn get_frame(&self, c: usize, z: usize, t: usize) -> Result<Frame, Error>;
|
||||||
|
|
||||||
/// the path to the image file
|
/// the path to the image file
|
||||||
@@ -486,7 +485,7 @@ impl Reader for DynReader {
|
|||||||
DynReader::BioFormatsRust(r) => r.reader_name(),
|
DynReader::BioFormatsRust(r) => r.reader_name(),
|
||||||
#[cfg(feature = "bioformats_java")]
|
#[cfg(feature = "bioformats_java")]
|
||||||
DynReader::BioFormatsJava(r) => r.reader_name(),
|
DynReader::BioFormatsJava(r) => r.reader_name(),
|
||||||
#[allow(unreachable_patterns)]
|
#[expect(unreachable_patterns)]
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -503,7 +502,7 @@ impl Reader for DynReader {
|
|||||||
DynReader::BioFormatsRust(r) => r.metadata()?,
|
DynReader::BioFormatsRust(r) => r.metadata()?,
|
||||||
#[cfg(feature = "bioformats_java")]
|
#[cfg(feature = "bioformats_java")]
|
||||||
DynReader::BioFormatsJava(r) => r.metadata()?,
|
DynReader::BioFormatsJava(r) => r.metadata()?,
|
||||||
#[allow(unreachable_patterns)]
|
#[expect(unreachable_patterns)]
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -520,7 +519,7 @@ impl Reader for DynReader {
|
|||||||
DynReader::BioFormatsRust(r) => r.get_frame(c, z, t),
|
DynReader::BioFormatsRust(r) => r.get_frame(c, z, t),
|
||||||
#[cfg(feature = "bioformats_java")]
|
#[cfg(feature = "bioformats_java")]
|
||||||
DynReader::BioFormatsJava(r) => r.get_frame(c, z, t),
|
DynReader::BioFormatsJava(r) => r.get_frame(c, z, t),
|
||||||
#[allow(unreachable_patterns)]
|
#[expect(unreachable_patterns)]
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -537,7 +536,7 @@ impl Reader for DynReader {
|
|||||||
DynReader::BioFormatsRust(r) => r.path(),
|
DynReader::BioFormatsRust(r) => r.path(),
|
||||||
#[cfg(feature = "bioformats_java")]
|
#[cfg(feature = "bioformats_java")]
|
||||||
DynReader::BioFormatsJava(r) => r.path(),
|
DynReader::BioFormatsJava(r) => r.path(),
|
||||||
#[allow(unreachable_patterns)]
|
#[expect(unreachable_patterns)]
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -554,7 +553,7 @@ impl Reader for DynReader {
|
|||||||
DynReader::BioFormatsRust(r) => r.series(),
|
DynReader::BioFormatsRust(r) => r.series(),
|
||||||
#[cfg(feature = "bioformats_java")]
|
#[cfg(feature = "bioformats_java")]
|
||||||
DynReader::BioFormatsJava(r) => r.series(),
|
DynReader::BioFormatsJava(r) => r.series(),
|
||||||
#[allow(unreachable_patterns)]
|
#[expect(unreachable_patterns)]
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -571,7 +570,7 @@ impl Reader for DynReader {
|
|||||||
DynReader::BioFormatsRust(r) => r.position(),
|
DynReader::BioFormatsRust(r) => r.position(),
|
||||||
#[cfg(feature = "bioformats_java")]
|
#[cfg(feature = "bioformats_java")]
|
||||||
DynReader::BioFormatsJava(r) => r.position(),
|
DynReader::BioFormatsJava(r) => r.position(),
|
||||||
#[allow(unreachable_patterns)]
|
#[expect(unreachable_patterns)]
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -588,7 +587,7 @@ impl Reader for DynReader {
|
|||||||
DynReader::BioFormatsRust(r) => r.shape(),
|
DynReader::BioFormatsRust(r) => r.shape(),
|
||||||
#[cfg(feature = "bioformats_java")]
|
#[cfg(feature = "bioformats_java")]
|
||||||
DynReader::BioFormatsJava(r) => r.shape(),
|
DynReader::BioFormatsJava(r) => r.shape(),
|
||||||
#[allow(unreachable_patterns)]
|
#[expect(unreachable_patterns)]
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -605,7 +604,7 @@ impl Reader for DynReader {
|
|||||||
DynReader::BioFormatsRust(r) => r.pixel_type(),
|
DynReader::BioFormatsRust(r) => r.pixel_type(),
|
||||||
#[cfg(feature = "bioformats_java")]
|
#[cfg(feature = "bioformats_java")]
|
||||||
DynReader::BioFormatsJava(r) => r.pixel_type(),
|
DynReader::BioFormatsJava(r) => r.pixel_type(),
|
||||||
#[allow(unreachable_patterns)]
|
#[expect(unreachable_patterns)]
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -389,6 +389,7 @@ impl BioFormatsJavaReader {
|
|||||||
|
|
||||||
fn deinterleave(&self, bytes: Vec<u8>, channel: usize) -> Result<Vec<u8>, Error> {
|
fn deinterleave(&self, bytes: Vec<u8>, channel: usize) -> Result<Vec<u8>, Error> {
|
||||||
let chunk_size = match self.pixel_type {
|
let chunk_size = match self.pixel_type {
|
||||||
|
PixelType::Bool => 1,
|
||||||
PixelType::I8 => 1,
|
PixelType::I8 => 1,
|
||||||
PixelType::U8 => 1,
|
PixelType::U8 => 1,
|
||||||
PixelType::I16 => 2,
|
PixelType::I16 => 2,
|
||||||
@@ -413,6 +414,26 @@ impl BioFormatsJavaReader {
|
|||||||
|
|
||||||
fn bytes_to_frame(&self, bytes: Vec<u8>) -> Result<Frame, Error> {
|
fn bytes_to_frame(&self, bytes: Vec<u8>) -> Result<Frame, Error> {
|
||||||
macro_rules! get_frame {
|
macro_rules! get_frame {
|
||||||
|
(bool, <$n:expr) => {
|
||||||
|
Ok(ArrayT::from(Array2::from_shape_vec(
|
||||||
|
(self.shape.y, self.shape.x),
|
||||||
|
bytes
|
||||||
|
.iter()
|
||||||
|
.map(|x| [x & 128, x & 64, x & 32, x & 16, x & 8, x & 4, x & 2, x & 1])
|
||||||
|
.flatten()
|
||||||
|
.collect(),
|
||||||
|
)?))
|
||||||
|
};
|
||||||
|
(bool, >$n:expr) => {
|
||||||
|
Ok(ArrayT::from(Array2::from_shape_vec(
|
||||||
|
(self.shape.y, self.shape.x),
|
||||||
|
bytes
|
||||||
|
.iter()
|
||||||
|
.map(|x| [x & 1, x & 2, x & 4, x & 8, x & 16, x & 32, x & 64, x & 128])
|
||||||
|
.flatten()
|
||||||
|
.collect(),
|
||||||
|
)?))
|
||||||
|
};
|
||||||
($t:tt, <$n:expr) => {
|
($t:tt, <$n:expr) => {
|
||||||
Ok(ArrayT::from(Array2::from_shape_vec(
|
Ok(ArrayT::from(Array2::from_shape_vec(
|
||||||
(self.shape.y, self.shape.x),
|
(self.shape.y, self.shape.x),
|
||||||
@@ -434,6 +455,7 @@ impl BioFormatsJavaReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match (&self.pixel_type, self.little_endian) {
|
match (&self.pixel_type, self.little_endian) {
|
||||||
|
(PixelType::Bool, true) => get_frame!(bool, <1),
|
||||||
(PixelType::I8, true) => get_frame!(i8, <1),
|
(PixelType::I8, true) => get_frame!(i8, <1),
|
||||||
(PixelType::U8, true) => get_frame!(u8, <1),
|
(PixelType::U8, true) => get_frame!(u8, <1),
|
||||||
(PixelType::I16, true) => get_frame!(i16, <2),
|
(PixelType::I16, true) => get_frame!(i16, <2),
|
||||||
@@ -447,6 +469,7 @@ impl BioFormatsJavaReader {
|
|||||||
(PixelType::I128, true) => get_frame!(i128, <16),
|
(PixelType::I128, true) => get_frame!(i128, <16),
|
||||||
(PixelType::U128, true) => get_frame!(u128, <16),
|
(PixelType::U128, true) => get_frame!(u128, <16),
|
||||||
(PixelType::F128, true) => get_frame!(f64, <8),
|
(PixelType::F128, true) => get_frame!(f64, <8),
|
||||||
|
(PixelType::Bool, false) => get_frame!(bool, >1),
|
||||||
(PixelType::I8, false) => get_frame!(i8, >1),
|
(PixelType::I8, false) => get_frame!(i8, >1),
|
||||||
(PixelType::U8, false) => get_frame!(u8, >1),
|
(PixelType::U8, false) => get_frame!(u8, >1),
|
||||||
(PixelType::I16, false) => get_frame!(i16, >2),
|
(PixelType::I16, false) => get_frame!(i16, >2),
|
||||||
@@ -501,7 +524,7 @@ impl Reader for BioFormatsJavaReader {
|
|||||||
Error::FileDoesNotExist(orig.join("**").join("*.tif").display().to_string())
|
Error::FileDoesNotExist(orig.join("**").join("*.tif").display().to_string())
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
let mut new = BioFormatsJavaReader {
|
let mut new = Self {
|
||||||
reader: ThreadLocal::default(),
|
reader: ThreadLocal::default(),
|
||||||
path,
|
path,
|
||||||
series,
|
series,
|
||||||
|
|||||||
@@ -78,19 +78,20 @@ impl Deref for BioFormatsRustReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_pixel_type(bf: bioformats::PixelType) -> Result<PixelType, Error> {
|
impl From<bioformats::PixelType> for PixelType {
|
||||||
use bioformats::PixelType as Bf;
|
fn from(bf: bioformats::PixelType) -> Self {
|
||||||
Ok(match bf {
|
match bf {
|
||||||
Bf::Int8 => PixelType::I8,
|
bioformats::PixelType::Bit => PixelType::Bool,
|
||||||
Bf::Uint8 => PixelType::U8,
|
bioformats::PixelType::Int8 => PixelType::I8,
|
||||||
Bf::Int16 => PixelType::I16,
|
bioformats::PixelType::Uint8 => PixelType::U8,
|
||||||
Bf::Uint16 => PixelType::U16,
|
bioformats::PixelType::Int16 => PixelType::I16,
|
||||||
Bf::Int32 => PixelType::I32,
|
bioformats::PixelType::Uint16 => PixelType::U16,
|
||||||
Bf::Uint32 => PixelType::U32,
|
bioformats::PixelType::Int32 => PixelType::I32,
|
||||||
Bf::Float32 => PixelType::F32,
|
bioformats::PixelType::Uint32 => PixelType::U32,
|
||||||
Bf::Float64 => PixelType::F64,
|
bioformats::PixelType::Float32 => PixelType::F32,
|
||||||
Bf::Bit => PixelType::U8,
|
bioformats::PixelType::Float64 => PixelType::F64,
|
||||||
})
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BioFormatsRustReader {
|
impl BioFormatsRustReader {
|
||||||
@@ -126,6 +127,7 @@ impl BioFormatsRustReader {
|
|||||||
|
|
||||||
fn deinterleave(&self, bytes: Vec<u8>, channel: usize) -> Result<Vec<u8>, Error> {
|
fn deinterleave(&self, bytes: Vec<u8>, channel: usize) -> Result<Vec<u8>, Error> {
|
||||||
let chunk_size = match self.pixel_type {
|
let chunk_size = match self.pixel_type {
|
||||||
|
PixelType::Bool => 1,
|
||||||
PixelType::I8 => 1,
|
PixelType::I8 => 1,
|
||||||
PixelType::U8 => 1,
|
PixelType::U8 => 1,
|
||||||
PixelType::I16 => 2,
|
PixelType::I16 => 2,
|
||||||
@@ -150,6 +152,26 @@ impl BioFormatsRustReader {
|
|||||||
|
|
||||||
fn bytes_to_frame(&self, bytes: Vec<u8>) -> Result<Frame, Error> {
|
fn bytes_to_frame(&self, bytes: Vec<u8>) -> Result<Frame, Error> {
|
||||||
macro_rules! get_frame {
|
macro_rules! get_frame {
|
||||||
|
(bool, <$n:expr) => {
|
||||||
|
Ok(ArrayT::from(Array2::from_shape_vec(
|
||||||
|
(self.shape.y, self.shape.x),
|
||||||
|
bytes
|
||||||
|
.iter()
|
||||||
|
.map(|x| [x & 128, x & 64, x & 32, x & 16, x & 8, x & 4, x & 2, x & 1])
|
||||||
|
.flatten()
|
||||||
|
.collect(),
|
||||||
|
)?))
|
||||||
|
};
|
||||||
|
(bool, >$n:expr) => {
|
||||||
|
Ok(ArrayT::from(Array2::from_shape_vec(
|
||||||
|
(self.shape.y, self.shape.x),
|
||||||
|
bytes
|
||||||
|
.iter()
|
||||||
|
.map(|x| [x & 1, x & 2, x & 4, x & 8, x & 16, x & 32, x & 64, x & 128])
|
||||||
|
.flatten()
|
||||||
|
.collect(),
|
||||||
|
)?))
|
||||||
|
};
|
||||||
($t:tt, <$n:expr) => {
|
($t:tt, <$n:expr) => {
|
||||||
Ok(ArrayT::from(Array2::from_shape_vec(
|
Ok(ArrayT::from(Array2::from_shape_vec(
|
||||||
(self.shape.y, self.shape.x),
|
(self.shape.y, self.shape.x),
|
||||||
@@ -171,6 +193,7 @@ impl BioFormatsRustReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match (&self.pixel_type, self.little_endian) {
|
match (&self.pixel_type, self.little_endian) {
|
||||||
|
(PixelType::Bool, true) => get_frame!(bool, <1),
|
||||||
(PixelType::I8, true) => get_frame!(i8, <1),
|
(PixelType::I8, true) => get_frame!(i8, <1),
|
||||||
(PixelType::U8, true) => get_frame!(u8, <1),
|
(PixelType::U8, true) => get_frame!(u8, <1),
|
||||||
(PixelType::I16, true) => get_frame!(i16, <2),
|
(PixelType::I16, true) => get_frame!(i16, <2),
|
||||||
@@ -184,6 +207,7 @@ impl BioFormatsRustReader {
|
|||||||
(PixelType::I128, true) => get_frame!(i128, <16),
|
(PixelType::I128, true) => get_frame!(i128, <16),
|
||||||
(PixelType::U128, true) => get_frame!(u128, <16),
|
(PixelType::U128, true) => get_frame!(u128, <16),
|
||||||
(PixelType::F128, true) => get_frame!(f64, <8),
|
(PixelType::F128, true) => get_frame!(f64, <8),
|
||||||
|
(PixelType::Bool, false) => get_frame!(bool, >1),
|
||||||
(PixelType::I8, false) => get_frame!(i8, >1),
|
(PixelType::I8, false) => get_frame!(i8, >1),
|
||||||
(PixelType::U8, false) => get_frame!(u8, >1),
|
(PixelType::U8, false) => get_frame!(u8, >1),
|
||||||
(PixelType::I16, false) => get_frame!(i16, >2),
|
(PixelType::I16, false) => get_frame!(i16, >2),
|
||||||
@@ -246,7 +270,7 @@ impl Reader for BioFormatsRustReader {
|
|||||||
new.shape.y = metadata.size_y as usize;
|
new.shape.y = metadata.size_y as usize;
|
||||||
new.shape.x = metadata.size_x as usize;
|
new.shape.x = metadata.size_x as usize;
|
||||||
new.little_endian = metadata.is_little_endian;
|
new.little_endian = metadata.is_little_endian;
|
||||||
new.pixel_type = map_pixel_type(metadata.pixel_type)?;
|
new.pixel_type = PixelType::from(metadata.pixel_type);
|
||||||
Ok(new)
|
Ok(new)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,6 +328,13 @@ impl Reader for BioFormatsRustReader {
|
|||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
|
let mut path = path.as_ref().to_path_buf();
|
||||||
|
if path.is_dir() {
|
||||||
|
let orig = path.clone();
|
||||||
|
path = find_tiff(&path)?.ok_or_else(|| {
|
||||||
|
Error::FileDoesNotExist(orig.join("**").join("*.tif").display().to_string())
|
||||||
|
})?;
|
||||||
|
}
|
||||||
let reader = ImageReader::open(path.as_ref())
|
let reader = ImageReader::open(path.as_ref())
|
||||||
.map_err(|e| Error::Parse(format!("bioformats failed to open: {}", e)))?;
|
.map_err(|e| Error::Parse(format!("bioformats failed to open: {}", e)))?;
|
||||||
let n = reader.series_count();
|
let n = reader.series_count();
|
||||||
|
|||||||
@@ -1053,6 +1053,44 @@ impl Reader for CziReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! get_frame {
|
macro_rules! get_frame {
|
||||||
|
(bool, $n:expr) => {{
|
||||||
|
let mut array = Array2::zeros((self.shape.y, self.shape.x));
|
||||||
|
if let Some(indices) = self.block_map.get(&(c, z, t)) {
|
||||||
|
for &i in indices {
|
||||||
|
let sub_block = reader.read_sub_block(i)?;
|
||||||
|
let bitmap = sub_block.create_bitmap()?.lock()?;
|
||||||
|
let bytes = bitmap.lock_info.get_data_roi();
|
||||||
|
let info = sub_block.get_info()?;
|
||||||
|
let rect = info.get_logical_rect();
|
||||||
|
let x = (rect.get_x() - min_x) as usize;
|
||||||
|
let y = (rect.get_y() - min_y) as usize;
|
||||||
|
let w = rect.get_w() as usize;
|
||||||
|
let h = rect.get_h() as usize;
|
||||||
|
array
|
||||||
|
.slice_mut(s![x..x + w, y..y + h])
|
||||||
|
.assign(&Array2::from_shape_vec(
|
||||||
|
(w, h),
|
||||||
|
bytes
|
||||||
|
.iter()
|
||||||
|
.map(|x| {
|
||||||
|
[
|
||||||
|
x & 128,
|
||||||
|
x & 64,
|
||||||
|
x & 32,
|
||||||
|
x & 16,
|
||||||
|
x & 8,
|
||||||
|
x & 4,
|
||||||
|
x & 2,
|
||||||
|
x & 1,
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect(),
|
||||||
|
)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ArrayT::from(array))
|
||||||
|
}};
|
||||||
($t:tt, $n:expr) => {{
|
($t:tt, $n:expr) => {{
|
||||||
let mut array = Array2::zeros((self.shape.y, self.shape.x));
|
let mut array = Array2::zeros((self.shape.y, self.shape.x));
|
||||||
if let Some(indices) = self.block_map.get(&(c, z, t)) {
|
if let Some(indices) = self.block_map.get(&(c, z, t)) {
|
||||||
@@ -1082,6 +1120,7 @@ impl Reader for CziReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match self.pixel_type {
|
match self.pixel_type {
|
||||||
|
PixelType::Bool => get_frame!(bool, 1),
|
||||||
PixelType::I8 => get_frame!(i8, 1),
|
PixelType::I8 => get_frame!(i8, 1),
|
||||||
PixelType::U8 => get_frame!(u8, 1),
|
PixelType::U8 => get_frame!(u8, 1),
|
||||||
PixelType::I16 => get_frame!(i16, 2),
|
PixelType::I16 => get_frame!(i16, 2),
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ pub struct TiffSeqReader {
|
|||||||
filedict: HashMap<(usize, usize, usize), PathBuf>,
|
filedict: HashMap<(usize, usize, usize), PathBuf>,
|
||||||
cnamelist: Vec<String>,
|
cnamelist: Vec<String>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
metadata_map: HashMap<String, serde_yaml::Value>,
|
metadata_map: HashMap<String, yaml_serde::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TiffSeqReader> for DynReader {
|
impl From<TiffSeqReader> for DynReader {
|
||||||
@@ -73,10 +73,10 @@ impl TiffSeqReader {
|
|||||||
Ok(files)
|
Ok(files)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_metadata_from_file(dir: &Path) -> Result<HashMap<String, serde_yaml::Value>, Error> {
|
fn read_metadata_from_file(dir: &Path) -> Result<HashMap<String, yaml_serde::Value>, Error> {
|
||||||
let md_path = dir.join("metadata.txt");
|
let md_path = dir.join("metadata.txt");
|
||||||
let text = std::fs::read_to_string(&md_path)?;
|
let text = std::fs::read_to_string(&md_path)?;
|
||||||
let parsed: serde_yaml::Value = serde_yaml::from_str(&text)?;
|
let parsed: yaml_serde::Value = yaml_serde::from_str(&text)?;
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert("Info".to_string(), parsed);
|
map.insert("Info".to_string(), parsed);
|
||||||
Ok(map)
|
Ok(map)
|
||||||
@@ -129,11 +129,11 @@ impl Reader for TiffSeqReader {
|
|||||||
.ok_or_else(|| Error::Parse("missing Info key in tag 50839".to_string()))?;
|
.ok_or_else(|| Error::Parse("missing Info key in tag 50839".to_string()))?;
|
||||||
|
|
||||||
let lookup = |key: &str| {
|
let lookup = |key: &str| {
|
||||||
info.get(serde_yaml::Value::String(key.to_string()))
|
info.get(yaml_serde::Value::String(key.to_string()))
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
info.get(serde_yaml::Value::String("Summary".to_string()))
|
info.get(yaml_serde::Value::String("Summary".to_string()))
|
||||||
.and_then(|s| s.as_mapping())
|
.and_then(|s| s.as_mapping())
|
||||||
.and_then(|s| s.get(serde_yaml::Value::String(key.to_string())))
|
.and_then(|s| s.get(yaml_serde::Value::String(key.to_string())))
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -240,12 +240,12 @@ impl Reader for TiffSeqReader {
|
|||||||
let info = self.metadata_map.get("Info").and_then(|v| v.as_mapping());
|
let info = self.metadata_map.get("Info").and_then(|v| v.as_mapping());
|
||||||
|
|
||||||
let slookup =
|
let slookup =
|
||||||
|key: &str| info.and_then(|m| m.get(serde_yaml::Value::String(key.to_string())));
|
|key: &str| info.and_then(|m| m.get(yaml_serde::Value::String(key.to_string())));
|
||||||
|
|
||||||
let summary = slookup("Summary").and_then(|v| v.as_mapping());
|
let summary = slookup("Summary").and_then(|v| v.as_mapping());
|
||||||
|
|
||||||
let summary_lookup =
|
let summary_lookup =
|
||||||
|key: &str| summary.and_then(|m| m.get(serde_yaml::Value::String(key.to_string())));
|
|key: &str| summary.and_then(|m| m.get(yaml_serde::Value::String(key.to_string())));
|
||||||
|
|
||||||
let first_frame = info.and_then(|m| {
|
let first_frame = info.and_then(|m| {
|
||||||
m.iter()
|
m.iter()
|
||||||
@@ -254,7 +254,7 @@ impl Reader for TiffSeqReader {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let frame_lookup =
|
let frame_lookup =
|
||||||
|key: &str| first_frame.and_then(|m| m.get(serde_yaml::Value::String(key.to_string())));
|
|key: &str| first_frame.and_then(|m| m.get(yaml_serde::Value::String(key.to_string())));
|
||||||
|
|
||||||
let ome_pixel_type = match self.pixel_type {
|
let ome_pixel_type = match self.pixel_type {
|
||||||
PixelType::I8 => ome::PixelType::Int8,
|
PixelType::I8 => ome::PixelType::Int8,
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ where
|
|||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
match self.pixel_type() {
|
match self.pixel_type() {
|
||||||
|
PixelType::Bool => self.save_as_tiff_with_type::<u8, P>(path, options)?,
|
||||||
PixelType::I8 => self.save_as_tiff_with_type::<i8, P>(path, options)?,
|
PixelType::I8 => self.save_as_tiff_with_type::<i8, P>(path, options)?,
|
||||||
PixelType::U8 => self.save_as_tiff_with_type::<u8, P>(path, options)?,
|
PixelType::U8 => self.save_as_tiff_with_type::<u8, P>(path, options)?,
|
||||||
PixelType::I16 => self.save_as_tiff_with_type::<i16, P>(path, options)?,
|
PixelType::I16 => self.save_as_tiff_with_type::<i16, P>(path, options)?,
|
||||||
|
|||||||
+162
-7
@@ -1,20 +1,175 @@
|
|||||||
use crate::readers::Reader;
|
use crate::axes::Axis;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::readers::{DynReader, Reader};
|
||||||
use crate::view::View;
|
use crate::view::View;
|
||||||
pub use image_registration::transform::Transform;
|
pub use image_registration::transform::Transform;
|
||||||
use ndarray::{Dimension, Ix2, Ix3};
|
use ndarray::{Dimension, Ix2, Ix3, s};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[expect(clippy::upper_case_acronyms)]
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum TransformD {
|
pub enum TransformD {
|
||||||
YX(Transform<Ix2>),
|
YX(Transform<Ix2>),
|
||||||
ZYX(Transform<Ix3>),
|
ZYX(Transform<Ix3>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: Dimension, R: Reader> View<D, R> {}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Transforms {
|
pub struct Transforms {
|
||||||
channel: Vec<TransformD>,
|
pub channel: Vec<TransformD>,
|
||||||
drift: Vec<TransformD>,
|
pub drift: Vec<TransformD>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 save(&self, path: &Path) -> Result<(), Error> {
|
||||||
|
let file = std::fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.open(path)?;
|
||||||
|
Ok(yaml_serde::to_writer(file, self)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_channel_transforms_2d(
|
||||||
|
bead_files: &[&Path],
|
||||||
|
main_channel: usize,
|
||||||
|
default_transform: Option<Transform<Ix2>>,
|
||||||
|
) -> Result<Vec<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
|
||||||
|
.iter()
|
||||||
|
.map(|t| (&t[channel] * &default_transform).matrix())
|
||||||
|
.reduce(|a, b| a + b)
|
||||||
|
.unwrap()
|
||||||
|
/ n_channels as f64;
|
||||||
|
let dmatrix = 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(
|
||||||
|
Transform::default()
|
||||||
|
.with_matrix(matrix.view())
|
||||||
|
.with_dmatrix(dmatrix.view()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(average_transforms)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_channel_transforms_3d(
|
||||||
|
bead_files: &[&Path],
|
||||||
|
main_channel: usize,
|
||||||
|
default_transform: Option<Transform<Ix3>>,
|
||||||
|
) -> Result<Vec<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
|
||||||
|
.iter()
|
||||||
|
.map(|t| (&t[channel] * &default_transform).matrix())
|
||||||
|
.reduce(|a, b| a + b)
|
||||||
|
.unwrap()
|
||||||
|
/ n_channels as f64;
|
||||||
|
let dmatrix = 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(
|
||||||
|
Transform::default()
|
||||||
|
.with_matrix(matrix.view())
|
||||||
|
.with_dmatrix(dmatrix.view()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(average_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)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_transform_from_yaml(&mut self, path: &Path) -> Result<(), Error> {
|
||||||
|
self.transforms = Transforms::load(path)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_channel_transforms_2d(
|
||||||
|
&self,
|
||||||
|
main_channel: usize,
|
||||||
|
) -> Result<Vec<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 {
|
||||||
|
if channel == main_channel {
|
||||||
|
transforms.push(Transform::default());
|
||||||
|
} 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())?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(transforms)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_channel_transforms_3d(
|
||||||
|
&self,
|
||||||
|
main_channel: usize,
|
||||||
|
) -> Result<Vec<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 {
|
||||||
|
if channel == main_channel {
|
||||||
|
transforms.push(Transform::default());
|
||||||
|
} else {
|
||||||
|
let max = self
|
||||||
|
.slice_cztyx(s![channel, .., 0, .., ..])?
|
||||||
|
.as_array::<f64>()?;
|
||||||
|
transforms.push(Transform::register_affine(main_max.view(), max.view())?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(transforms)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_drift_transform_2d(&self) -> Result<Vec<Transform<Ix2>>, Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_drift_transform_3d(&self) -> Result<Vec<Transform<Ix3>>, Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -23,7 +178,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_transforms() -> Result<(), Box<dyn std::error::Error>> {
|
fn test_transforms() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let t = Transforms::default();
|
let _t = Transforms::default();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -91,7 +91,7 @@ pub struct View<D: Dimension, R: Reader = DynReader> {
|
|||||||
operations: IndexMap<Axis, Operation>,
|
operations: IndexMap<Axis, Operation>,
|
||||||
dimensionality: PhantomData<D>,
|
dimensionality: PhantomData<D>,
|
||||||
#[cfg(feature = "transforms")]
|
#[cfg(feature = "transforms")]
|
||||||
transforms: Transforms,
|
pub(crate) transforms: Transforms,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D, R> Hash for View<D, R>
|
impl<D, R> Hash for View<D, R>
|
||||||
@@ -123,7 +123,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[expect(dead_code)]
|
||||||
pub(crate) fn new_with_axes(reader: R, axes: Vec<Axis>) -> Result<Self, Error> {
|
pub(crate) fn new_with_axes(reader: R, axes: Vec<Axis>) -> Result<Self, Error> {
|
||||||
let mut slice = Vec::new();
|
let mut slice = Vec::new();
|
||||||
let shape = reader.shape();
|
let shape = reader.shape();
|
||||||
@@ -251,7 +251,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
operations: self.operations,
|
operations: self.operations,
|
||||||
dimensionality: PhantomData,
|
dimensionality: PhantomData,
|
||||||
#[cfg(feature = "transforms")]
|
#[cfg(feature = "transforms")]
|
||||||
transforms: Transforms::default(),
|
transforms: self.transforms,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(Error::DimensionalityMismatch(d, self.ndim()))
|
Err(Error::DimensionalityMismatch(d, self.ndim()))
|
||||||
@@ -264,7 +264,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
operations: self.operations,
|
operations: self.operations,
|
||||||
dimensionality: PhantomData,
|
dimensionality: PhantomData,
|
||||||
#[cfg(feature = "transforms")]
|
#[cfg(feature = "transforms")]
|
||||||
transforms: Transforms::default(),
|
transforms: self.transforms,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -274,7 +274,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
&self.axes
|
&self.axes
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[expect(dead_code)]
|
||||||
pub(crate) fn get_operations(&self) -> &IndexMap<Axis, Operation> {
|
pub(crate) fn get_operations(&self) -> &IndexMap<Axis, Operation> {
|
||||||
&self.operations
|
&self.operations
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user