- more transforms
This commit is contained in:
+5
-6
@@ -21,7 +21,6 @@ crate-type = ["cdylib", "rlib"]
|
||||
[dependencies]
|
||||
bioformats = { version = "0.1", optional = true }
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
color-eyre = { version = "0.6", optional = true }
|
||||
console = { version = "0.16", optional = true }
|
||||
downloader = { version = "0.2", optional = true, default-features = false, features = ["rustls-tls"] }
|
||||
ffmpeg-sidecar = { version = "2", optional = true }
|
||||
@@ -38,12 +37,11 @@ ome-metadata = "0.5"
|
||||
ordered-float = { version = "5", optional = true }
|
||||
phf = { version = "0.14", features = ["macros"] }
|
||||
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 }
|
||||
rayon = { version = "1", optional = true }
|
||||
regex = "1"
|
||||
serde = { version = "1", features = ["rc", "derive"] }
|
||||
serde_yaml = { version = "0.9", optional = true }
|
||||
serde_with = "3"
|
||||
strum = { version = "0.28", features = ["derive"] }
|
||||
thiserror = "2"
|
||||
@@ -52,6 +50,7 @@ tiffwrite = { version = "2026.6.0", optional = true }
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread"], optional = true }
|
||||
thread_local = { version = "1", optional = true }
|
||||
xmltree = { version = "0.12", optional = true }
|
||||
yaml_serde = { version = "0.10", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rayon = "1"
|
||||
@@ -67,15 +66,15 @@ toml = "1"
|
||||
default = ["bioformats_java", "gpl-formats", "czi", "tiff", "tiffseq", "movie", "tiffwrite"]
|
||||
all = ["bioformats_java", "bioformats_rust", "czi", "gpl-formats", "movie", "tiffseq", "tiffwrite", "tiff", "transforms"]
|
||||
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"]
|
||||
bioformats_rust = ["dep:bioformats", "dep:thread_local"]
|
||||
bioformats_java = ["dep:j4rs", "dep:thread_local", "dep:downloader"]
|
||||
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"]
|
||||
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]
|
||||
no-default-features = true
|
||||
|
||||
@@ -13,9 +13,8 @@ from numpy.typing import ArrayLike
|
||||
os.environ["RUST_BACKTRACE"] = "full"
|
||||
os.environ["COLORBT_SHOW_HIDDEN"] = "1"
|
||||
|
||||
from . import ndbioimage_rs as rs # noqa
|
||||
from .ndbioimage_rs import Imread
|
||||
from .transforms import Transform, Transforms # noqa: F401
|
||||
from . import ndbioimage_rs as rs
|
||||
from .ndbioimage_rs import Imread, Transform, Transforms
|
||||
|
||||
try:
|
||||
from .ndbioimage_rs import batch_to_tiff
|
||||
@@ -63,11 +62,9 @@ def ndbioimage_generate_stub():
|
||||
rs.generate_stub(str(path)) # noqa
|
||||
else:
|
||||
raise ModuleNotFoundError(str(path / "py" / "ndbioimage" / "__init__.py"))
|
||||
(path / "py" / "ndbioimage" / "__init__.pyi").unlink(missing_ok=True)
|
||||
(path / "py" / "ndbioimage" / "ndbioimage_rs" / "__init__.pyi").rename(
|
||||
(path / "py" / "ndbioimage" / "__init__.pyi").rename(
|
||||
path / "py" / "ndbioimage" / "ndbioimage_rs.pyi"
|
||||
)
|
||||
(path / "py" / "ndbioimage" / "ndbioimage_rs").rmdir()
|
||||
|
||||
|
||||
R = TypeVar("R")
|
||||
|
||||
@@ -12,6 +12,8 @@ import numpy.typing
|
||||
__all__ = [
|
||||
"Imread",
|
||||
"Shape",
|
||||
"Transform",
|
||||
"Transforms",
|
||||
"batch_to_tiff",
|
||||
"main",
|
||||
]
|
||||
@@ -52,9 +54,9 @@ class Imread:
|
||||
the name of the reader used to open the file
|
||||
"""
|
||||
@property
|
||||
def transform(self) -> None:
|
||||
def transform(self) -> Transforms:
|
||||
r"""
|
||||
get the transformation matrix (not yet implemented)
|
||||
get the transformation
|
||||
"""
|
||||
@property
|
||||
def path(self) -> pathlib.Path:
|
||||
@@ -203,10 +205,27 @@ class Imread:
|
||||
drift: builtins.bool = False,
|
||||
file: typing.Optional[typing.Any] = None,
|
||||
bead_files: typing.Optional[typing.Any] = None,
|
||||
main_channel: typing.Optional[builtins.int] = None,
|
||||
default_transform: typing.Optional[typing.Sequence[builtins.float]] = None,
|
||||
) -> Imread:
|
||||
r"""
|
||||
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 close(self) -> None:
|
||||
r"""
|
||||
@@ -672,6 +691,127 @@ class Shape:
|
||||
convert shape to a list of dimension sizes in order
|
||||
"""
|
||||
|
||||
class Transform:
|
||||
@property
|
||||
def parameters(self) -> builtins.list[builtins.float]: ...
|
||||
@parameters.setter
|
||||
def parameters(self, value: typing.Sequence[builtins.float]) -> None: ...
|
||||
@property
|
||||
def dparameters(self) -> builtins.list[builtins.float]: ...
|
||||
@dparameters.setter
|
||||
def dparameters(self, value: typing.Sequence[builtins.float]) -> None: ...
|
||||
@property
|
||||
def center(self) -> builtins.list[builtins.float]: ...
|
||||
@center.setter
|
||||
def center(self, value: typing.Sequence[builtins.float]) -> None: ...
|
||||
@property
|
||||
def shape(self) -> builtins.list[builtins.int]: ...
|
||||
@shape.setter
|
||||
def shape(self, value: typing.Sequence[builtins.int]) -> None: ...
|
||||
@property
|
||||
def ndim(self) -> builtins.int: ...
|
||||
@property
|
||||
def matrix(self) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
@matrix.setter
|
||||
def matrix(self, value: numpy.typing.ArrayLike) -> None: ...
|
||||
@property
|
||||
def dmatrix(self) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
@dmatrix.setter
|
||||
def dmatrix(self, value: numpy.typing.ArrayLike) -> None: ...
|
||||
@property
|
||||
def inverse(self) -> Transform: ...
|
||||
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
|
||||
def __new__(
|
||||
cls,
|
||||
parameters: typing.Sequence[builtins.float],
|
||||
shape: typing.Sequence[builtins.int],
|
||||
center: typing.Optional[typing.Sequence[builtins.float]] = None,
|
||||
) -> Transform: ...
|
||||
def __getnewargs__(
|
||||
self,
|
||||
) -> tuple[
|
||||
builtins.list[builtins.float],
|
||||
builtins.list[builtins.int],
|
||||
typing.Optional[builtins.list[builtins.float]],
|
||||
]: ...
|
||||
def __getstate__(self) -> builtins.list[builtins.float]: ...
|
||||
def __setstate__(self, state: typing.Sequence[builtins.float]) -> None: ...
|
||||
def __add__(self, other: Transform) -> Transform: ...
|
||||
def __radd__(self, other: Transform) -> Transform: ...
|
||||
def __sub__(self, other: Transform) -> Transform: ...
|
||||
def __rsub__(self, other: Transform) -> Transform: ...
|
||||
def __mul__(self, other: Transform | float) -> Transform: ...
|
||||
def __rmul__(self, other: Transform | float) -> Transform: ...
|
||||
def __truediv__(self, other: builtins.float) -> Transform: ...
|
||||
def adapt(
|
||||
self,
|
||||
center: typing.Sequence[builtins.float],
|
||||
shape: typing.Sequence[builtins.int],
|
||||
) -> None: ...
|
||||
@staticmethod
|
||||
def from_scaling(scaling: typing.Sequence[builtins.float]) -> Transform: ...
|
||||
@staticmethod
|
||||
def from_translation(translation: typing.Sequence[builtins.float]) -> Transform: ...
|
||||
@staticmethod
|
||||
def from_rotation(
|
||||
theta: builtins.float, center: typing.Sequence[builtins.float]
|
||||
) -> Transform: ...
|
||||
def with_scaling(self, scaling: typing.Sequence[builtins.float]) -> Transform: ...
|
||||
def with_translation(
|
||||
self, translation: typing.Sequence[builtins.float]
|
||||
) -> Transform: ...
|
||||
def with_rotation(
|
||||
self, theta: builtins.float, center: typing.Sequence[builtins.float]
|
||||
) -> Transform: ...
|
||||
def interpolate(
|
||||
self, order: builtins.int, image: numpy.typing.ArrayLike
|
||||
) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
def interpolate_par(
|
||||
self, order: builtins.int, image: numpy.typing.ArrayLike
|
||||
) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
def is_unity(self) -> builtins.bool: ...
|
||||
def transform_point(
|
||||
self, point: numpy.typing.ArrayLike
|
||||
) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
def transform_points(
|
||||
self, points: numpy.typing.ArrayLike
|
||||
) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
@staticmethod
|
||||
def register(
|
||||
fixed: numpy.typing.ArrayLike,
|
||||
moving: numpy.typing.ArrayLike,
|
||||
fixed_mu: typing.Sequence[typing.Optional[builtins.float]],
|
||||
initial_guess: typing.Optional[typing.Sequence[builtins.float]] = None,
|
||||
) -> Transform: ...
|
||||
@staticmethod
|
||||
def register_affine(
|
||||
fixed: numpy.typing.ArrayLike, moving: numpy.typing.ArrayLike
|
||||
) -> Transform: ...
|
||||
@staticmethod
|
||||
def register_translation(
|
||||
fixed: numpy.typing.ArrayLike, moving: numpy.typing.ArrayLike
|
||||
) -> Transform: ...
|
||||
|
||||
class Transforms:
|
||||
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
|
||||
def __getstate__(self) -> builtins.list[builtins.int]: ...
|
||||
def __setstate__(self, state: typing.Sequence[builtins.int]) -> None: ...
|
||||
@staticmethod
|
||||
def load(path: builtins.str | os.PathLike | pathlib.Path) -> Transforms: ...
|
||||
def save(self, path: builtins.str | os.PathLike | pathlib.Path) -> None: ...
|
||||
@staticmethod
|
||||
def calculate_channel_transforms_2d(
|
||||
bead_files: typing.Sequence[builtins.str | os.PathLike | pathlib.Path],
|
||||
main_channel: builtins.int,
|
||||
default_transform: typing.Optional[Transform],
|
||||
) -> builtins.list[Transform]: ...
|
||||
@staticmethod
|
||||
def calculate_channel_transforms_3d(
|
||||
bead_files: typing.Sequence[builtins.str | os.PathLike | pathlib.Path],
|
||||
main_channel: builtins.int,
|
||||
default_transform: typing.Optional[Transform],
|
||||
) -> builtins.list[Transform]: ...
|
||||
|
||||
def batch_to_tiff(
|
||||
files_in: typing.Sequence[builtins.str | os.PathLike | pathlib.Path],
|
||||
files_out: typing.Sequence[builtins.str | os.PathLike | pathlib.Path],
|
||||
|
||||
@@ -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]
|
||||
name = "ndbioimage"
|
||||
version = "2027.0.3"
|
||||
version = "2027.0.4"
|
||||
requires-python = ">=3.10"
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: MIT License",
|
||||
@@ -35,7 +35,7 @@ ndbioimage_generate_stub = "ndbioimage:ndbioimage_generate_stub"
|
||||
|
||||
[tool.maturin]
|
||||
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
|
||||
module-name = "ndbioimage.ndbioimage_rs"
|
||||
include = ["py/ndbioimage/jassets/j4rs*", "py/ndbioimage/deps/libj4rs*"]
|
||||
|
||||
+24
-14
@@ -10,8 +10,8 @@ pub enum Error {
|
||||
/// an ndarray shape error
|
||||
#[error(transparent)]
|
||||
Shape(#[from] ndarray::ShapeError),
|
||||
#[cfg(feature = "bioformats_java")]
|
||||
/// an error from the j4rs java bridge
|
||||
#[cfg(feature = "bioformats_java")]
|
||||
#[error(transparent)]
|
||||
J4rs(#[from] j4rs::errors::J4RsError),
|
||||
/// an infallible conversion
|
||||
@@ -23,60 +23,64 @@ pub enum Error {
|
||||
/// an ome metadata error
|
||||
#[error(transparent)]
|
||||
Ome(#[from] ome_metadata::error::Error),
|
||||
#[cfg(feature = "bioformats_java")]
|
||||
/// an error while downloading (e.g. the bioformats jar)
|
||||
#[cfg(feature = "bioformats_java")]
|
||||
#[error(transparent)]
|
||||
Downloader(#[from] downloader::Error),
|
||||
/// an error parsing an enum string with strum
|
||||
#[error(transparent)]
|
||||
Strum(#[from] strum::ParseError),
|
||||
#[cfg(feature = "tiffwrite")]
|
||||
/// an indicatif progress bar template error
|
||||
#[cfg(feature = "tiffwrite")]
|
||||
#[error(transparent)]
|
||||
TemplateError(#[from] indicatif::style::TemplateError),
|
||||
#[cfg(feature = "tiffwrite")]
|
||||
/// an error from the tiffwrite crate
|
||||
#[cfg(feature = "tiffwrite")]
|
||||
#[error(transparent)]
|
||||
TiffWrite(#[from] tiffwrite::error::Error),
|
||||
#[cfg(feature = "tiffseq")]
|
||||
/// a yaml (de)serialization error
|
||||
#[cfg(feature = "tiffseq")]
|
||||
#[error(transparent)]
|
||||
SerdeYaml(#[from] serde_yaml::Error),
|
||||
#[cfg(any(feature = "tiffseq", feature = "tiff"))]
|
||||
SerdeYaml(#[from] yaml_serde::Error),
|
||||
/// an error from the tiff crate
|
||||
#[cfg(any(feature = "tiffseq", feature = "tiff"))]
|
||||
#[error(transparent)]
|
||||
Tiff(#[from] tiff::TiffError),
|
||||
#[cfg(feature = "python")]
|
||||
/// a postcard (de)serialization error
|
||||
#[cfg(feature = "python")]
|
||||
#[error(transparent)]
|
||||
PostCard(#[from] postcard::Error),
|
||||
#[cfg(feature = "czi")]
|
||||
/// an error from the libczi binding
|
||||
#[cfg(feature = "czi")]
|
||||
#[error(transparent)]
|
||||
LibCzi(#[from] libczirw_sys::error::Error),
|
||||
/// a regex error
|
||||
#[error(transparent)]
|
||||
RegexError(#[from] regex::Error),
|
||||
#[cfg(feature = "czi")]
|
||||
/// an xmltree error
|
||||
#[cfg(feature = "czi")]
|
||||
#[error(transparent)]
|
||||
XmlTree(#[from] xmltree::Error),
|
||||
#[cfg(feature = "czi")]
|
||||
/// an xmltree parse error
|
||||
#[cfg(feature = "czi")]
|
||||
#[error(transparent)]
|
||||
XmlTreeParse(#[from] xmltree::ParseError),
|
||||
#[cfg(feature = "czi")]
|
||||
/// a czi-specific error
|
||||
#[cfg(feature = "czi")]
|
||||
#[error(transparent)]
|
||||
Czi(#[from] crate::readers::czi::CziError),
|
||||
#[cfg(feature = "movie")]
|
||||
/// an error joining a tokio task
|
||||
#[cfg(feature = "movie")]
|
||||
#[error(transparent)]
|
||||
TokioJoin(#[from] tokio::task::JoinError),
|
||||
#[cfg(feature = "bioformats_rust")]
|
||||
/// an error from the bioformats rust crate
|
||||
#[cfg(feature = "bioformats_rust")]
|
||||
#[error(transparent)]
|
||||
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
|
||||
#[error("invalid axis: {0}")]
|
||||
@@ -162,6 +166,12 @@ pub enum Error {
|
||||
/// cannot remove axes that have a size != 1
|
||||
#[error("cannot remove axes {0}, size {1} != 1")]
|
||||
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 {
|
||||
|
||||
+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
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum PixelType {
|
||||
/// true / false
|
||||
Bool,
|
||||
/// signed 8-bit integer
|
||||
I8,
|
||||
/// unsigned 8-bit integer
|
||||
@@ -135,7 +136,7 @@ impl PixelType {
|
||||
/// number of bytes per pixel for this type
|
||||
pub fn bytes_per_pixel(&self) -> usize {
|
||||
match self {
|
||||
PixelType::I8 | PixelType::U8 => 1,
|
||||
PixelType::Bool | PixelType::I8 | PixelType::U8 => 1,
|
||||
PixelType::I16 | PixelType::U16 => 2,
|
||||
PixelType::I32 | PixelType::U32 | PixelType::F32 => 4,
|
||||
PixelType::I64 | PixelType::U64 | PixelType::F64 => 8,
|
||||
@@ -145,7 +146,6 @@ impl PixelType {
|
||||
}
|
||||
|
||||
/// array data with a specific pixel type
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ArrayT<D: Dimension> {
|
||||
/// signed 8-bit integer array
|
||||
@@ -207,7 +207,6 @@ pub trait Reader: Clone + Sized + Debug + Send + Hash + Into<DynReader> {
|
||||
}
|
||||
|
||||
/// retrieve frame at channel c, slice z and time t
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
fn get_frame(&self, c: usize, z: usize, t: usize) -> Result<Frame, Error>;
|
||||
|
||||
/// the path to the image file
|
||||
@@ -486,7 +485,7 @@ impl Reader for DynReader {
|
||||
DynReader::BioFormatsRust(r) => r.reader_name(),
|
||||
#[cfg(feature = "bioformats_java")]
|
||||
DynReader::BioFormatsJava(r) => r.reader_name(),
|
||||
#[allow(unreachable_patterns)]
|
||||
#[expect(unreachable_patterns)]
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -503,7 +502,7 @@ impl Reader for DynReader {
|
||||
DynReader::BioFormatsRust(r) => r.metadata()?,
|
||||
#[cfg(feature = "bioformats_java")]
|
||||
DynReader::BioFormatsJava(r) => r.metadata()?,
|
||||
#[allow(unreachable_patterns)]
|
||||
#[expect(unreachable_patterns)]
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
@@ -520,7 +519,7 @@ impl Reader for DynReader {
|
||||
DynReader::BioFormatsRust(r) => r.get_frame(c, z, t),
|
||||
#[cfg(feature = "bioformats_java")]
|
||||
DynReader::BioFormatsJava(r) => r.get_frame(c, z, t),
|
||||
#[allow(unreachable_patterns)]
|
||||
#[expect(unreachable_patterns)]
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -537,7 +536,7 @@ impl Reader for DynReader {
|
||||
DynReader::BioFormatsRust(r) => r.path(),
|
||||
#[cfg(feature = "bioformats_java")]
|
||||
DynReader::BioFormatsJava(r) => r.path(),
|
||||
#[allow(unreachable_patterns)]
|
||||
#[expect(unreachable_patterns)]
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -554,7 +553,7 @@ impl Reader for DynReader {
|
||||
DynReader::BioFormatsRust(r) => r.series(),
|
||||
#[cfg(feature = "bioformats_java")]
|
||||
DynReader::BioFormatsJava(r) => r.series(),
|
||||
#[allow(unreachable_patterns)]
|
||||
#[expect(unreachable_patterns)]
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -571,7 +570,7 @@ impl Reader for DynReader {
|
||||
DynReader::BioFormatsRust(r) => r.position(),
|
||||
#[cfg(feature = "bioformats_java")]
|
||||
DynReader::BioFormatsJava(r) => r.position(),
|
||||
#[allow(unreachable_patterns)]
|
||||
#[expect(unreachable_patterns)]
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -588,7 +587,7 @@ impl Reader for DynReader {
|
||||
DynReader::BioFormatsRust(r) => r.shape(),
|
||||
#[cfg(feature = "bioformats_java")]
|
||||
DynReader::BioFormatsJava(r) => r.shape(),
|
||||
#[allow(unreachable_patterns)]
|
||||
#[expect(unreachable_patterns)]
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -605,7 +604,7 @@ impl Reader for DynReader {
|
||||
DynReader::BioFormatsRust(r) => r.pixel_type(),
|
||||
#[cfg(feature = "bioformats_java")]
|
||||
DynReader::BioFormatsJava(r) => r.pixel_type(),
|
||||
#[allow(unreachable_patterns)]
|
||||
#[expect(unreachable_patterns)]
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,6 +389,7 @@ impl BioFormatsJavaReader {
|
||||
|
||||
fn deinterleave(&self, bytes: Vec<u8>, channel: usize) -> Result<Vec<u8>, Error> {
|
||||
let chunk_size = match self.pixel_type {
|
||||
PixelType::Bool => 1,
|
||||
PixelType::I8 => 1,
|
||||
PixelType::U8 => 1,
|
||||
PixelType::I16 => 2,
|
||||
@@ -413,6 +414,26 @@ impl BioFormatsJavaReader {
|
||||
|
||||
fn bytes_to_frame(&self, bytes: Vec<u8>) -> Result<Frame, Error> {
|
||||
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) => {
|
||||
Ok(ArrayT::from(Array2::from_shape_vec(
|
||||
(self.shape.y, self.shape.x),
|
||||
@@ -434,6 +455,7 @@ impl BioFormatsJavaReader {
|
||||
}
|
||||
|
||||
match (&self.pixel_type, self.little_endian) {
|
||||
(PixelType::Bool, true) => get_frame!(bool, <1),
|
||||
(PixelType::I8, true) => get_frame!(i8, <1),
|
||||
(PixelType::U8, true) => get_frame!(u8, <1),
|
||||
(PixelType::I16, true) => get_frame!(i16, <2),
|
||||
@@ -447,6 +469,7 @@ impl BioFormatsJavaReader {
|
||||
(PixelType::I128, true) => get_frame!(i128, <16),
|
||||
(PixelType::U128, true) => get_frame!(u128, <16),
|
||||
(PixelType::F128, true) => get_frame!(f64, <8),
|
||||
(PixelType::Bool, false) => get_frame!(bool, >1),
|
||||
(PixelType::I8, false) => get_frame!(i8, >1),
|
||||
(PixelType::U8, false) => get_frame!(u8, >1),
|
||||
(PixelType::I16, false) => get_frame!(i16, >2),
|
||||
@@ -501,7 +524,7 @@ impl Reader for BioFormatsJavaReader {
|
||||
Error::FileDoesNotExist(orig.join("**").join("*.tif").display().to_string())
|
||||
})?;
|
||||
}
|
||||
let mut new = BioFormatsJavaReader {
|
||||
let mut new = Self {
|
||||
reader: ThreadLocal::default(),
|
||||
path,
|
||||
series,
|
||||
|
||||
@@ -78,19 +78,20 @@ impl Deref for BioFormatsRustReader {
|
||||
}
|
||||
}
|
||||
|
||||
fn map_pixel_type(bf: bioformats::PixelType) -> Result<PixelType, Error> {
|
||||
use bioformats::PixelType as Bf;
|
||||
Ok(match bf {
|
||||
Bf::Int8 => PixelType::I8,
|
||||
Bf::Uint8 => PixelType::U8,
|
||||
Bf::Int16 => PixelType::I16,
|
||||
Bf::Uint16 => PixelType::U16,
|
||||
Bf::Int32 => PixelType::I32,
|
||||
Bf::Uint32 => PixelType::U32,
|
||||
Bf::Float32 => PixelType::F32,
|
||||
Bf::Float64 => PixelType::F64,
|
||||
Bf::Bit => PixelType::U8,
|
||||
})
|
||||
impl From<bioformats::PixelType> for PixelType {
|
||||
fn from(bf: bioformats::PixelType) -> Self {
|
||||
match bf {
|
||||
bioformats::PixelType::Bit => PixelType::Bool,
|
||||
bioformats::PixelType::Int8 => PixelType::I8,
|
||||
bioformats::PixelType::Uint8 => PixelType::U8,
|
||||
bioformats::PixelType::Int16 => PixelType::I16,
|
||||
bioformats::PixelType::Uint16 => PixelType::U16,
|
||||
bioformats::PixelType::Int32 => PixelType::I32,
|
||||
bioformats::PixelType::Uint32 => PixelType::U32,
|
||||
bioformats::PixelType::Float32 => PixelType::F32,
|
||||
bioformats::PixelType::Float64 => PixelType::F64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BioFormatsRustReader {
|
||||
@@ -126,6 +127,7 @@ impl BioFormatsRustReader {
|
||||
|
||||
fn deinterleave(&self, bytes: Vec<u8>, channel: usize) -> Result<Vec<u8>, Error> {
|
||||
let chunk_size = match self.pixel_type {
|
||||
PixelType::Bool => 1,
|
||||
PixelType::I8 => 1,
|
||||
PixelType::U8 => 1,
|
||||
PixelType::I16 => 2,
|
||||
@@ -150,6 +152,26 @@ impl BioFormatsRustReader {
|
||||
|
||||
fn bytes_to_frame(&self, bytes: Vec<u8>) -> Result<Frame, Error> {
|
||||
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) => {
|
||||
Ok(ArrayT::from(Array2::from_shape_vec(
|
||||
(self.shape.y, self.shape.x),
|
||||
@@ -171,6 +193,7 @@ impl BioFormatsRustReader {
|
||||
}
|
||||
|
||||
match (&self.pixel_type, self.little_endian) {
|
||||
(PixelType::Bool, true) => get_frame!(bool, <1),
|
||||
(PixelType::I8, true) => get_frame!(i8, <1),
|
||||
(PixelType::U8, true) => get_frame!(u8, <1),
|
||||
(PixelType::I16, true) => get_frame!(i16, <2),
|
||||
@@ -184,6 +207,7 @@ impl BioFormatsRustReader {
|
||||
(PixelType::I128, true) => get_frame!(i128, <16),
|
||||
(PixelType::U128, true) => get_frame!(u128, <16),
|
||||
(PixelType::F128, true) => get_frame!(f64, <8),
|
||||
(PixelType::Bool, false) => get_frame!(bool, >1),
|
||||
(PixelType::I8, false) => get_frame!(i8, >1),
|
||||
(PixelType::U8, false) => get_frame!(u8, >1),
|
||||
(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.x = metadata.size_x as usize;
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -304,6 +328,13 @@ impl Reader for BioFormatsRustReader {
|
||||
where
|
||||
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())
|
||||
.map_err(|e| Error::Parse(format!("bioformats failed to open: {}", e)))?;
|
||||
let n = reader.series_count();
|
||||
|
||||
@@ -1053,6 +1053,44 @@ impl Reader for CziReader {
|
||||
}
|
||||
|
||||
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) => {{
|
||||
let mut array = Array2::zeros((self.shape.y, self.shape.x));
|
||||
if let Some(indices) = self.block_map.get(&(c, z, t)) {
|
||||
@@ -1082,6 +1120,7 @@ impl Reader for CziReader {
|
||||
}
|
||||
|
||||
match self.pixel_type {
|
||||
PixelType::Bool => get_frame!(bool, 1),
|
||||
PixelType::I8 => get_frame!(i8, 1),
|
||||
PixelType::U8 => get_frame!(u8, 1),
|
||||
PixelType::I16 => get_frame!(i16, 2),
|
||||
|
||||
@@ -22,7 +22,7 @@ pub struct TiffSeqReader {
|
||||
filedict: HashMap<(usize, usize, usize), PathBuf>,
|
||||
cnamelist: Vec<String>,
|
||||
#[serde(skip)]
|
||||
metadata_map: HashMap<String, serde_yaml::Value>,
|
||||
metadata_map: HashMap<String, yaml_serde::Value>,
|
||||
}
|
||||
|
||||
impl From<TiffSeqReader> for DynReader {
|
||||
@@ -73,10 +73,10 @@ impl TiffSeqReader {
|
||||
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 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();
|
||||
map.insert("Info".to_string(), parsed);
|
||||
Ok(map)
|
||||
@@ -129,11 +129,11 @@ impl Reader for TiffSeqReader {
|
||||
.ok_or_else(|| Error::Parse("missing Info key in tag 50839".to_string()))?;
|
||||
|
||||
let lookup = |key: &str| {
|
||||
info.get(serde_yaml::Value::String(key.to_string()))
|
||||
info.get(yaml_serde::Value::String(key.to_string()))
|
||||
.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.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 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_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| {
|
||||
m.iter()
|
||||
@@ -254,7 +254,7 @@ impl Reader for TiffSeqReader {
|
||||
});
|
||||
|
||||
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 {
|
||||
PixelType::I8 => ome::PixelType::Int8,
|
||||
|
||||
@@ -165,6 +165,7 @@ where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
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::U8 => self.save_as_tiff_with_type::<u8, 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;
|
||||
pub use image_registration::transform::Transform;
|
||||
use ndarray::{Dimension, Ix2, Ix3};
|
||||
use ndarray::{Dimension, Ix2, Ix3, s};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
|
||||
#[expect(clippy::upper_case_acronyms)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum TransformD {
|
||||
YX(Transform<Ix2>),
|
||||
ZYX(Transform<Ix3>),
|
||||
}
|
||||
|
||||
impl<D: Dimension, R: Reader> View<D, R> {}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Transforms {
|
||||
channel: Vec<TransformD>,
|
||||
drift: Vec<TransformD>,
|
||||
pub channel: 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)]
|
||||
@@ -23,7 +178,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_transforms() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let t = Transforms::default();
|
||||
let _t = Transforms::default();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+5
-5
@@ -91,7 +91,7 @@ pub struct View<D: Dimension, R: Reader = DynReader> {
|
||||
operations: IndexMap<Axis, Operation>,
|
||||
dimensionality: PhantomData<D>,
|
||||
#[cfg(feature = "transforms")]
|
||||
transforms: Transforms,
|
||||
pub(crate) transforms: Transforms,
|
||||
}
|
||||
|
||||
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> {
|
||||
let mut slice = Vec::new();
|
||||
let shape = reader.shape();
|
||||
@@ -251,7 +251,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
operations: self.operations,
|
||||
dimensionality: PhantomData,
|
||||
#[cfg(feature = "transforms")]
|
||||
transforms: Transforms::default(),
|
||||
transforms: self.transforms,
|
||||
})
|
||||
} else {
|
||||
Err(Error::DimensionalityMismatch(d, self.ndim()))
|
||||
@@ -264,7 +264,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
operations: self.operations,
|
||||
dimensionality: PhantomData,
|
||||
#[cfg(feature = "transforms")]
|
||||
transforms: Transforms::default(),
|
||||
transforms: self.transforms,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -274,7 +274,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
&self.axes
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[expect(dead_code)]
|
||||
pub(crate) fn get_operations(&self) -> &IndexMap<Axis, Operation> {
|
||||
&self.operations
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user