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