From 4074770fd315a96153f99c4e066f8c6d8fe35002 Mon Sep 17 00:00:00 2001 From: "w.pomp" Date: Fri, 4 Sep 2026 15:51:53 +0200 Subject: [PATCH] - inital transforms code --- Cargo.toml | 9 ++++++--- README.md | 12 ++++++------ build.rs | 6 +++--- src/lib.rs | 5 ++++- src/readers/bioformats_rust.rs | 30 +++++++++++++++++++++++++++--- src/transforms.rs | 30 ++++++++++++++++++++++++++++++ src/view.rs | 14 ++++++++++++++ 7 files changed, 90 insertions(+), 16 deletions(-) create mode 100644 src/transforms.rs diff --git a/Cargo.toml b/Cargo.toml index e2962df..fd8d7d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,12 +19,14 @@ name = "ndbioimage" 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 } itertools = "0.15" +image-registration = { path = "../image-registration", optional = true } indexmap = { version = "2", features = ["serde"] } indicatif = { version = "0.18", features = ["rayon"], optional = true } j4rs = { version = "0.25", optional = true } @@ -50,7 +52,6 @@ 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 } -bioformats = { version = "0.1", optional = true } [dev-dependencies] rayon = "1" @@ -64,7 +65,7 @@ toml = "1" [features] default = ["bioformats_java", "gpl-formats", "czi", "tiff", "tiffseq", "movie", "tiffwrite"] -all = ["bioformats_java", "bioformats_rust", "czi", "gpl-formats", "movie", "tiffseq", "tiffwrite", "tiff"] +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"] czi = ["dep:libczirw-sys", "dep:xmltree", "dep:thread_local"] @@ -74,9 +75,11 @@ tiffwrite = ["dep:tiffwrite", "dep:indicatif", "dep:console", "dep:rayon"] tiffseq = ["dep:tiff", "dep:serde_yaml"] tiff = ["dep:tiff", "dep:thread_local"] movie = ["dep:ffmpeg-sidecar", "dep:tokio", "dep:ordered-float", "dep:indicatif", "dep:console"] +transforms = ["dep:image-registration"] [package.metadata.docs.rs] -features = ["bioformats_java", "czi", "tiff", "tiffseq", "movie", "tiffwrite", "movie"] +no-default-features = true +features = ["bioformats_java", "bioformats_rust", "czi", "movie", "tiffseq", "tiffwrite", "tiff", "transforms"] [profile.test] inherits = "release" diff --git a/README.md b/README.md index ea56c1f..74411ce 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ To transition to semver, versions before 0.1.0 were yanked from crates.io. ## Installation -``` +```sh pip install ndbioimage ``` @@ -28,7 +28,7 @@ pip install ndbioimage Work in progress! Make sure ffmpeg is installed. -``` +```sh pip install ndbioimage[write] ``` @@ -38,7 +38,7 @@ pip install ndbioimage[write] - Reading an image file and plotting the frame at channel=2, time=1 -``` +```python import matplotlib.pyplot as plt from ndbioimage import Imread with Imread('image_file.tif', axes='ctyx', dtype=int) as im: @@ -47,7 +47,7 @@ with Imread('image_file.tif', axes='ctyx', dtype=int) as im: - Showing some image metadata -``` +```python from ndbioimage import Imread from pprint import pprint with Imread('image_file.tif') as im: @@ -56,7 +56,7 @@ with Imread('image_file.tif') as im: - Slicing the image without loading the image into memory -``` +```python from ndbioimage import Imread with Imread('image_file.tif', axes='cztyx') as im: sliced_im = im[1, :, :, 100:200, 100:200] @@ -66,7 +66,7 @@ sliced_im is an instance of Imread which will load any image data from file only - Converting (part) of the image to a numpy ndarray -``` +```python from ndbioimage import Imread import numpy as np with Imread('image_file.tif', axes='cztyx') as im: diff --git a/build.rs b/build.rs index d4e53ef..d5fb558 100644 --- a/build.rs +++ b/build.rs @@ -159,13 +159,13 @@ mod python_bioformats { fn main() -> Result<(), Box> { println!("cargo::rerun-if-changed=build.rs"); + #[cfg(feature = "bioformats_java")] + bioformats::build()?; + if std::env::var("DOCS_RS").is_err() { #[cfg(feature = "movie")] ffmpeg_sidecar::download::auto_download()?; - #[cfg(feature = "bioformats_java")] - bioformats::build()?; - #[cfg(all(not(feature = "python"), feature = "bioformats_java"))] no_python_bioformats::build()?; diff --git a/src/lib.rs b/src/lib.rs index fc19bd6..ab9cfcf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ #![cfg_attr(docsrs, feature(doc_cfg))] -//! The ndbioimage crate exposes (bio) images a struct that can be sliced like an ndarray Array +//! The ndbioimage crate exposes (bio) images as a struct that can be sliced like an ndarray Array //! (Rust), but without loading the whole image into memory, reading from the file only when needed. //! Some metadata is read //! and stored in an [ome](https://genomebiology.biomedcentral.com/articles/10.1186/gb-2005-6-5-r47) @@ -68,6 +68,9 @@ pub mod readers; #[cfg(feature = "tiffwrite")] /// saving views as tiff files pub mod tiffwrite; +#[cfg(feature = "transforms")] +/// image registration and warping images using affine transforms +mod transforms; mod utils; /// main entry point for the application diff --git a/src/readers/bioformats_rust.rs b/src/readers/bioformats_rust.rs index fd572ed..b28e844 100644 --- a/src/readers/bioformats_rust.rs +++ b/src/readers/bioformats_rust.rs @@ -2,6 +2,7 @@ use crate::error::Error; use crate::readers::ArrayT; use crate::readers::{DynReader, Frame, PixelType, Reader, Shape}; use bioformats::{DimensionOrder, ImageReader}; +use itertools::Itertools; use ndarray::Array2; use ome_metadata::Ome; use std::cell::{RefCell, RefMut}; @@ -200,18 +201,41 @@ impl BioFormatsRustReader { } } +fn find_tiff(path: &Path) -> Result, Error> { + if let Some(ext) = path.extension() + && path.is_file() + && (["tif", "tiff"].contains(&ext.to_string_lossy().to_lowercase().as_str())) + { + return Ok(Some(path.to_path_buf())); + } else if path.is_dir() { + for file in path.read_dir()?.flatten().sorted_by_key(|i| i.file_name()) { + if let Ok(Some(file)) = find_tiff(file.path().as_path()) { + return Ok(Some(file)); + } + } + } + Ok(None) +} + impl Reader for BioFormatsRustReader { fn new

(path: P, series: usize, _position: usize) -> Result where P: AsRef, { + 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 mut new = Self { reader: ThreadLocal::default(), - path: path.as_ref().to_path_buf(), + path, series, shape: Shape::default(), - pixel_type: PixelType::U16, - little_endian: true, + pixel_type: PixelType::I8, + little_endian: false, }; let reader = new.get_reader()?; let metadata = reader.metadata().clone(); diff --git a/src/transforms.rs b/src/transforms.rs new file mode 100644 index 0000000..465ec0f --- /dev/null +++ b/src/transforms.rs @@ -0,0 +1,30 @@ +use crate::readers::Reader; +use crate::view::View; +pub use image_registration::transform::Transform; +use ndarray::{Dimension, Ix2, Ix3}; +use serde::{Deserialize, Serialize}; + +pub enum TransformD { + YX(Transform), + ZYX(Transform), +} + +impl View {} + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct Transforms { + channel: Vec, + drift: Vec, +} + +#[cfg(test)] +mod tests { + use crate::transforms::Transforms; + + #[test] + fn test_transforms() -> Result<(), Box> { + let t = Transforms::default(); + + Ok(()) + } +} diff --git a/src/view.rs b/src/view.rs index e3e960b..1939bae 100644 --- a/src/view.rs +++ b/src/view.rs @@ -4,6 +4,8 @@ use crate::error::Error; use crate::metadata::Metadata; use crate::readers::{Dimensions, DynReader, Frame, Reader}; use crate::stats::MinMax; +#[cfg(feature = "transforms")] +use crate::transforms::Transforms; use indexmap::IndexMap; use itertools::{Itertools, iproduct}; use ndarray::{ @@ -88,6 +90,8 @@ pub struct View { axes: Vec, operations: IndexMap, dimensionality: PhantomData, + #[cfg(feature = "transforms")] + transforms: Transforms, } impl Hash for View @@ -114,6 +118,8 @@ impl View { axes, operations: IndexMap::new(), dimensionality: PhantomData, + #[cfg(feature = "transforms")] + transforms: Transforms::default(), } } @@ -177,6 +183,8 @@ impl View { axes, operations: IndexMap::new(), dimensionality: PhantomData, + #[cfg(feature = "transforms")] + transforms: Transforms::default(), }) } @@ -227,6 +235,8 @@ impl View { axes: self.axes, operations: self.operations, dimensionality: PhantomData, + #[cfg(feature = "transforms")] + transforms: Transforms::default(), } } @@ -240,6 +250,8 @@ impl View { axes: self.axes, operations: self.operations, dimensionality: PhantomData, + #[cfg(feature = "transforms")] + transforms: Transforms::default(), }) } else { Err(Error::DimensionalityMismatch(d, self.ndim())) @@ -251,6 +263,8 @@ impl View { axes: self.axes, operations: self.operations, dimensionality: PhantomData, + #[cfg(feature = "transforms")] + transforms: Transforms::default(), }) } }