- various fixes
This commit is contained in:
+2
-2
@@ -28,7 +28,7 @@ itertools = "0.15"
|
||||
indexmap = { version = "2", features = ["serde"] }
|
||||
indicatif = { version = "0.18", features = ["rayon"], optional = true }
|
||||
j4rs = { version = "0.25", optional = true }
|
||||
libczirw-sys = { path = "../libczirw-sys", optional = true }
|
||||
libczirw-sys = { version = "0.5", optional = true }
|
||||
ndarray = { version = "0.17", features = ["serde"] }
|
||||
num = "0.4"
|
||||
numpy = { version = "0.29", optional = true }
|
||||
@@ -76,7 +76,7 @@ tiff = ["dep:tiff", "dep:thread_local"]
|
||||
movie = ["dep:ffmpeg-sidecar", "dep:tokio", "dep:ordered-float", "dep:indicatif", "dep:console"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["bioformats_java", "gpl-formats", "czi", "tiff", "movie"]
|
||||
features = ["bioformats_java", "czi", "tiff", "tiffseq", "movie", "tiffwrite", "movie"]
|
||||
|
||||
[profile.test]
|
||||
inherits = "release"
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
# ndbioimage
|
||||
|
||||
[](https://github.com/pomppervova/ndbioimage/actions/workflows/pytest.yml)
|
||||
|
||||
## Work in progress
|
||||
|
||||
Rust rewrite of python version. Read bio image formats using the bio-formats java package.
|
||||
[https://www.openmicroscopy.org/bio-formats/](https://www.openmicroscopy.org/bio-formats/)
|
||||
|
||||
Exposes (bio) images as a numpy ndarray-like object, but without loading the whole
|
||||
image into memory, reading from the file only when needed. Some metadata is read
|
||||
Exposes (bio) images as a numpy ndarray-like object (Python) or 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) structure.
|
||||
Additionally, it can automatically calculate an affine transform that corrects for chromatic aberrations etc. and apply
|
||||
it on the fly to the image.
|
||||
@@ -23,6 +25,7 @@ pip install ndbioimage
|
||||
```
|
||||
|
||||
### Installation with option to write mp4 or mkv:
|
||||
|
||||
Work in progress! Make sure ffmpeg is installed.
|
||||
|
||||
```
|
||||
@@ -30,6 +33,7 @@ pip install ndbioimage[write]
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Python
|
||||
|
||||
- Reading an image file and plotting the frame at channel=2, time=1
|
||||
@@ -60,7 +64,6 @@ with Imread('image_file.tif', axes='cztyx') as im:
|
||||
|
||||
sliced_im is an instance of Imread which will load any image data from file only when needed
|
||||
|
||||
|
||||
- Converting (part) of the image to a numpy ndarray
|
||||
|
||||
```
|
||||
@@ -71,36 +74,45 @@ with Imread('image_file.tif', axes='cztyx') as im:
|
||||
```
|
||||
|
||||
### Rust
|
||||
```
|
||||
use ndarray::Array2;
|
||||
use ndbioimage::Reader;
|
||||
|
||||
let path = "/path/to/file";
|
||||
let reader = Reader::new(&path, 0)?;
|
||||
println!("size: {}, {}", reader.size_y, reader.size_y);
|
||||
let frame = reader.get_frame(0, 0, 0).unwrap();
|
||||
if let Ok(arr) = <Frame as TryInto<Array2<i8>>>::try_into(frame) {
|
||||
println!("{:?}", arr);
|
||||
} else {
|
||||
println!("could not convert Frame to Array<i8>");
|
||||
```rust
|
||||
use ndarray::Array2;
|
||||
use ndbioimage::{DynReader, Frame, Reader};
|
||||
|
||||
fn main() -> Result<(), ndbioimage::error::Error> {
|
||||
let path = "/path/to/file";
|
||||
let reader = DynReader::new(&path, 0, 0)?;
|
||||
println!("shape: {}", reader.shape());
|
||||
let frame = reader.get_frame(0, 0, 0)?;
|
||||
if let Ok(arr) = <Frame as TryInto<Array2<i8>>>::try_into(frame) {
|
||||
println!("{:?}", arr);
|
||||
} else {
|
||||
println!("could not convert Frame to Array<i8>");
|
||||
}
|
||||
let xml = reader.metadata()?.to_xml()?;
|
||||
println!("{}", xml);
|
||||
Ok(())
|
||||
}
|
||||
let xml = reader.get_ome_xml().unwrap();
|
||||
println!("{}", xml);
|
||||
```
|
||||
|
||||
```
|
||||
use ndarray::Array2;
|
||||
use ndbioimage::Reader;
|
||||
```rust
|
||||
use ndbioimage::{DynReader, Reader};
|
||||
|
||||
let path = "/path/to/file";
|
||||
let reader = Reader::new(&path, 0)?;
|
||||
let view = reader.view();
|
||||
let view = view.max_proj(3)?;
|
||||
let array = view.as_array::<u16>()?
|
||||
fn main() -> Result<(), ndbioimage::error::Error> {
|
||||
let path = "/path/to/file";
|
||||
let reader = DynReader::new(&path, 0, 0)?;
|
||||
let view = reader.view();
|
||||
let view = view.max_proj(3)?;
|
||||
let array = view.as_array::<u16>()?;
|
||||
println!("{:?}", array.shape());
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Command line
|
||||
|
||||
```ndbioimage --help```: show help
|
||||
```ndbioimage image```: show metadata about image
|
||||
```ndbioimage image -w {name}.tif -r```: copy image into image.tif (replacing {name} with image), while registering channels
|
||||
```ndbioimage image -w image.mp4 -C cyan lime red``` copy image into image.mp4 (z will be max projected), make channel colors cyan lime and red
|
||||
```ndbioimage info image```: show metadata about image
|
||||
```ndbioimage tiff image image.tif -r```: copy image into image.tif, while registering channels
|
||||
```ndbioimage movie image image.mp4 -C cyan lime red``` copy image into image.mp4 (z will be max projected), make channel
|
||||
colors cyan lime and red
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "ndbioimage"
|
||||
version = "2027.0.2"
|
||||
version = "2027.0.3"
|
||||
requires-python = ">=3.10"
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: MIT License",
|
||||
|
||||
+47
-3
@@ -1,4 +1,47 @@
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
//! The ndbioimage crate exposes (bio) images 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)
|
||||
//! structure. Additionally, it can automatically calculate an affine transform that corrects for
|
||||
//! chromatic aberrations etc. and apply it on the fly to the image.
|
||||
//!
|
||||
//! Currently, it supports imagej tif files, czi files, micromanager tif sequences and anything
|
||||
//! [bioformats](https://www.openmicroscopy.org/bio-formats/) can handle.
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use ndarray::Array2;
|
||||
//! use ndbioimage::readers::{DynReader, Frame, Reader};
|
||||
//!
|
||||
//! # fn main() -> Result<(), ndbioimage::error::Error> {
|
||||
//! let path = "/path/to/file";
|
||||
//! let reader = DynReader::new(&path, 0, 0)?;
|
||||
//! println!("shape: {}", reader.shape());
|
||||
//! let frame = reader.get_frame(0, 0, 0)?;
|
||||
//! if let Ok(arr) = <Frame as TryInto<Array2<i8>>>::try_into(frame) {
|
||||
//! println!("{:?}", arr);
|
||||
//! } else {
|
||||
//! println!("could not convert Frame to Array<i8>");
|
||||
//! }
|
||||
//! let xml = reader.metadata()?.to_xml()?;
|
||||
//! println!("{}", xml);
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use ndbioimage::readers::{DynReader, Reader};
|
||||
//!
|
||||
//! # fn main() -> Result<(), ndbioimage::error::Error> {
|
||||
//! let path = "/path/to/file";
|
||||
//! let reader = DynReader::new(&path, 0, 0)?;
|
||||
//! let view = reader.view();
|
||||
//! let view = view.max_proj(3)?;
|
||||
//! let array = view.as_array::<u16>()?;
|
||||
//! println!("{:?}", array.shape());
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
pub mod axes;
|
||||
#[cfg(feature = "python")]
|
||||
@@ -14,7 +57,7 @@ pub mod movie;
|
||||
pub mod readers;
|
||||
#[cfg(feature = "tiffwrite")]
|
||||
pub mod tiffwrite;
|
||||
// mod cache;
|
||||
mod utils;
|
||||
|
||||
pub mod main {
|
||||
#[cfg(feature = "tiffwrite")]
|
||||
@@ -30,7 +73,8 @@ pub mod main {
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(arg_required_else_help = true, version, about, long_about = None, propagate_version = true)]
|
||||
#[command(arg_required_else_help = true, version, about, long_about = None, propagate_version = true
|
||||
)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
@@ -162,7 +206,7 @@ pub mod main {
|
||||
output,
|
||||
} => {
|
||||
let options = crate::tiffwrite::TiffOptions::new(
|
||||
Some(crate::tiffwrite::get_bar(
|
||||
Some(crate::utils::progress::get_bar(
|
||||
Some(0),
|
||||
Some("writing tiff file".to_string()),
|
||||
)),
|
||||
|
||||
+6
-19
@@ -2,18 +2,16 @@ use crate::axes::Axis;
|
||||
use crate::colors::Color;
|
||||
use crate::error::Error;
|
||||
use crate::readers::{PixelType, Reader};
|
||||
use crate::utils::progress::get_bar;
|
||||
use crate::view::View;
|
||||
use console::Term;
|
||||
use ffmpeg_sidecar::command::FfmpegCommand;
|
||||
use ffmpeg_sidecar::download::auto_download;
|
||||
use ffmpeg_sidecar::event::{FfmpegEvent, LogLevel};
|
||||
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
|
||||
use itertools::Itertools;
|
||||
use ndarray::{Array2, Array3, Dimension, IxDyn, s, stack};
|
||||
use ordered_float::OrderedFloat;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct MovieOptions {
|
||||
velocity: f64,
|
||||
@@ -138,20 +136,6 @@ fn cframe(frame: Array2<f64>, color: &[u8], a: f64, b: f64) -> Array3<f64> {
|
||||
stack(ndarray::Axis(2), &view).unwrap()
|
||||
}
|
||||
|
||||
/// a progress bar with an ok style that when py::detach is used also works in jupyter
|
||||
pub fn get_bar(count: Option<usize>) -> ProgressBar {
|
||||
let style = ProgressStyle::with_template(
|
||||
"{spinner:.green} {percent}% [{wide_bar:.green/lime}] {pos:>7}/{len:7} [{elapsed}/{eta}, {per_sec:<5}]",
|
||||
).expect("template should be working").progress_chars("#>-");
|
||||
let bar = ProgressBar::with_draw_target(
|
||||
count.map(|i| i as u64),
|
||||
ProgressDrawTarget::term_like_with_hz(Box::new(Term::buffered_stdout()), 20),
|
||||
)
|
||||
.with_style(style);
|
||||
bar.enable_steady_tick(Duration::from_millis(100));
|
||||
bar
|
||||
}
|
||||
|
||||
impl<D, R> View<D, R>
|
||||
where
|
||||
D: Dimension,
|
||||
@@ -252,7 +236,10 @@ where
|
||||
};
|
||||
|
||||
let rt = tokio::runtime::Runtime::new()?;
|
||||
let bar = get_bar(Some(size_t));
|
||||
let bar = get_bar(
|
||||
Some(size_t),
|
||||
Some("autoscaling movie brightness".to_string()),
|
||||
);
|
||||
let rt_bar = bar.clone();
|
||||
let write_task = rt.spawn(async move {
|
||||
for t in 0..size_t {
|
||||
@@ -275,7 +262,7 @@ where
|
||||
});
|
||||
bar.finish();
|
||||
|
||||
let bar = get_bar(Some(size_t));
|
||||
let bar = get_bar(Some(size_t), Some("saving movie".to_string()));
|
||||
let rt_bar = bar.clone();
|
||||
let progress_task = rt.spawn(async move {
|
||||
for event in movie.iter().map_err(|e| Error::Ffmpeg(e.to_string()))? {
|
||||
|
||||
@@ -1741,7 +1741,7 @@ impl PyView {
|
||||
bar: bool,
|
||||
) -> PyResult<()> {
|
||||
let bar = if bar {
|
||||
Some(crate::tiffwrite::get_bar(
|
||||
Some(crate::utils::progress::get_bar(
|
||||
Some(0),
|
||||
Some("writing tiff file".to_string()),
|
||||
))
|
||||
@@ -1991,7 +1991,16 @@ impl PyShape {
|
||||
false,
|
||||
)
|
||||
} else if idx.is_instance_of::<PyInt>() {
|
||||
(vec![idx.cast::<PyInt>()?.extract::<usize>()?], true)
|
||||
let i = idx.cast::<PyInt>()?.extract::<isize>()?;
|
||||
let len = self.inner.order.len() as isize;
|
||||
let i = if i < 0 { i + len } else { i };
|
||||
if i < 0 || i >= len {
|
||||
return Err(PyIndexError::new_err(format!(
|
||||
"index {} is out of bounds for size {}",
|
||||
i, len
|
||||
)));
|
||||
}
|
||||
(vec![i as usize], true)
|
||||
} else {
|
||||
return Err(PyErr::new::<PyTypeError, _>(format!(
|
||||
"Unknown type: {:?}",
|
||||
|
||||
+1
-1
@@ -136,7 +136,7 @@ pub enum ArrayT<D: Dimension> {
|
||||
F128(Array<f64, D>), // f128 is nightly
|
||||
}
|
||||
|
||||
pub(crate) type Frame = ArrayT<Ix2>;
|
||||
pub type Frame = ArrayT<Ix2>;
|
||||
|
||||
pub trait Reader: Clone + Sized + Debug + Send + Hash + Into<DynReader> {
|
||||
fn new<P>(path: P, series: usize, position: usize) -> Result<Self, Error>
|
||||
|
||||
@@ -5,10 +5,9 @@ use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub use crate::readers::{ArrayT, PixelType, Reader};
|
||||
use crate::readers::{DynReader, Frame, Shape};
|
||||
use crate::readers::{ArrayT, DynReader, Frame, PixelType, Reader, Shape};
|
||||
use itertools::Itertools;
|
||||
use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder};
|
||||
use j4rs::{Instance, InvocationArg, JavaOpt, Jvm, JvmBuilder};
|
||||
use std::cell::OnceCell;
|
||||
use std::collections::HashSet;
|
||||
use std::hash::{Hash, Hasher};
|
||||
@@ -109,8 +108,16 @@ fn jvm() -> Rc<Jvm> {
|
||||
let j = JvmBuilder::new()
|
||||
.skip_setting_native_lib()
|
||||
.with_base_path(class_path.to_str().unwrap())
|
||||
.build()
|
||||
.expect("Failed to build JVM");
|
||||
.java_opt(JavaOpt::new("--enable-native-access=ALL-UNNAMED"))
|
||||
.build();
|
||||
let j = match j {
|
||||
Ok(j) => j,
|
||||
Err(_) => JvmBuilder::new()
|
||||
.skip_setting_native_lib()
|
||||
.with_base_path(class_path.to_str().unwrap())
|
||||
.build()
|
||||
.expect("Failed to build JVM"),
|
||||
};
|
||||
if let Ok(e) = InvocationArg::try_from("ERROR") {
|
||||
let _ = j.invoke_static(
|
||||
"loci.common.DebugTools",
|
||||
@@ -122,7 +129,7 @@ fn jvm() -> Rc<Jvm> {
|
||||
})
|
||||
}
|
||||
})
|
||||
.clone()
|
||||
.clone()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -331,7 +331,7 @@ impl Reader for TiffReader {
|
||||
|
||||
fn get_frame(&self, c: usize, z: usize, t: usize) -> Result<Frame, Error> {
|
||||
let (page_idx, offset, stride) = if self.p_ndim == 3 {
|
||||
(z * self.shape.t + t, c, self.n_samples)
|
||||
(t * self.shape.z + z, c, self.n_samples)
|
||||
} else {
|
||||
(c + z * self.shape.c + t * self.shape.c * self.shape.z, 0, 1)
|
||||
};
|
||||
|
||||
+19
-26
@@ -4,15 +4,14 @@ use crate::error::Error;
|
||||
use crate::metadata::Metadata;
|
||||
use crate::readers::{DynReader, PixelType, Reader};
|
||||
use crate::stats::MinMax;
|
||||
use crate::utils::progress::get_bar;
|
||||
use crate::view::{Number, View};
|
||||
use console::Term;
|
||||
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
|
||||
use indicatif::ProgressBar;
|
||||
use itertools::iproduct;
|
||||
use ndarray::{Array0, Array1, Array2, ArrayD, Dimension};
|
||||
use rayon::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
use std::time::Duration;
|
||||
use tiffwrite::{Bytes, Colors, Compression, IJTiffFile};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -34,23 +33,6 @@ impl Default for TiffOptions {
|
||||
}
|
||||
}
|
||||
|
||||
/// a progress bar with an ok style that when py::detach is used also works in jupyter
|
||||
pub fn get_bar(count: Option<usize>, message: Option<String>) -> ProgressBar {
|
||||
let style = ProgressStyle::with_template(
|
||||
"{spinner:.green} {percent}% [{wide_bar:.green/lime}] {pos:>7}/{len:7} [{elapsed}/{eta}, {per_sec:<5}]",
|
||||
).expect("template should be working").progress_chars("#>-");
|
||||
let bar = ProgressBar::with_draw_target(
|
||||
count.map(|i| i as u64),
|
||||
ProgressDrawTarget::term_like_with_hz(Box::new(Term::buffered_stdout()), 20),
|
||||
)
|
||||
.with_style(style);
|
||||
if let Some(message) = message {
|
||||
bar.set_message(message);
|
||||
}
|
||||
bar.enable_steady_tick(Duration::from_millis(100));
|
||||
bar
|
||||
}
|
||||
|
||||
impl TiffOptions {
|
||||
pub fn new(
|
||||
bar: Option<ProgressBar>,
|
||||
@@ -258,14 +240,25 @@ mod tests {
|
||||
use std::fs::create_dir_all;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(any(
|
||||
feature = "czi",
|
||||
feature = "tiffseq",
|
||||
feature = "tiff",
|
||||
feature = "bioformats_java"
|
||||
))]
|
||||
#[test]
|
||||
fn tiff() -> Result<(), Error> {
|
||||
#[cfg(any(feature = "czi", feature = "bioformats_java"))]
|
||||
let file = "czi/1xp53-01-AP1.czi";
|
||||
#[cfg(feature = "tiff")]
|
||||
let file = "tiff/20251014_20-Pos_000_000_loc_results_Cy3.tif";
|
||||
#[cfg(feature = "tiffseq")]
|
||||
let file = "tiffseq/20-Pos_005_005";
|
||||
let file = if cfg!(any(feature = "czi", feature = "bioformats_java")) {
|
||||
"czi/1xp53-01-AP1.czi"
|
||||
} else if cfg!(feature = "tiff") {
|
||||
"tiff/20251014_20-Pos_000_000_loc_results_Cy3.tif"
|
||||
} else if cfg!(feature = "tiffseq") {
|
||||
"tiffseq/20-Pos_005_005"
|
||||
} else {
|
||||
unreachable!(
|
||||
"need to enable one of these features: czi, bioformats_java, tiff, tiffseq"
|
||||
);
|
||||
};
|
||||
let path = std::env::current_dir()?
|
||||
.join("tests")
|
||||
.join("files")
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
#[cfg(any(feature = "tiffwrite", feature = "movie"))]
|
||||
pub(crate) mod progress {
|
||||
use console::Term;
|
||||
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressState, ProgressStyle};
|
||||
use std::time::Duration;
|
||||
|
||||
/// a progress bar with an ok style that when py::detach is used also works in jupyter
|
||||
pub fn get_bar(count: Option<usize>, message: Option<String>) -> ProgressBar {
|
||||
let style = ProgressStyle::with_template(
|
||||
"{spinner:.green} {msg} {percent}% [{wide_bar:.green/lime}] {pos:>7}/{len:7} [{elapsed}/{eta}, {rate}]",
|
||||
)
|
||||
.expect("could not build progress bar style")
|
||||
.with_key("rate", |state: &ProgressState, w: &mut dyn std::fmt::Write| {
|
||||
if state.per_sec() < 1.0 {
|
||||
write!(w, "{:>4.2} s", 1.0 / state.per_sec()).expect("could not write to progress bar");
|
||||
} else {
|
||||
write!(w, "{:>4.2}/s", state.per_sec()).expect("could not write to progress bar");
|
||||
}
|
||||
})
|
||||
.progress_chars("#>-");
|
||||
let bar = ProgressBar::with_draw_target(
|
||||
count.map(|i| i as u64),
|
||||
ProgressDrawTarget::term_like_with_hz(Box::new(Term::buffered_stdout()), 20),
|
||||
)
|
||||
.with_style(style);
|
||||
if let Some(message) = message {
|
||||
bar.set_message(message);
|
||||
}
|
||||
bar.enable_steady_tick(Duration::from_millis(100));
|
||||
bar
|
||||
}
|
||||
}
|
||||
+17
-8
@@ -1197,14 +1197,23 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
let _ = out.insert(a);
|
||||
}
|
||||
}
|
||||
let mut n = 1;
|
||||
for (ax, size) in self.shape().to_hashmap().into_iter() {
|
||||
if ((ax == Axis::C) || (ax == Axis::Z) || (ax == Axis::T))
|
||||
&& let Some(Operation::Mean) = self.operations.get(&ax)
|
||||
{
|
||||
n *= size;
|
||||
}
|
||||
}
|
||||
let n = if let Some((&ax, op)) = op_czt.first()
|
||||
&& *op == Operation::Mean
|
||||
{
|
||||
self.axes
|
||||
.iter()
|
||||
.zip(self.slice.iter())
|
||||
.find(|(a, _)| **a == ax)
|
||||
.and_then(|(_, s)| match s {
|
||||
SliceInfoElem::Slice { start, end, step } => {
|
||||
end.map(|e| (((e - start).max(0) / step) as usize).max(1))
|
||||
}
|
||||
_ => Some(1),
|
||||
})
|
||||
.unwrap_or(1)
|
||||
} else {
|
||||
1
|
||||
};
|
||||
let array = if n == 1 {
|
||||
out.take().unwrap()
|
||||
} else {
|
||||
|
||||
@@ -14,10 +14,11 @@ def array():
|
||||
return np.random.randint(0, 255, (64, 64, 2, 3, 4), "uint16")
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def image(array):
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
file = Path(folder) / "tiff" / "test.tif"
|
||||
file.parent.mkdir(parents=True)
|
||||
tiffwrite(file, array, "yxczt")
|
||||
with Imread(file, axes="yxczt") as im:
|
||||
yield im
|
||||
|
||||
Reference in New Issue
Block a user