- inital transforms code

This commit is contained in:
w.pomp
2026-09-04 15:51:53 +02:00
parent 6913e65420
commit 4074770fd3
7 changed files with 90 additions and 16 deletions
+6 -3
View File
@@ -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"
+6 -6
View File
@@ -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:
+3 -3
View File
@@ -159,13 +159,13 @@ mod python_bioformats {
fn main() -> Result<(), Box<dyn std::error::Error>> {
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()?;
+4 -1
View File
@@ -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
+27 -3
View File
@@ -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<Option<PathBuf>, 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<P>(path: P, series: usize, _position: usize) -> Result<Self, Error>
where
P: AsRef<Path>,
{
let mut path = path.as_ref().to_path_buf();
if path.is_dir() {
let orig = path.clone();
path = find_tiff(&path)?.ok_or_else(|| {
Error::FileDoesNotExist(orig.join("**").join("*.tif").display().to_string())
})?;
}
let 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();
+30
View File
@@ -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<Ix2>),
ZYX(Transform<Ix3>),
}
impl<D: Dimension, R: Reader> View<D, R> {}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct Transforms {
channel: Vec<TransformD>,
drift: Vec<TransformD>,
}
#[cfg(test)]
mod tests {
use crate::transforms::Transforms;
#[test]
fn test_transforms() -> Result<(), Box<dyn std::error::Error>> {
let t = Transforms::default();
Ok(())
}
}
+14
View File
@@ -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<D: Dimension, R: Reader = DynReader> {
axes: Vec<Axis>,
operations: IndexMap<Axis, Operation>,
dimensionality: PhantomData<D>,
#[cfg(feature = "transforms")]
transforms: Transforms,
}
impl<D, R> Hash for View<D, R>
@@ -114,6 +118,8 @@ impl<D: Dimension, R: Reader> View<D, R> {
axes,
operations: IndexMap::new(),
dimensionality: PhantomData,
#[cfg(feature = "transforms")]
transforms: Transforms::default(),
}
}
@@ -177,6 +183,8 @@ impl<D: Dimension, R: Reader> View<D, R> {
axes,
operations: IndexMap::new(),
dimensionality: PhantomData,
#[cfg(feature = "transforms")]
transforms: Transforms::default(),
})
}
@@ -227,6 +235,8 @@ impl<D: Dimension, R: Reader> View<D, R> {
axes: self.axes,
operations: self.operations,
dimensionality: PhantomData,
#[cfg(feature = "transforms")]
transforms: Transforms::default(),
}
}
@@ -240,6 +250,8 @@ impl<D: Dimension, R: Reader> View<D, R> {
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<D: Dimension, R: Reader> View<D, R> {
axes: self.axes,
operations: self.operations,
dimensionality: PhantomData,
#[cfg(feature = "transforms")]
transforms: Transforms::default(),
})
}
}