- implement custom error types
- less restrictive dependency versions - some extra features and bugfixes for movie writing - make python tests work again
This commit is contained in:
55
Cargo.toml
55
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ndbioimage"
|
name = "ndbioimage"
|
||||||
version = "2025.8.0"
|
version = "2026.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.85.1"
|
rust-version = "1.85.1"
|
||||||
authors = ["Wim Pomp <w.pomp@nki.nl>"]
|
authors = ["Wim Pomp <w.pomp@nki.nl>"]
|
||||||
@@ -8,6 +8,7 @@ license = "MIT"
|
|||||||
description = "Read bio image formats using the bio-formats java package."
|
description = "Read bio image formats using the bio-formats java package."
|
||||||
homepage = "https://github.com/wimpomp/ndbioimage/tree/rs"
|
homepage = "https://github.com/wimpomp/ndbioimage/tree/rs"
|
||||||
repository = "https://github.com/wimpomp/ndbioimage/tree/rs"
|
repository = "https://github.com/wimpomp/ndbioimage/tree/rs"
|
||||||
|
documentation = "https://docs.rs/ndbioimage"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["bioformats", "imread", "ndarray", "metadata"]
|
keywords = ["bioformats", "imread", "ndarray", "metadata"]
|
||||||
categories = ["multimedia::images", "science"]
|
categories = ["multimedia::images", "science"]
|
||||||
@@ -19,39 +20,38 @@ name = "ndbioimage"
|
|||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1.0.99", features = ["backtrace"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
clap = { version = "4.5.45", features = ["derive"] }
|
ffmpeg-sidecar = { version = "2", optional = true }
|
||||||
ffmpeg-sidecar = { version = "2.1.0", optional = true }
|
itertools = "0.14"
|
||||||
itertools = "0.14.0"
|
indexmap = { version = "2", features = ["serde"] }
|
||||||
indexmap = { version = "2.0.0", features = ["serde"] }
|
indicatif = { version = "0.18", features = ["rayon"], optional = true }
|
||||||
indicatif = { version = "0.18.0", features = ["rayon"], optional = true }
|
j4rs = "0.24"
|
||||||
j4rs = "0.22.0"
|
ndarray = { version = "0.17", features = ["serde"] }
|
||||||
ndarray = { version = "0.16.1", features = ["serde"] }
|
num = "0.4"
|
||||||
num = "0.4.3"
|
numpy = { version = "0.27", optional = true }
|
||||||
numpy = { version = "0.25.0", optional = true }
|
ordered-float = "5"
|
||||||
ordered-float = "5.0.0"
|
rayon = { version = "1", optional = true }
|
||||||
rayon = { version = "1.11.0", optional = true }
|
serde = { version = "1", features = ["rc"] }
|
||||||
serde = { version = "1.0.219", features = ["rc"] }
|
serde_json = { version = "1", optional = true }
|
||||||
serde_json = { version = "1.0.143", optional = true }
|
serde_with = "3"
|
||||||
serde_with = "3.12.0"
|
tiffwrite = { version = "2025.12.0", optional = true}
|
||||||
tiffwrite = { version = "2025.5.0", optional = true}
|
thread_local = "1"
|
||||||
thread_local = "1.1.9"
|
ome-metadata = "0.3"
|
||||||
ome-metadata = "0.2.2"
|
lazy_static = "1"
|
||||||
lazy_static = "1.5.0"
|
thiserror = "2"
|
||||||
|
|
||||||
[dependencies.pyo3]
|
[dependencies.pyo3]
|
||||||
version = "0.25.1"
|
version = "0.27"
|
||||||
features = ["extension-module", "abi3-py310", "generate-import-lib", "anyhow"]
|
features = ["extension-module", "abi3-py310", "generate-import-lib", "anyhow"]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rayon = "1.10.0"
|
rayon = "1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
anyhow = "1.0.99"
|
j4rs = "0.24"
|
||||||
j4rs = "0.22.0"
|
ffmpeg-sidecar = "2"
|
||||||
ffmpeg-sidecar = "2.1.0"
|
retry = "2"
|
||||||
retry = "2.1.0"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# Enables formats for which code in bioformats with a GPL license is needed
|
# Enables formats for which code in bioformats with a GPL license is needed
|
||||||
@@ -62,3 +62,6 @@ python = ["dep:pyo3", "dep:numpy", "dep:serde_json"]
|
|||||||
tiff = ["dep:tiffwrite", "dep:indicatif", "dep:rayon"]
|
tiff = ["dep:tiffwrite", "dep:indicatif", "dep:rayon"]
|
||||||
# Enables writing as mp4 using ffmpeg
|
# Enables writing as mp4 using ffmpeg
|
||||||
movie = ["dep:ffmpeg-sidecar"]
|
movie = ["dep:ffmpeg-sidecar"]
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
features = ["gpl-formats", "tiff", "movie"]
|
||||||
@@ -102,6 +102,3 @@ let array = view.as_array::<u16>()?
|
|||||||
```ndbioimage image```: show metadata about image
|
```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 {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 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
|
||||||
|
|
||||||
# TODO
|
|
||||||
- more image formats
|
|
||||||
|
|||||||
59
build.rs
59
build.rs
@@ -1,8 +1,16 @@
|
|||||||
#[cfg(not(feature = "python"))]
|
#[cfg(not(feature = "python"))]
|
||||||
use j4rs::{JvmBuilder, MavenArtifact, MavenArtifactRepo, MavenSettings, errors::J4RsError};
|
use j4rs::{JvmBuilder, MavenArtifact, MavenArtifactRepo, MavenSettings, errors::J4RsError};
|
||||||
|
|
||||||
#[cfg(not(feature = "python"))]
|
#[cfg(not(feature = "python"))]
|
||||||
use retry::{delay, delay::Exponential, retry};
|
use retry::{delay, delay::Exponential, retry};
|
||||||
|
use std::error::Error;
|
||||||
|
#[cfg(not(feature = "python"))]
|
||||||
|
use std::fmt::Display;
|
||||||
|
#[cfg(not(feature = "python"))]
|
||||||
|
use std::fmt::Formatter;
|
||||||
|
#[cfg(not(feature = "python"))]
|
||||||
|
use std::path::PathBuf;
|
||||||
|
#[cfg(not(feature = "python"))]
|
||||||
|
use std::{env, fs};
|
||||||
|
|
||||||
#[cfg(feature = "python")]
|
#[cfg(feature = "python")]
|
||||||
use j4rs::Jvm;
|
use j4rs::Jvm;
|
||||||
@@ -10,7 +18,23 @@ use j4rs::Jvm;
|
|||||||
#[cfg(feature = "movie")]
|
#[cfg(feature = "movie")]
|
||||||
use ffmpeg_sidecar::download::auto_download;
|
use ffmpeg_sidecar::download::auto_download;
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
#[cfg(not(feature = "python"))]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum BuildError {
|
||||||
|
BioFormatsNotDownloaded,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "python"))]
|
||||||
|
impl Display for BuildError {
|
||||||
|
fn fmt(&self, fmt: &mut Formatter) -> Result<(), std::fmt::Error> {
|
||||||
|
write!(fmt, "Bioformats package not downloaded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "python"))]
|
||||||
|
impl Error for BuildError {}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
println!("cargo::rerun-if-changed=build.rs");
|
println!("cargo::rerun-if-changed=build.rs");
|
||||||
|
|
||||||
if std::env::var("DOCS_RS").is_err() {
|
if std::env::var("DOCS_RS").is_err() {
|
||||||
@@ -18,10 +42,16 @@ fn main() -> anyhow::Result<()> {
|
|||||||
auto_download()?;
|
auto_download()?;
|
||||||
|
|
||||||
#[cfg(not(feature = "python"))]
|
#[cfg(not(feature = "python"))]
|
||||||
|
{
|
||||||
retry(
|
retry(
|
||||||
Exponential::from_millis(1000).map(delay::jitter).take(4),
|
Exponential::from_millis(1000).map(delay::jitter).take(4),
|
||||||
deploy_java_artifacts,
|
deploy_java_artifacts,
|
||||||
)?;
|
)?;
|
||||||
|
let path = default_jassets_path()?;
|
||||||
|
if !path.join("bioformats_package-8.3.0.jar").exists() {
|
||||||
|
Err(BuildError::BioFormatsNotDownloaded)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "python")]
|
#[cfg(feature = "python")]
|
||||||
{
|
{
|
||||||
@@ -58,6 +88,31 @@ fn main() -> anyhow::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "python"))]
|
||||||
|
fn default_jassets_path() -> Result<PathBuf, J4RsError> {
|
||||||
|
let is_build_script = env::var("OUT_DIR").is_ok();
|
||||||
|
|
||||||
|
let mut start_path = if is_build_script {
|
||||||
|
PathBuf::from(env::var("OUT_DIR")?)
|
||||||
|
} else {
|
||||||
|
env::current_exe()?
|
||||||
|
};
|
||||||
|
start_path = fs::canonicalize(start_path)?;
|
||||||
|
|
||||||
|
while start_path.pop() {
|
||||||
|
for entry in std::fs::read_dir(&start_path)? {
|
||||||
|
let path = entry?.path();
|
||||||
|
if path.file_name().map(|x| x == "jassets").unwrap_or(false) {
|
||||||
|
return Ok(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(J4RsError::GeneralError(
|
||||||
|
"Can not find jassets directory".to_owned(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "python"))]
|
#[cfg(not(feature = "python"))]
|
||||||
fn deploy_java_artifacts() -> Result<(), J4RsError> {
|
fn deploy_java_artifacts() -> Result<(), J4RsError> {
|
||||||
let jvm = JvmBuilder::new()
|
let jvm = JvmBuilder::new()
|
||||||
|
|||||||
@@ -45,6 +45,10 @@ if not list((Path(__file__).parent / "jassets").glob("bioformats*.jar")):
|
|||||||
rs.download_bioformats(True)
|
rs.download_bioformats(True)
|
||||||
|
|
||||||
|
|
||||||
|
class ReaderNotFoundError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TransformTiff(IJTiffParallel):
|
class TransformTiff(IJTiffParallel):
|
||||||
"""transform frames in a parallel process to speed up saving"""
|
"""transform frames in a parallel process to speed up saving"""
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ python-source = "py"
|
|||||||
features = ["pyo3/extension-module", "python", "gpl-formats"]
|
features = ["pyo3/extension-module", "python", "gpl-formats"]
|
||||||
module-name = "ndbioimage.ndbioimage_rs"
|
module-name = "ndbioimage.ndbioimage_rs"
|
||||||
exclude = ["py/ndbioimage/jassets/*", "py/ndbioimage/deps/*"]
|
exclude = ["py/ndbioimage/jassets/*", "py/ndbioimage/deps/*"]
|
||||||
|
strip = true
|
||||||
|
|
||||||
[tool.isort]
|
[tool.isort]
|
||||||
line_length = 119
|
line_length = 119
|
||||||
|
|||||||
57
src/axes.rs
57
src/axes.rs
@@ -1,5 +1,5 @@
|
|||||||
|
use crate::error::Error;
|
||||||
use crate::stats::MinMax;
|
use crate::stats::MinMax;
|
||||||
use anyhow::{Error, Result, anyhow};
|
|
||||||
use ndarray::{Array, Dimension, Ix2, SliceInfo, SliceInfoElem};
|
use ndarray::{Array, Dimension, Ix2, SliceInfo, SliceInfoElem};
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use serde_with::{DeserializeAs, SerializeAs};
|
use serde_with::{DeserializeAs, SerializeAs};
|
||||||
@@ -13,10 +13,15 @@ pub trait Ax {
|
|||||||
fn n(&self) -> usize;
|
fn n(&self) -> usize;
|
||||||
|
|
||||||
/// the indices of axes in self.axes, which always has all of CZTYX
|
/// the indices of axes in self.axes, which always has all of CZTYX
|
||||||
fn pos(&self, axes: &[Axis], slice: &[SliceInfoElem]) -> Result<usize>;
|
fn pos(&self, axes: &[Axis], slice: &[SliceInfoElem]) -> Result<usize, Error>;
|
||||||
|
|
||||||
/// the indices of axes in self.axes, which always has all of CZTYX, but skip axes with an operation
|
/// the indices of axes in self.axes, which always has all of CZTYX, but skip axes with an operation
|
||||||
fn pos_op(&self, axes: &[Axis], slice: &[SliceInfoElem], op_axes: &[Axis]) -> Result<usize>;
|
fn pos_op(
|
||||||
|
&self,
|
||||||
|
axes: &[Axis],
|
||||||
|
slice: &[SliceInfoElem],
|
||||||
|
op_axes: &[Axis],
|
||||||
|
) -> Result<usize, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enum for CZTYX axes or a new axis
|
/// Enum for CZTYX axes or a new axis
|
||||||
@@ -39,7 +44,7 @@ impl Hash for Axis {
|
|||||||
impl FromStr for Axis {
|
impl FromStr for Axis {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match s.to_uppercase().as_str() {
|
match s.to_uppercase().as_str() {
|
||||||
"C" => Ok(Axis::C),
|
"C" => Ok(Axis::C),
|
||||||
"Z" => Ok(Axis::Z),
|
"Z" => Ok(Axis::Z),
|
||||||
@@ -47,7 +52,7 @@ impl FromStr for Axis {
|
|||||||
"Y" => Ok(Axis::Y),
|
"Y" => Ok(Axis::Y),
|
||||||
"X" => Ok(Axis::X),
|
"X" => Ok(Axis::X),
|
||||||
"NEW" => Ok(Axis::New),
|
"NEW" => Ok(Axis::New),
|
||||||
_ => Err(anyhow!("invalid axis: {}", s)),
|
_ => Err(Error::InvalidAxis(s.to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,18 +76,23 @@ impl Ax for Axis {
|
|||||||
*self as usize
|
*self as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pos(&self, axes: &[Axis], _slice: &[SliceInfoElem]) -> Result<usize> {
|
fn pos(&self, axes: &[Axis], _slice: &[SliceInfoElem]) -> Result<usize, Error> {
|
||||||
if let Some(pos) = axes.iter().position(|a| a == self) {
|
if let Some(pos) = axes.iter().position(|a| a == self) {
|
||||||
Ok(pos)
|
Ok(pos)
|
||||||
} else {
|
} else {
|
||||||
Err(Error::msg(format!(
|
Err(Error::AxisNotFound(
|
||||||
"Axis {:?} not found in axes {:?}",
|
format!("{:?}", self),
|
||||||
self, axes
|
format!("{:?}", axes),
|
||||||
)))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pos_op(&self, axes: &[Axis], _slice: &[SliceInfoElem], _op_axes: &[Axis]) -> Result<usize> {
|
fn pos_op(
|
||||||
|
&self,
|
||||||
|
axes: &[Axis],
|
||||||
|
_slice: &[SliceInfoElem],
|
||||||
|
_op_axes: &[Axis],
|
||||||
|
) -> Result<usize, Error> {
|
||||||
self.pos(axes, _slice)
|
self.pos(axes, _slice)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,7 +102,7 @@ impl Ax for usize {
|
|||||||
*self
|
*self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pos(&self, _axes: &[Axis], slice: &[SliceInfoElem]) -> Result<usize> {
|
fn pos(&self, _axes: &[Axis], slice: &[SliceInfoElem]) -> Result<usize, Error> {
|
||||||
let idx: Vec<_> = slice
|
let idx: Vec<_> = slice
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -101,7 +111,12 @@ impl Ax for usize {
|
|||||||
Ok(idx[*self])
|
Ok(idx[*self])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pos_op(&self, axes: &[Axis], slice: &[SliceInfoElem], op_axes: &[Axis]) -> Result<usize> {
|
fn pos_op(
|
||||||
|
&self,
|
||||||
|
axes: &[Axis],
|
||||||
|
slice: &[SliceInfoElem],
|
||||||
|
op_axes: &[Axis],
|
||||||
|
) -> Result<usize, Error> {
|
||||||
let idx: Vec<_> = axes
|
let idx: Vec<_> = axes
|
||||||
.iter()
|
.iter()
|
||||||
.zip(slice.iter())
|
.zip(slice.iter())
|
||||||
@@ -132,7 +147,7 @@ impl Operation {
|
|||||||
&self,
|
&self,
|
||||||
array: Array<T, D>,
|
array: Array<T, D>,
|
||||||
axis: usize,
|
axis: usize,
|
||||||
) -> Result<<Array<T, D> as MinMax>::Output>
|
) -> Result<<Array<T, D> as MinMax>::Output, Error>
|
||||||
where
|
where
|
||||||
D: Dimension,
|
D: Dimension,
|
||||||
Array<T, D>: MinMax,
|
Array<T, D>: MinMax,
|
||||||
@@ -154,8 +169,11 @@ impl PartialEq for Axis {
|
|||||||
|
|
||||||
pub(crate) fn slice_info<D: Dimension>(
|
pub(crate) fn slice_info<D: Dimension>(
|
||||||
info: &[SliceInfoElem],
|
info: &[SliceInfoElem],
|
||||||
) -> Result<SliceInfo<&[SliceInfoElem], Ix2, D>> {
|
) -> Result<SliceInfo<&[SliceInfoElem], Ix2, D>, Error> {
|
||||||
Ok(info.try_into()?)
|
match info.try_into() {
|
||||||
|
Ok(slice) => Ok(slice),
|
||||||
|
Err(err) => Err(Error::TryInto(err.to_string())),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -171,10 +189,7 @@ pub(crate) enum SliceInfoElemDef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SerializeAs<SliceInfoElem> for SliceInfoElemDef {
|
impl SerializeAs<SliceInfoElem> for SliceInfoElemDef {
|
||||||
fn serialize_as<S>(
|
fn serialize_as<S>(source: &SliceInfoElem, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
source: &SliceInfoElem,
|
|
||||||
serializer: S,
|
|
||||||
) -> std::result::Result<S::Ok, S::Error>
|
|
||||||
where
|
where
|
||||||
S: Serializer,
|
S: Serializer,
|
||||||
{
|
{
|
||||||
@@ -183,7 +198,7 @@ impl SerializeAs<SliceInfoElem> for SliceInfoElemDef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> DeserializeAs<'de, SliceInfoElem> for SliceInfoElemDef {
|
impl<'de> DeserializeAs<'de, SliceInfoElem> for SliceInfoElemDef {
|
||||||
fn deserialize_as<D>(deserializer: D) -> std::result::Result<SliceInfoElem, D::Error>
|
fn deserialize_as<D>(deserializer: D) -> Result<SliceInfoElem, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use anyhow::Result;
|
use crate::error::Error;
|
||||||
use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder};
|
use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder};
|
||||||
use std::cell::OnceCell;
|
use std::cell::OnceCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@@ -12,7 +12,7 @@ fn jvm() -> Rc<Jvm> {
|
|||||||
JVM.with(|cell| {
|
JVM.with(|cell| {
|
||||||
cell.get_or_init(move || {
|
cell.get_or_init(move || {
|
||||||
#[cfg(feature = "python")]
|
#[cfg(feature = "python")]
|
||||||
let path = crate::py::ndbioimage_file().unwrap();
|
let path = crate::py::ndbioimage_file();
|
||||||
|
|
||||||
#[cfg(not(feature = "python"))]
|
#[cfg(not(feature = "python"))]
|
||||||
let path = std::env::current_exe()
|
let path = std::env::current_exe()
|
||||||
@@ -45,13 +45,12 @@ fn jvm() -> Rc<Jvm> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn download_bioformats(gpl_formats: bool) -> Result<()> {
|
pub fn download_bioformats(gpl_formats: bool) -> Result<(), Error> {
|
||||||
#[cfg(feature = "python")]
|
#[cfg(feature = "python")]
|
||||||
let path = crate::py::ndbioimage_file()?;
|
let path = crate::py::ndbioimage_file();
|
||||||
|
|
||||||
#[cfg(not(feature = "python"))]
|
#[cfg(not(feature = "python"))]
|
||||||
let path = std::env::current_exe()
|
let path = std::env::current_exe()?
|
||||||
.unwrap()
|
|
||||||
.parent()
|
.parent()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_path_buf();
|
.to_path_buf();
|
||||||
@@ -82,8 +81,8 @@ pub fn download_bioformats(gpl_formats: bool) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! method_return {
|
macro_rules! method_return {
|
||||||
($R:ty$(|c)?) => { Result<$R> };
|
($R:ty$(|c)?) => { Result<$R, Error> };
|
||||||
() => { Result<()> };
|
() => { Result<(), Error> };
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! method_arg {
|
macro_rules! method_arg {
|
||||||
@@ -139,7 +138,7 @@ pub struct DebugTools;
|
|||||||
|
|
||||||
impl DebugTools {
|
impl DebugTools {
|
||||||
/// set debug root level: ERROR, DEBUG, TRACE, INFO, OFF
|
/// set debug root level: ERROR, DEBUG, TRACE, INFO, OFF
|
||||||
pub fn set_root_level(level: &str) -> Result<()> {
|
pub fn set_root_level(level: &str) -> Result<(), Error> {
|
||||||
jvm().invoke_static(
|
jvm().invoke_static(
|
||||||
"loci.common.DebugTools",
|
"loci.common.DebugTools",
|
||||||
"setRootLevel",
|
"setRootLevel",
|
||||||
@@ -153,7 +152,7 @@ impl DebugTools {
|
|||||||
pub(crate) struct ChannelSeparator(Instance);
|
pub(crate) struct ChannelSeparator(Instance);
|
||||||
|
|
||||||
impl ChannelSeparator {
|
impl ChannelSeparator {
|
||||||
pub(crate) fn new(image_reader: &ImageReader) -> Result<Self> {
|
pub(crate) fn new(image_reader: &ImageReader) -> Result<Self, Error> {
|
||||||
let jvm = jvm();
|
let jvm = jvm();
|
||||||
let channel_separator = jvm.create_instance(
|
let channel_separator = jvm.create_instance(
|
||||||
"loci.formats.ChannelSeparator",
|
"loci.formats.ChannelSeparator",
|
||||||
@@ -162,7 +161,7 @@ impl ChannelSeparator {
|
|||||||
Ok(ChannelSeparator(channel_separator))
|
Ok(ChannelSeparator(channel_separator))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn open_bytes(&self, index: i32) -> Result<Vec<u8>> {
|
pub(crate) fn open_bytes(&self, index: i32) -> Result<Vec<u8>, Error> {
|
||||||
Ok(transmute_vec(self.open_bi8(index)?))
|
Ok(transmute_vec(self.open_bi8(index)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,16 +179,16 @@ impl Drop for ImageReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ImageReader {
|
impl ImageReader {
|
||||||
pub(crate) fn new() -> Result<Self> {
|
pub(crate) fn new() -> Result<Self, Error> {
|
||||||
let reader = jvm().create_instance("loci.formats.ImageReader", InvocationArg::empty())?;
|
let reader = jvm().create_instance("loci.formats.ImageReader", InvocationArg::empty())?;
|
||||||
Ok(ImageReader(reader))
|
Ok(ImageReader(reader))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn open_bytes(&self, index: i32) -> Result<Vec<u8>> {
|
pub(crate) fn open_bytes(&self, index: i32) -> Result<Vec<u8>, Error> {
|
||||||
Ok(transmute_vec(self.open_bi8(index)?))
|
Ok(transmute_vec(self.open_bi8(index)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn ome_xml(&self) -> Result<String> {
|
pub(crate) fn ome_xml(&self) -> Result<String, Error> {
|
||||||
let mds = self.get_metadata_store()?;
|
let mds = self.get_metadata_store()?;
|
||||||
Ok(jvm()
|
Ok(jvm()
|
||||||
.chain(&mds)?
|
.chain(&mds)?
|
||||||
@@ -224,7 +223,7 @@ impl ImageReader {
|
|||||||
pub(crate) struct MetadataTools(Instance);
|
pub(crate) struct MetadataTools(Instance);
|
||||||
|
|
||||||
impl MetadataTools {
|
impl MetadataTools {
|
||||||
pub(crate) fn new() -> Result<Self> {
|
pub(crate) fn new() -> Result<Self, Error> {
|
||||||
let meta_data_tools =
|
let meta_data_tools =
|
||||||
jvm().create_instance("loci.formats.MetadataTools", InvocationArg::empty())?;
|
jvm().create_instance("loci.formats.MetadataTools", InvocationArg::empty())?;
|
||||||
Ok(MetadataTools(meta_data_tools))
|
Ok(MetadataTools(meta_data_tools))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use anyhow::{Error, Result};
|
use crate::error::Error;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
@@ -177,12 +177,12 @@ pub struct Color {
|
|||||||
impl FromStr for Color {
|
impl FromStr for Color {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let s = if !s.starts_with("#") {
|
let s = if !s.starts_with("#") {
|
||||||
if let Some(s) = COLORS.get(s) {
|
if let Some(s) = COLORS.get(s) {
|
||||||
s
|
s
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::msg(format!("invalid color: {}", s)));
|
return Err(Error::InvalidColor(s.to_string()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s
|
s
|
||||||
|
|||||||
65
src/error.rs
Normal file
65
src/error.rs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error(transparent)]
|
||||||
|
IO(#[from] std::io::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
Shape(#[from] ndarray::ShapeError),
|
||||||
|
#[error(transparent)]
|
||||||
|
J4rs(#[from] j4rs::errors::J4RsError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Infallible(#[from] std::convert::Infallible),
|
||||||
|
#[error(transparent)]
|
||||||
|
ParseIntError(#[from] std::num::ParseIntError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Ome(#[from] ome_metadata::error::Error),
|
||||||
|
#[cfg(feature = "tiff")]
|
||||||
|
#[error(transparent)]
|
||||||
|
TemplateError(#[from] indicatif::style::TemplateError),
|
||||||
|
#[cfg(feature = "tiff")]
|
||||||
|
#[error(transparent)]
|
||||||
|
TiffWrite(#[from] tiffwrite::error::Error),
|
||||||
|
#[error("invalid axis: {0}")]
|
||||||
|
InvalidAxis(String),
|
||||||
|
#[error("axis {0} not found in axes {1}")]
|
||||||
|
AxisNotFound(String, String),
|
||||||
|
#[error("conversion error: {0}")]
|
||||||
|
TryInto(String),
|
||||||
|
#[error("file already exists {0}")]
|
||||||
|
FileAlreadyExists(String),
|
||||||
|
#[error("could not download ffmpeg: {0}")]
|
||||||
|
Ffmpeg(String),
|
||||||
|
#[error("index {0} out of bounds {1}")]
|
||||||
|
OutOfBounds(isize, isize),
|
||||||
|
#[error("axis {0} has length {1}, but was not included")]
|
||||||
|
OutOfBoundsAxis(String, usize),
|
||||||
|
#[error("dimensionality mismatch: {0} != {0}")]
|
||||||
|
DimensionalityMismatch(usize, usize),
|
||||||
|
#[error("axis {0}: {1} is already operated on!")]
|
||||||
|
AxisAlreadyOperated(usize, String),
|
||||||
|
#[error("not enough free dimensions")]
|
||||||
|
NotEnoughFreeDimensions,
|
||||||
|
#[error("cannot cast {0} to {1}")]
|
||||||
|
Cast(String, String),
|
||||||
|
#[error("empty view")]
|
||||||
|
EmptyView,
|
||||||
|
#[error("invalid color: {0}")]
|
||||||
|
InvalidColor(String),
|
||||||
|
#[error("no image or pixels found")]
|
||||||
|
NoImageOrPixels,
|
||||||
|
#[error("invalid attenuation value: {0}")]
|
||||||
|
InvalidAttenuation(String),
|
||||||
|
#[error("not a valid file name")]
|
||||||
|
InvalidFileName,
|
||||||
|
#[error("unknown pixel type {0}")]
|
||||||
|
UnknownPixelType(String),
|
||||||
|
#[error("no mean")]
|
||||||
|
NoMean,
|
||||||
|
#[error("tiff is locked")]
|
||||||
|
TiffLock,
|
||||||
|
#[error("not implemented: {0}")]
|
||||||
|
NotImplemented(String),
|
||||||
|
#[error("cannot parse: {0}")]
|
||||||
|
Parse(String),
|
||||||
|
}
|
||||||
49
src/lib.rs
49
src/lib.rs
@@ -1,3 +1,5 @@
|
|||||||
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
|
||||||
mod bioformats;
|
mod bioformats;
|
||||||
|
|
||||||
pub mod axes;
|
pub mod axes;
|
||||||
@@ -9,6 +11,7 @@ pub mod stats;
|
|||||||
pub mod view;
|
pub mod view;
|
||||||
|
|
||||||
pub mod colors;
|
pub mod colors;
|
||||||
|
pub mod error;
|
||||||
#[cfg(feature = "movie")]
|
#[cfg(feature = "movie")]
|
||||||
pub mod movie;
|
pub mod movie;
|
||||||
#[cfg(feature = "tiff")]
|
#[cfg(feature = "tiff")]
|
||||||
@@ -19,15 +22,15 @@ pub use bioformats::download_bioformats;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::axes::Axis;
|
use crate::axes::Axis;
|
||||||
|
use crate::error::Error;
|
||||||
use crate::reader::{Frame, Reader};
|
use crate::reader::{Frame, Reader};
|
||||||
use crate::stats::MinMax;
|
use crate::stats::MinMax;
|
||||||
use crate::view::Item;
|
use crate::view::Item;
|
||||||
use anyhow::Result;
|
|
||||||
use ndarray::{Array, Array4, Array5, NewAxis};
|
use ndarray::{Array, Array4, Array5, NewAxis};
|
||||||
use ndarray::{Array2, s};
|
use ndarray::{Array2, s};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
|
||||||
fn open(file: &str) -> Result<Reader> {
|
fn open(file: &str) -> Result<Reader, Error> {
|
||||||
let path = std::env::current_dir()?
|
let path = std::env::current_dir()?
|
||||||
.join("tests")
|
.join("tests")
|
||||||
.join("files")
|
.join("files")
|
||||||
@@ -35,7 +38,7 @@ mod tests {
|
|||||||
Reader::new(&path, 0)
|
Reader::new(&path, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_pixel_type(file: &str) -> Result<String> {
|
fn get_pixel_type(file: &str) -> Result<String, Error> {
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"file: {}, pixel type: {:?}",
|
"file: {}, pixel type: {:?}",
|
||||||
@@ -43,13 +46,13 @@ mod tests {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_frame(file: &str) -> Result<Frame> {
|
fn get_frame(file: &str) -> Result<Frame, Error> {
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
reader.get_frame(0, 0, 0)
|
reader.get_frame(0, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read_ser() -> Result<()> {
|
fn read_ser() -> Result<(), Error> {
|
||||||
let file = "Experiment-2029.czi";
|
let file = "Experiment-2029.czi";
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
println!("size: {}, {}", reader.size_y, reader.size_y);
|
println!("size: {}, {}", reader.size_y, reader.size_y);
|
||||||
@@ -63,7 +66,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read_par() -> Result<()> {
|
fn read_par() -> Result<(), Error> {
|
||||||
let files = vec!["Experiment-2029.czi", "test.tif"];
|
let files = vec!["Experiment-2029.czi", "test.tif"];
|
||||||
let pixel_type = files
|
let pixel_type = files
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
@@ -74,7 +77,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read_frame_par() -> Result<()> {
|
fn read_frame_par() -> Result<(), Error> {
|
||||||
let files = vec!["Experiment-2029.czi", "test.tif"];
|
let files = vec!["Experiment-2029.czi", "test.tif"];
|
||||||
let frames = files
|
let frames = files
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
@@ -85,7 +88,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read_sequence() -> Result<()> {
|
fn read_sequence() -> Result<(), Error> {
|
||||||
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
|
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
println!("reader: {:?}", reader);
|
println!("reader: {:?}", reader);
|
||||||
@@ -97,7 +100,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read_sequence1() -> Result<()> {
|
fn read_sequence1() -> Result<(), Error> {
|
||||||
let file = "4-Pos_001_002/img_000000000_Cy3-Cy3_filter_000.tif";
|
let file = "4-Pos_001_002/img_000000000_Cy3-Cy3_filter_000.tif";
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
println!("reader: {:?}", reader);
|
println!("reader: {:?}", reader);
|
||||||
@@ -105,7 +108,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ome_xml() -> Result<()> {
|
fn ome_xml() -> Result<(), Error> {
|
||||||
let file = "Experiment-2029.czi";
|
let file = "Experiment-2029.czi";
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
let xml = reader.get_ome_xml()?;
|
let xml = reader.get_ome_xml()?;
|
||||||
@@ -114,7 +117,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn view() -> Result<()> {
|
fn view() -> Result<(), Error> {
|
||||||
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
|
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
let view = reader.view();
|
let view = reader.view();
|
||||||
@@ -127,7 +130,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn view_shape() -> Result<()> {
|
fn view_shape() -> Result<(), Error> {
|
||||||
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
|
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
let view = reader.view();
|
let view = reader.view();
|
||||||
@@ -138,7 +141,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn view_new_axis() -> Result<()> {
|
fn view_new_axis() -> Result<(), Error> {
|
||||||
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
|
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
let view = reader.view();
|
let view = reader.view();
|
||||||
@@ -153,7 +156,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn view_permute_axes() -> Result<()> {
|
fn view_permute_axes() -> Result<(), Error> {
|
||||||
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
|
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
let view = reader.view();
|
let view = reader.view();
|
||||||
@@ -180,7 +183,7 @@ mod tests {
|
|||||||
($($name:ident: $b:expr $(,)?)*) => {
|
($($name:ident: $b:expr $(,)?)*) => {
|
||||||
$(
|
$(
|
||||||
#[test]
|
#[test]
|
||||||
fn $name() -> Result<()> {
|
fn $name() -> Result<(), Error> {
|
||||||
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
|
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
let view = reader.view();
|
let view = reader.view();
|
||||||
@@ -208,7 +211,7 @@ mod tests {
|
|||||||
($($name:ident: $b:expr $(,)?)*) => {
|
($($name:ident: $b:expr $(,)?)*) => {
|
||||||
$(
|
$(
|
||||||
#[test]
|
#[test]
|
||||||
fn $name() -> Result<()> {
|
fn $name() -> Result<(), Error> {
|
||||||
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
|
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
let view = reader.view();
|
let view = reader.view();
|
||||||
@@ -254,7 +257,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dyn_view() -> Result<()> {
|
fn dyn_view() -> Result<(), Error> {
|
||||||
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
|
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
let a = reader.view().into_dyn();
|
let a = reader.view().into_dyn();
|
||||||
@@ -266,7 +269,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn item() -> Result<()> {
|
fn item() -> Result<(), Error> {
|
||||||
let file = "1xp53-01-AP1.czi";
|
let file = "1xp53-01-AP1.czi";
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
let view = reader.view();
|
let view = reader.view();
|
||||||
@@ -278,7 +281,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn slice_cztyx() -> Result<()> {
|
fn slice_cztyx() -> Result<(), Error> {
|
||||||
let file = "1xp53-01-AP1.czi";
|
let file = "1xp53-01-AP1.czi";
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
let view = reader.view().max_proj(Axis::Z)?.into_dyn();
|
let view = reader.view().max_proj(Axis::Z)?.into_dyn();
|
||||||
@@ -295,7 +298,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reset_axes() -> Result<()> {
|
fn reset_axes() -> Result<(), Error> {
|
||||||
let file = "1xp53-01-AP1.czi";
|
let file = "1xp53-01-AP1.czi";
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
let view = reader.view().max_proj(Axis::Z)?;
|
let view = reader.view().max_proj(Axis::Z)?;
|
||||||
@@ -307,7 +310,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reset_axes2() -> Result<()> {
|
fn reset_axes2() -> Result<(), Error> {
|
||||||
let file = "Experiment-2029.czi";
|
let file = "Experiment-2029.czi";
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
let view = reader.view().squeeze()?;
|
let view = reader.view().squeeze()?;
|
||||||
@@ -317,7 +320,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reset_axes3() -> Result<()> {
|
fn reset_axes3() -> Result<(), Error> {
|
||||||
let file = "Experiment-2029.czi";
|
let file = "Experiment-2029.czi";
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
let view4 = reader.view().squeeze()?;
|
let view4 = reader.view().squeeze()?;
|
||||||
@@ -345,7 +348,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn max() -> Result<()> {
|
fn max() -> Result<(), Error> {
|
||||||
let file = "Experiment-2029.czi";
|
let file = "Experiment-2029.czi";
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
let view = reader.view();
|
let view = reader.view();
|
||||||
|
|||||||
83
src/main.rs
83
src/main.rs
@@ -1,6 +1,8 @@
|
|||||||
use anyhow::Result;
|
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
#[cfg(feature = "movie")]
|
#[cfg(feature = "movie")]
|
||||||
|
use ndarray::SliceInfoElem;
|
||||||
|
use ndbioimage::error::Error;
|
||||||
|
#[cfg(feature = "movie")]
|
||||||
use ndbioimage::movie::MovieOptions;
|
use ndbioimage::movie::MovieOptions;
|
||||||
use ndbioimage::reader::split_path_and_series;
|
use ndbioimage::reader::split_path_and_series;
|
||||||
#[cfg(feature = "tiff")]
|
#[cfg(feature = "tiff")]
|
||||||
@@ -37,16 +39,26 @@ enum Commands {
|
|||||||
Movie {
|
Movie {
|
||||||
#[arg(value_name = "FILE", num_args(1..))]
|
#[arg(value_name = "FILE", num_args(1..))]
|
||||||
file: Vec<PathBuf>,
|
file: Vec<PathBuf>,
|
||||||
#[arg(short, long, value_name = "Velocity", default_value = "3.6")]
|
#[arg(short, long, value_name = "VELOCITY", default_value = "3.6")]
|
||||||
velocity: f64,
|
velocity: f64,
|
||||||
#[arg(short, long, value_name = "BRIGHTNESS")]
|
#[arg(short, long, value_name = "BRIGHTNESS", num_args(1..))]
|
||||||
brightness: Vec<f64>,
|
brightness: Vec<f64>,
|
||||||
#[arg(short, long, value_name = "SCALE", default_value = "1.0")]
|
#[arg(short, long, value_name = "SCALE", default_value = "1.0")]
|
||||||
scale: f64,
|
scale: f64,
|
||||||
#[arg(short, long, value_name = "COLOR", num_args(1..))]
|
#[arg(short = 'C', long, value_name = "COLOR", num_args(1..))]
|
||||||
colors: Vec<String>,
|
colors: Vec<String>,
|
||||||
#[arg(short, long, value_name = "OVERWRITE")]
|
#[arg(short, long, value_name = "OVERWRITE")]
|
||||||
overwrite: bool,
|
overwrite: bool,
|
||||||
|
#[arg(short, long, value_name = "REGISTER")]
|
||||||
|
register: bool,
|
||||||
|
#[arg(short, long, value_name = "CHANNEL")]
|
||||||
|
channel: Option<isize>,
|
||||||
|
#[arg(short, long, value_name = "ZSLICE")]
|
||||||
|
zslice: Option<String>,
|
||||||
|
#[arg(short, long, value_name = "TIME")]
|
||||||
|
time: Option<String>,
|
||||||
|
#[arg(short, long, value_name = "NO-SCALE-BRIGHTNESS")]
|
||||||
|
no_scaling: bool,
|
||||||
},
|
},
|
||||||
/// Download the BioFormats jar into the correct folder
|
/// Download the BioFormats jar into the correct folder
|
||||||
DownloadBioFormats {
|
DownloadBioFormats {
|
||||||
@@ -55,7 +67,43 @@ enum Commands {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn main() -> Result<()> {
|
#[cfg(feature = "movie")]
|
||||||
|
fn parse_slice(s: &str) -> Result<SliceInfoElem, Error> {
|
||||||
|
let mut t = s
|
||||||
|
.trim()
|
||||||
|
.replace("..", ":")
|
||||||
|
.split(":")
|
||||||
|
.map(|i| i.parse().ok())
|
||||||
|
.collect::<Vec<Option<isize>>>();
|
||||||
|
if t.len() > 3 {
|
||||||
|
return Err(Error::Parse(s.to_string()));
|
||||||
|
}
|
||||||
|
while t.len() < 3 {
|
||||||
|
t.push(None);
|
||||||
|
}
|
||||||
|
match t[..] {
|
||||||
|
[Some(start), None, None] => Ok(SliceInfoElem::Index(start)),
|
||||||
|
[Some(start), end, None] => Ok(SliceInfoElem::Slice {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
step: 1,
|
||||||
|
}),
|
||||||
|
[Some(start), end, Some(step)] => Ok(SliceInfoElem::Slice { start, end, step }),
|
||||||
|
[None, end, None] => Ok(SliceInfoElem::Slice {
|
||||||
|
start: 0,
|
||||||
|
end,
|
||||||
|
step: 1,
|
||||||
|
}),
|
||||||
|
[None, end, Some(step)] => Ok(SliceInfoElem::Slice {
|
||||||
|
start: 0,
|
||||||
|
end,
|
||||||
|
step,
|
||||||
|
}),
|
||||||
|
_ => Err(Error::Parse(s.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn main() -> Result<(), Error> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
match &cli.command {
|
match &cli.command {
|
||||||
Commands::Info { file } => {
|
Commands::Info { file } => {
|
||||||
@@ -87,6 +135,11 @@ pub(crate) fn main() -> Result<()> {
|
|||||||
scale,
|
scale,
|
||||||
colors,
|
colors,
|
||||||
overwrite,
|
overwrite,
|
||||||
|
register,
|
||||||
|
channel,
|
||||||
|
zslice,
|
||||||
|
time,
|
||||||
|
no_scaling,
|
||||||
} => {
|
} => {
|
||||||
let options = MovieOptions::new(
|
let options = MovieOptions::new(
|
||||||
*speed,
|
*speed,
|
||||||
@@ -94,11 +147,29 @@ pub(crate) fn main() -> Result<()> {
|
|||||||
*scale,
|
*scale,
|
||||||
colors.to_vec(),
|
colors.to_vec(),
|
||||||
*overwrite,
|
*overwrite,
|
||||||
|
*register,
|
||||||
|
*no_scaling,
|
||||||
)?;
|
)?;
|
||||||
for f in file {
|
for f in file {
|
||||||
let (path, series) = split_path_and_series(f)?;
|
let (path, series) = split_path_and_series(f)?;
|
||||||
let view = View::from_path(path, series.unwrap_or(0))?;
|
let view = View::from_path(path, series.unwrap_or(0))?;
|
||||||
view.save_as_movie(f.with_extension("mp4"), &options)?;
|
let mut s = [SliceInfoElem::Slice {
|
||||||
|
start: 0,
|
||||||
|
end: None,
|
||||||
|
step: 1,
|
||||||
|
}; 5];
|
||||||
|
if let Some(channel) = channel {
|
||||||
|
s[0] = SliceInfoElem::Index(*channel);
|
||||||
|
};
|
||||||
|
if let Some(zslice) = zslice {
|
||||||
|
s[1] = parse_slice(zslice)?;
|
||||||
|
}
|
||||||
|
if let Some(time) = time {
|
||||||
|
s[2] = parse_slice(time)?;
|
||||||
|
}
|
||||||
|
view.into_dyn()
|
||||||
|
.slice(s.as_slice())?
|
||||||
|
.save_as_movie(f.with_extension("mp4"), &options)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Commands::DownloadBioFormats { gpl_formats } => {
|
Commands::DownloadBioFormats { gpl_formats } => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use anyhow::{Result, anyhow};
|
use crate::error::Error;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ome_metadata::Ome;
|
use ome_metadata::Ome;
|
||||||
use ome_metadata::ome::{
|
use ome_metadata::ome::{
|
||||||
@@ -67,7 +67,7 @@ pub trait Metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// shape of the data along cztyx axes
|
/// shape of the data along cztyx axes
|
||||||
fn shape(&self) -> Result<(usize, usize, usize, usize, usize)> {
|
fn shape(&self) -> Result<(usize, usize, usize, usize, usize), Error> {
|
||||||
if let Some(pixels) = self.get_pixels() {
|
if let Some(pixels) = self.get_pixels() {
|
||||||
Ok((
|
Ok((
|
||||||
pixels.size_c as usize,
|
pixels.size_c as usize,
|
||||||
@@ -77,12 +77,12 @@ pub trait Metadata {
|
|||||||
pixels.size_x as usize,
|
pixels.size_x as usize,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("No image or pixels found"))
|
Err(Error::NoImageOrPixels)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// pixel size in nm
|
/// pixel size in nm
|
||||||
fn pixel_size(&self) -> Result<Option<f64>> {
|
fn pixel_size(&self) -> Result<Option<f64>, Error> {
|
||||||
if let Some(pixels) = self.get_pixels() {
|
if let Some(pixels) = self.get_pixels() {
|
||||||
match (pixels.physical_size_x, pixels.physical_size_y) {
|
match (pixels.physical_size_x, pixels.physical_size_y) {
|
||||||
(Some(x), Some(y)) => Ok(Some(
|
(Some(x), Some(y)) => Ok(Some(
|
||||||
@@ -114,7 +114,7 @@ pub trait Metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// distance between planes in z-stack in nm
|
/// distance between planes in z-stack in nm
|
||||||
fn delta_z(&self) -> Result<Option<f64>> {
|
fn delta_z(&self) -> Result<Option<f64>, Error> {
|
||||||
if let Some(pixels) = self.get_pixels() {
|
if let Some(pixels) = self.get_pixels() {
|
||||||
if let Some(z) = pixels.physical_size_z {
|
if let Some(z) = pixels.physical_size_z {
|
||||||
return Ok(Some(
|
return Ok(Some(
|
||||||
@@ -128,7 +128,7 @@ pub trait Metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// time interval in seconds for time-lapse images
|
/// time interval in seconds for time-lapse images
|
||||||
fn time_interval(&self) -> Result<Option<f64>> {
|
fn time_interval(&self) -> Result<Option<f64>, Error> {
|
||||||
if let Some(pixels) = self.get_pixels() {
|
if let Some(pixels) = self.get_pixels() {
|
||||||
if let Some(plane) = &pixels.plane {
|
if let Some(plane) = &pixels.plane {
|
||||||
if let Some(t) = plane.iter().map(|p| p.the_t).max() {
|
if let Some(t) = plane.iter().map(|p| p.the_t).max() {
|
||||||
@@ -157,7 +157,7 @@ pub trait Metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// exposure time for channel, z=0 and t=0
|
/// exposure time for channel, z=0 and t=0
|
||||||
fn exposure_time(&self, channel: usize) -> Result<Option<f64>> {
|
fn exposure_time(&self, channel: usize) -> Result<Option<f64>, Error> {
|
||||||
let c = channel as i32;
|
let c = channel as i32;
|
||||||
if let Some(pixels) = self.get_pixels() {
|
if let Some(pixels) = self.get_pixels() {
|
||||||
if let Some(plane) = &pixels.plane {
|
if let Some(plane) = &pixels.plane {
|
||||||
@@ -192,7 +192,7 @@ pub trait Metadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn laser_wavelengths(&self, channel: usize) -> Result<Option<f64>> {
|
fn laser_wavelengths(&self, channel: usize) -> Result<Option<f64>, Error> {
|
||||||
if let Some(pixels) = self.get_pixels() {
|
if let Some(pixels) = self.get_pixels() {
|
||||||
if let Some(channel) = pixels.channel.get(channel) {
|
if let Some(channel) = pixels.channel.get(channel) {
|
||||||
if let Some(w) = channel.excitation_wavelength {
|
if let Some(w) = channel.excitation_wavelength {
|
||||||
@@ -207,7 +207,7 @@ pub trait Metadata {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn laser_powers(&self, channel: usize) -> Result<Option<f64>> {
|
fn laser_powers(&self, channel: usize) -> Result<Option<f64>, Error> {
|
||||||
if let Some(pixels) = self.get_pixels() {
|
if let Some(pixels) = self.get_pixels() {
|
||||||
if let Some(channel) = pixels.channel.get(channel) {
|
if let Some(channel) = pixels.channel.get(channel) {
|
||||||
if let Some(ls) = &channel.light_source_settings {
|
if let Some(ls) = &channel.light_source_settings {
|
||||||
@@ -215,7 +215,7 @@ pub trait Metadata {
|
|||||||
return if (0. ..=1.).contains(&a) {
|
return if (0. ..=1.).contains(&a) {
|
||||||
Ok(Some(1f64 - (a as f64)))
|
Ok(Some(1f64 - (a as f64)))
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("Invalid attenuation value"))
|
Err(Error::InvalidAttenuation(a.to_string()))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,7 +273,7 @@ pub trait Metadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn summary(&self) -> Result<String> {
|
fn summary(&self) -> Result<String, Error> {
|
||||||
let size_c = if let Some(pixels) = self.get_pixels() {
|
let size_c = if let Some(pixels) = self.get_pixels() {
|
||||||
pixels.channel.len()
|
pixels.channel.len()
|
||||||
} else {
|
} else {
|
||||||
@@ -291,7 +291,7 @@ pub trait Metadata {
|
|||||||
}
|
}
|
||||||
let exposure_time = (0..size_c)
|
let exposure_time = (0..size_c)
|
||||||
.map(|c| self.exposure_time(c))
|
.map(|c| self.exposure_time(c))
|
||||||
.collect::<Result<Vec<_>>>()?
|
.collect::<Result<Vec<_>, Error>>()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@@ -333,7 +333,7 @@ pub trait Metadata {
|
|||||||
}
|
}
|
||||||
let laser_wavelengths = (0..size_c)
|
let laser_wavelengths = (0..size_c)
|
||||||
.map(|c| self.laser_wavelengths(c))
|
.map(|c| self.laser_wavelengths(c))
|
||||||
.collect::<Result<Vec<_>>>()?
|
.collect::<Result<Vec<_>, Error>>()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@@ -345,7 +345,7 @@ pub trait Metadata {
|
|||||||
}
|
}
|
||||||
let laser_powers = (0..size_c)
|
let laser_powers = (0..size_c)
|
||||||
.map(|c| self.laser_powers(c))
|
.map(|c| self.laser_powers(c))
|
||||||
.collect::<Result<Vec<_>>>()?
|
.collect::<Result<Vec<_>, Error>>()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|||||||
89
src/movie.rs
89
src/movie.rs
@@ -1,7 +1,8 @@
|
|||||||
use crate::axes::Axis;
|
use crate::axes::Axis;
|
||||||
use crate::colors::Color;
|
use crate::colors::Color;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::reader::PixelType;
|
||||||
use crate::view::View;
|
use crate::view::View;
|
||||||
use anyhow::{Result, anyhow};
|
|
||||||
use ffmpeg_sidecar::command::FfmpegCommand;
|
use ffmpeg_sidecar::command::FfmpegCommand;
|
||||||
use ffmpeg_sidecar::download::auto_download;
|
use ffmpeg_sidecar::download::auto_download;
|
||||||
use ffmpeg_sidecar::event::{FfmpegEvent, LogLevel};
|
use ffmpeg_sidecar::event::{FfmpegEvent, LogLevel};
|
||||||
@@ -18,6 +19,8 @@ pub struct MovieOptions {
|
|||||||
scale: f64,
|
scale: f64,
|
||||||
colors: Option<Vec<Vec<u8>>>,
|
colors: Option<Vec<Vec<u8>>>,
|
||||||
overwrite: bool,
|
overwrite: bool,
|
||||||
|
register: bool,
|
||||||
|
no_scaling: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MovieOptions {
|
impl Default for MovieOptions {
|
||||||
@@ -28,6 +31,8 @@ impl Default for MovieOptions {
|
|||||||
scale: 1.0,
|
scale: 1.0,
|
||||||
colors: None,
|
colors: None,
|
||||||
overwrite: false,
|
overwrite: false,
|
||||||
|
register: false,
|
||||||
|
no_scaling: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,14 +44,16 @@ impl MovieOptions {
|
|||||||
scale: f64,
|
scale: f64,
|
||||||
colors: Vec<String>,
|
colors: Vec<String>,
|
||||||
overwrite: bool,
|
overwrite: bool,
|
||||||
) -> Result<Self> {
|
register: bool,
|
||||||
|
no_scaling: bool,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
let colors = if colors.is_empty() {
|
let colors = if colors.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let colors = colors
|
let colors = colors
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| c.parse::<Color>())
|
.map(|c| c.parse::<Color>())
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>, Error>>()?;
|
||||||
Some(colors.into_iter().map(|c| c.to_rgb()).collect())
|
Some(colors.into_iter().map(|c| c.to_rgb()).collect())
|
||||||
};
|
};
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@@ -55,6 +62,8 @@ impl MovieOptions {
|
|||||||
scale,
|
scale,
|
||||||
colors,
|
colors,
|
||||||
overwrite,
|
overwrite,
|
||||||
|
register,
|
||||||
|
no_scaling,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,11 +79,11 @@ impl MovieOptions {
|
|||||||
self.scale = scale;
|
self.scale = scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_colors(&mut self, colors: &[String]) -> Result<()> {
|
pub fn set_colors(&mut self, colors: &[String]) -> Result<(), Error> {
|
||||||
let colors = colors
|
let colors = colors
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| c.parse::<Color>())
|
.map(|c| c.parse::<Color>())
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>, Error>>()?;
|
||||||
self.colors = Some(colors.into_iter().map(|c| c.to_rgb()).collect());
|
self.colors = Some(colors.into_iter().map(|c| c.to_rgb()).collect());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -84,7 +93,7 @@ impl MovieOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_ab(tyx: View<IxDyn>) -> Result<(f64, f64)> {
|
fn get_ab(tyx: View<IxDyn>) -> Result<(f64, f64), Error> {
|
||||||
let s = tyx
|
let s = tyx
|
||||||
.as_array::<f64>()?
|
.as_array::<f64>()?
|
||||||
.iter()
|
.iter()
|
||||||
@@ -107,14 +116,14 @@ fn get_ab(tyx: View<IxDyn>) -> Result<(f64, f64)> {
|
|||||||
b = s[n - 1];
|
b = s[n - 1];
|
||||||
}
|
}
|
||||||
if a == b {
|
if a == b {
|
||||||
a = 1.0;
|
a = 0.0;
|
||||||
b = 1.0;
|
b = 1.0;
|
||||||
}
|
}
|
||||||
Ok((a, b))
|
Ok((a, b))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cframe(frame: Array2<f64>, color: &[u8], a: f64, b: f64) -> Array3<f64> {
|
fn cframe(frame: Array2<f64>, color: &[u8], a: f64, b: f64) -> Array3<f64> {
|
||||||
let frame = (frame - a) / (b - a);
|
let frame = ((frame - a) / (b - a)).clamp(0.0, 1.0);
|
||||||
let color = color
|
let color = color
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&c| (c as f64) / 255.0)
|
.map(|&c| (c as f64) / 255.0)
|
||||||
@@ -124,23 +133,26 @@ fn cframe(frame: Array2<f64>, color: &[u8], a: f64, b: f64) -> Array3<f64> {
|
|||||||
.map(|&c| (c * &frame).to_owned())
|
.map(|&c| (c * &frame).to_owned())
|
||||||
.collect::<Vec<Array2<f64>>>();
|
.collect::<Vec<Array2<f64>>>();
|
||||||
let view = frame.iter().map(|c| c.view()).collect::<Vec<_>>();
|
let view = frame.iter().map(|c| c.view()).collect::<Vec<_>>();
|
||||||
stack(ndarray::Axis(0), &view).unwrap()
|
stack(ndarray::Axis(2), &view).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> View<D>
|
impl<D> View<D>
|
||||||
where
|
where
|
||||||
D: Dimension,
|
D: Dimension,
|
||||||
{
|
{
|
||||||
pub fn save_as_movie<P>(&self, path: P, options: &MovieOptions) -> Result<()>
|
pub fn save_as_movie<P>(&self, path: P, options: &MovieOptions) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
|
if options.register {
|
||||||
|
return Err(Error::NotImplemented("register".to_string()));
|
||||||
|
}
|
||||||
let path = path.as_ref().to_path_buf();
|
let path = path.as_ref().to_path_buf();
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
if options.overwrite {
|
if options.overwrite {
|
||||||
std::fs::remove_file(&path)?;
|
std::fs::remove_file(&path)?;
|
||||||
} else {
|
} else {
|
||||||
return Err(anyhow!("File {} already exists", path.display()));
|
return Err(Error::FileAlreadyExists(path.display().to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let view = self.max_proj(Axis::Z)?.reset_axes()?;
|
let view = self.max_proj(Axis::Z)?.reset_axes()?;
|
||||||
@@ -150,10 +162,10 @@ where
|
|||||||
let shape = view.shape();
|
let shape = view.shape();
|
||||||
let size_c = shape[0];
|
let size_c = shape[0];
|
||||||
let size_t = shape[2];
|
let size_t = shape[2];
|
||||||
let size_x = shape[3];
|
let size_y = shape[3];
|
||||||
let size_y = shape[4];
|
let size_x = shape[4];
|
||||||
let shape_x = 2 * (((size_x as f64 * scale) / 2.).round() as usize);
|
|
||||||
let shape_y = 2 * (((size_y as f64 * scale) / 2.).round() as usize);
|
let shape_y = 2 * (((size_y as f64 * scale) / 2.).round() as usize);
|
||||||
|
let shape_x = 2 * (((size_x as f64 * scale) / 2.).round() as usize);
|
||||||
|
|
||||||
while brightness.len() < size_c {
|
while brightness.len() < size_c {
|
||||||
brightness.push(1.0);
|
brightness.push(1.0);
|
||||||
@@ -167,15 +179,18 @@ where
|
|||||||
colors.push(vec![255, 255, 255]);
|
colors.push(vec![255, 255, 255]);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto_download()?;
|
if let Err(e) = auto_download() {
|
||||||
|
return Err(Error::Ffmpeg(e.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
let mut movie = FfmpegCommand::new()
|
let mut movie = FfmpegCommand::new()
|
||||||
.args([
|
.args([
|
||||||
"-f",
|
"-f",
|
||||||
"rawvideo",
|
"rawvideo",
|
||||||
"-pix_fmt",
|
"-pix_fmt",
|
||||||
"gray",
|
"rgb24",
|
||||||
"-s",
|
"-s",
|
||||||
&format!("{}x{}", size_x, size_y),
|
&format!("{size_x}x{size_y}"),
|
||||||
])
|
])
|
||||||
.input("-")
|
.input("-")
|
||||||
.args([
|
.args([
|
||||||
@@ -194,16 +209,33 @@ where
|
|||||||
.spawn()?;
|
.spawn()?;
|
||||||
let mut stdin = movie.take_stdin().unwrap();
|
let mut stdin = movie.take_stdin().unwrap();
|
||||||
|
|
||||||
let ab = (0..size_c)
|
let ab = if options.no_scaling {
|
||||||
|
vec![
|
||||||
|
match view.pixel_type {
|
||||||
|
PixelType::I8 => (i8::MIN as f64, i8::MAX as f64),
|
||||||
|
PixelType::U8 => (u8::MIN as f64, u8::MAX as f64),
|
||||||
|
PixelType::I16 => (i16::MIN as f64, i16::MAX as f64),
|
||||||
|
PixelType::U16 => (u16::MIN as f64, u16::MAX as f64),
|
||||||
|
PixelType::I32 => (i32::MIN as f64, i32::MAX as f64),
|
||||||
|
PixelType::U32 => (u32::MIN as f64, u32::MAX as f64),
|
||||||
|
PixelType::I64 => (i64::MIN as f64, i64::MAX as f64),
|
||||||
|
PixelType::U64 => (u64::MIN as f64, u64::MAX as f64),
|
||||||
|
_ => (0.0, 1.0),
|
||||||
|
};
|
||||||
|
view.size_c
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
(0..size_c)
|
||||||
.map(|c| match view.slice(s![c, .., .., .., ..]) {
|
.map(|c| match view.slice(s![c, .., .., .., ..]) {
|
||||||
Ok(slice) => get_ab(slice.into_dyn()),
|
Ok(slice) => get_ab(slice.into_dyn()),
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>, Error>>()?
|
||||||
|
};
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
for t in 0..size_t {
|
for t in 0..size_t {
|
||||||
let mut frame = Array3::<f64>::zeros((3, size_y, size_y));
|
let mut frame = Array3::<f64>::zeros((size_y, size_x, 3));
|
||||||
for c in 0..size_c {
|
for c in 0..size_c {
|
||||||
frame = frame
|
frame = frame
|
||||||
+ cframe(
|
+ cframe(
|
||||||
@@ -213,21 +245,16 @@ where
|
|||||||
ab[c].1 / brightness[c],
|
ab[c].1 / brightness[c],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let frame = frame.mapv(|i| {
|
let frame = (frame.clamp(0.0, 1.0) * 255.0).round().mapv(|i| i as u8);
|
||||||
if i < 0.0 {
|
|
||||||
0
|
|
||||||
} else if i > 1.0 {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
(255.0 * i).round() as u8
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let bytes: Vec<_> = frame.flatten().into_iter().collect();
|
let bytes: Vec<_> = frame.flatten().into_iter().collect();
|
||||||
stdin.write_all(&bytes).unwrap();
|
stdin.write_all(&bytes).unwrap();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
movie.iter()?.for_each(|e| match e {
|
movie
|
||||||
|
.iter()
|
||||||
|
.map_err(|e| Error::Ffmpeg(e.to_string()))?
|
||||||
|
.for_each(|e| match e {
|
||||||
FfmpegEvent::Log(LogLevel::Error, e) => println!("Error: {}", e),
|
FfmpegEvent::Log(LogLevel::Error, e) => println!("Error: {}", e),
|
||||||
FfmpegEvent::Progress(p) => println!("Progress: {} / 00:00:15", p.time),
|
FfmpegEvent::Progress(p) => println!("Progress: {} / 00:00:15", p.time),
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -242,7 +269,7 @@ mod tests {
|
|||||||
use crate::reader::Reader;
|
use crate::reader::Reader;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn movie() -> Result<()> {
|
fn movie() -> Result<(), Error> {
|
||||||
let file = "1xp53-01-AP1.czi";
|
let file = "1xp53-01-AP1.czi";
|
||||||
let path = std::env::current_dir()?
|
let path = std::env::current_dir()?
|
||||||
.join("tests")
|
.join("tests")
|
||||||
|
|||||||
189
src/py.rs
189
src/py.rs
@@ -1,15 +1,15 @@
|
|||||||
use crate::axes::Axis;
|
use crate::axes::Axis;
|
||||||
use crate::bioformats::download_bioformats;
|
use crate::bioformats::download_bioformats;
|
||||||
|
use crate::error::Error;
|
||||||
use crate::metadata::Metadata;
|
use crate::metadata::Metadata;
|
||||||
use crate::reader::{PixelType, Reader};
|
use crate::reader::{PixelType, Reader};
|
||||||
use crate::view::{Item, View};
|
use crate::view::{Item, View};
|
||||||
use anyhow::{Result, anyhow};
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ndarray::{Ix0, IxDyn, SliceInfoElem};
|
use ndarray::{Ix0, IxDyn, SliceInfoElem};
|
||||||
use numpy::IntoPyArray;
|
use numpy::IntoPyArray;
|
||||||
use ome_metadata::Ome;
|
use ome_metadata::Ome;
|
||||||
use pyo3::IntoPyObjectExt;
|
use pyo3::IntoPyObjectExt;
|
||||||
use pyo3::exceptions::PyNotImplementedError;
|
use pyo3::exceptions::{PyNotImplementedError, PyValueError};
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::{PyEllipsis, PyInt, PyList, PySlice, PySliceMethods, PyString, PyTuple};
|
use pyo3::types::{PyEllipsis, PyInt, PyList, PySlice, PySliceMethods, PyString, PyTuple};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -17,6 +17,12 @@ use serde_json::{from_str, to_string};
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
impl From<crate::error::Error> for PyErr {
|
||||||
|
fn from(err: crate::error::Error) -> PyErr {
|
||||||
|
PyErr::new::<PyValueError, _>(err.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[pyclass(module = "ndbioimage.ndbioimage_rs")]
|
#[pyclass(module = "ndbioimage.ndbioimage_rs")]
|
||||||
struct ViewConstructor;
|
struct ViewConstructor;
|
||||||
|
|
||||||
@@ -36,7 +42,9 @@ impl ViewConstructor {
|
|||||||
if let Ok(new) = from_str(&state) {
|
if let Ok(new) = from_str(&state) {
|
||||||
Ok(new)
|
Ok(new)
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("cannot parse state").into())
|
Err(PyErr::new::<PyValueError, _>(
|
||||||
|
"cannot parse state".to_string(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,11 +63,24 @@ impl PyView {
|
|||||||
/// 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)
|
||||||
#[new]
|
#[new]
|
||||||
#[pyo3(signature = (path, series = 0, dtype = "uint16", axes = "cztyx"))]
|
#[pyo3(signature = (path, series = 0, dtype = "uint16", axes = "cztyx"))]
|
||||||
fn new(path: Bound<'_, PyAny>, series: usize, dtype: &str, axes: &str) -> PyResult<Self> {
|
fn new<'py>(
|
||||||
|
py: Python<'py>,
|
||||||
|
path: Bound<'py, PyAny>,
|
||||||
|
series: usize,
|
||||||
|
dtype: &str,
|
||||||
|
axes: &str,
|
||||||
|
) -> PyResult<Self> {
|
||||||
if path.is_instance_of::<Self>() {
|
if path.is_instance_of::<Self>() {
|
||||||
Ok(path.downcast_into::<Self>()?.extract::<Self>()?)
|
Ok(path.cast_into::<Self>()?.extract::<Self>()?)
|
||||||
} else {
|
} else {
|
||||||
let mut path = PathBuf::from(path.downcast_into::<PyString>()?.extract::<String>()?);
|
let builtins = PyModule::import(py, "builtins")?;
|
||||||
|
let mut path = PathBuf::from(
|
||||||
|
builtins
|
||||||
|
.getattr("str")?
|
||||||
|
.call1((path,))?
|
||||||
|
.cast_into::<PyString>()?
|
||||||
|
.extract::<String>()?,
|
||||||
|
);
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
for file in path.read_dir()?.flatten() {
|
for file in path.read_dir()?.flatten() {
|
||||||
let p = file.path();
|
let p = file.path();
|
||||||
@@ -72,7 +93,7 @@ impl PyView {
|
|||||||
let axes = axes
|
let axes = axes
|
||||||
.chars()
|
.chars()
|
||||||
.map(|a| a.to_string().parse())
|
.map(|a| a.to_string().parse())
|
||||||
.collect::<Result<Vec<Axis>>>()?;
|
.collect::<Result<Vec<Axis>, Error>>()?;
|
||||||
let reader = Reader::new(&path, series)?;
|
let reader = Reader::new(&path, series)?;
|
||||||
let view = View::new_with_axes(Arc::new(reader), axes)?;
|
let view = View::new_with_axes(Arc::new(reader), axes)?;
|
||||||
let dtype = dtype.parse()?;
|
let dtype = dtype.parse()?;
|
||||||
@@ -187,9 +208,9 @@ impl PyView {
|
|||||||
n: Bound<'py, PyAny>,
|
n: Bound<'py, PyAny>,
|
||||||
) -> PyResult<Bound<'py, PyAny>> {
|
) -> PyResult<Bound<'py, PyAny>> {
|
||||||
let slice: Vec<_> = if n.is_instance_of::<PyTuple>() {
|
let slice: Vec<_> = if n.is_instance_of::<PyTuple>() {
|
||||||
n.downcast_into::<PyTuple>()?.into_iter().collect()
|
n.cast_into::<PyTuple>()?.into_iter().collect()
|
||||||
} else if n.is_instance_of::<PyList>() {
|
} else if n.is_instance_of::<PyList>() {
|
||||||
n.downcast_into::<PyList>()?.into_iter().collect()
|
n.cast_into::<PyList>()?.into_iter().collect()
|
||||||
} else {
|
} else {
|
||||||
vec![n]
|
vec![n]
|
||||||
};
|
};
|
||||||
@@ -198,11 +219,9 @@ impl PyView {
|
|||||||
let shape = self.view.shape();
|
let shape = self.view.shape();
|
||||||
for (i, (s, t)) in slice.iter().zip(shape.iter()).enumerate() {
|
for (i, (s, t)) in slice.iter().zip(shape.iter()).enumerate() {
|
||||||
if s.is_instance_of::<PyInt>() {
|
if s.is_instance_of::<PyInt>() {
|
||||||
new_slice.push(SliceInfoElem::Index(
|
new_slice.push(SliceInfoElem::Index(s.cast::<PyInt>()?.extract::<isize>()?));
|
||||||
s.downcast::<PyInt>()?.extract::<isize>()?,
|
|
||||||
));
|
|
||||||
} else if s.is_instance_of::<PySlice>() {
|
} else if s.is_instance_of::<PySlice>() {
|
||||||
let u = s.downcast::<PySlice>()?.indices(*t as isize)?;
|
let u = s.cast::<PySlice>()?.indices(*t as isize)?;
|
||||||
new_slice.push(SliceInfoElem::Slice {
|
new_slice.push(SliceInfoElem::Slice {
|
||||||
start: u.start,
|
start: u.start,
|
||||||
end: Some(u.stop),
|
end: Some(u.stop),
|
||||||
@@ -210,20 +229,24 @@ impl PyView {
|
|||||||
});
|
});
|
||||||
} else if s.is_instance_of::<PyEllipsis>() {
|
} else if s.is_instance_of::<PyEllipsis>() {
|
||||||
if ellipsis.is_some() {
|
if ellipsis.is_some() {
|
||||||
return Err(anyhow!("cannot have more than one ellipsis").into());
|
return Err(PyErr::new::<PyValueError, _>(
|
||||||
|
"cannot have more than one ellipsis".to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
let _ = ellipsis.insert(i);
|
let _ = ellipsis.insert(i);
|
||||||
} else {
|
} else {
|
||||||
return Err(anyhow!("cannot convert {:?} to slice", s).into());
|
return Err(PyErr::new::<PyValueError, _>(format!(
|
||||||
|
"cannot convert {:?} to slice",
|
||||||
|
s
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if new_slice.len() > shape.len() {
|
if new_slice.len() > shape.len() {
|
||||||
return Err(anyhow!(
|
return Err(PyErr::new::<PyValueError, _>(format!(
|
||||||
"got more indices ({}) than dimensions ({})",
|
"got more indices ({}) than dimensions ({})",
|
||||||
new_slice.len(),
|
new_slice.len(),
|
||||||
shape.len()
|
shape.len()
|
||||||
)
|
)));
|
||||||
.into());
|
|
||||||
}
|
}
|
||||||
while new_slice.len() < shape.len() {
|
while new_slice.len() < shape.len() {
|
||||||
if let Some(i) = ellipsis {
|
if let Some(i) = ellipsis {
|
||||||
@@ -331,15 +354,22 @@ impl PyView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn __contains__(&self, _item: Bound<'_, PyAny>) -> PyResult<bool> {
|
fn __contains__(&self, _item: Bound<PyAny>) -> PyResult<bool> {
|
||||||
Err(PyNotImplementedError::new_err("contains not implemented"))
|
Err(PyNotImplementedError::new_err("contains not implemented"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn __enter__<'p>(slf: PyRef<'p, Self>, _py: Python<'p>) -> PyResult<PyRef<'p, Self>> {
|
fn __enter__<'py>(slf: PyRef<'py, Self>) -> PyResult<PyRef<'py, Self>> {
|
||||||
Ok(slf)
|
Ok(slf)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn __exit__(&self) -> PyResult<()> {
|
#[allow(unused_variables)]
|
||||||
|
#[pyo3(signature = (exc_type=None, exc_val=None, exc_tb=None))]
|
||||||
|
fn __exit__(
|
||||||
|
&self,
|
||||||
|
exc_type: Option<Bound<PyAny>>,
|
||||||
|
exc_val: Option<Bound<PyAny>>,
|
||||||
|
exc_tb: Option<Bound<PyAny>>,
|
||||||
|
) -> PyResult<()> {
|
||||||
self.close()
|
self.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +377,9 @@ impl PyView {
|
|||||||
if let Ok(s) = to_string(self) {
|
if let Ok(s) = to_string(self) {
|
||||||
Ok((ViewConstructor, (s,)))
|
Ok((ViewConstructor, (s,)))
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("cannot get state").into())
|
Err(PyErr::new::<PyValueError, _>(
|
||||||
|
"cannot get state".to_string(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,28 +581,31 @@ impl PyView {
|
|||||||
|
|
||||||
/// find the position of an axis
|
/// find the position of an axis
|
||||||
#[pyo3(text_signature = "axis: str | int")]
|
#[pyo3(text_signature = "axis: str | int")]
|
||||||
fn get_ax(&self, axis: Bound<'_, PyAny>) -> PyResult<usize> {
|
fn get_ax(&self, axis: Bound<PyAny>) -> PyResult<usize> {
|
||||||
if axis.is_instance_of::<PyString>() {
|
if axis.is_instance_of::<PyString>() {
|
||||||
let axis = axis
|
let axis = axis
|
||||||
.downcast_into::<PyString>()?
|
.cast_into::<PyString>()?
|
||||||
.extract::<String>()?
|
.extract::<String>()?
|
||||||
.parse::<Axis>()?;
|
.parse::<Axis>()?;
|
||||||
Ok(self
|
self.view
|
||||||
.view
|
|
||||||
.axes()
|
.axes()
|
||||||
.iter()
|
.iter()
|
||||||
.position(|a| *a == axis)
|
.position(|a| *a == axis)
|
||||||
.ok_or_else(|| anyhow!("cannot find axis {:?}", axis))?)
|
.ok_or_else(|| {
|
||||||
|
PyErr::new::<PyValueError, _>(format!("cannot find axis {:?}", axis))
|
||||||
|
})
|
||||||
} else if axis.is_instance_of::<PyInt>() {
|
} else if axis.is_instance_of::<PyInt>() {
|
||||||
Ok(axis.downcast_into::<PyInt>()?.extract::<usize>()?)
|
Ok(axis.cast_into::<PyInt>()?.extract::<usize>()?)
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("cannot convert to axis").into())
|
Err(PyErr::new::<PyValueError, _>(
|
||||||
|
"cannot convert to axis".to_string(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// swap two axes
|
/// swap two axes
|
||||||
#[pyo3(text_signature = "ax0: str | int, ax1: str | int")]
|
#[pyo3(text_signature = "ax0: str | int, ax1: str | int")]
|
||||||
fn swap_axes(&self, ax0: Bound<'_, PyAny>, ax1: Bound<'_, PyAny>) -> PyResult<Self> {
|
fn swap_axes(&self, ax0: Bound<PyAny>, ax1: Bound<PyAny>) -> PyResult<Self> {
|
||||||
let ax0 = self.get_ax(ax0)?;
|
let ax0 = self.get_ax(ax0)?;
|
||||||
let ax1 = self.get_ax(ax1)?;
|
let ax1 = self.get_ax(ax1)?;
|
||||||
let view = self.view.swap_axes(ax0, ax1)?;
|
let view = self.view.swap_axes(ax0, ax1)?;
|
||||||
@@ -583,7 +618,7 @@ impl PyView {
|
|||||||
|
|
||||||
/// permute the order of the axes
|
/// permute the order of the axes
|
||||||
#[pyo3(signature = (axes = None), text_signature = "axes: list[str | int] = None")]
|
#[pyo3(signature = (axes = None), text_signature = "axes: list[str | int] = None")]
|
||||||
fn transpose(&self, axes: Option<Vec<Bound<'_, PyAny>>>) -> PyResult<Self> {
|
fn transpose(&self, axes: Option<Vec<Bound<PyAny>>>) -> PyResult<Self> {
|
||||||
let view = if let Some(axes) = axes {
|
let view = if let Some(axes) = axes {
|
||||||
let ax = axes
|
let ax = axes
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -651,12 +686,30 @@ impl PyView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// get the maximum overall or along a given axis
|
/// get the maximum overall or along a given axis
|
||||||
#[pyo3(signature = (axis = None), text_signature = "axis: str | int")]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[pyo3(signature = (axis=None, dtype=None, out=None, keepdims=false, initial=0, r#where=true), text_signature = "axis: str | int")]
|
||||||
fn max<'py>(
|
fn max<'py>(
|
||||||
&self,
|
&self,
|
||||||
py: Python<'py>,
|
py: Python<'py>,
|
||||||
axis: Option<Bound<'py, PyAny>>,
|
axis: Option<Bound<'py, PyAny>>,
|
||||||
|
dtype: Option<Bound<'py, PyAny>>,
|
||||||
|
out: Option<Bound<'py, PyAny>>,
|
||||||
|
keepdims: bool,
|
||||||
|
initial: Option<usize>,
|
||||||
|
r#where: bool,
|
||||||
) -> PyResult<Bound<'py, PyAny>> {
|
) -> PyResult<Bound<'py, PyAny>> {
|
||||||
|
if let Some(i) = initial
|
||||||
|
&& i != 0
|
||||||
|
{
|
||||||
|
Err(Error::NotImplemented(
|
||||||
|
"arguments beyond axis are not implemented".to_string(),
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
if dtype.is_some() || out.is_some() || keepdims || !r#where {
|
||||||
|
Err(Error::NotImplemented(
|
||||||
|
"arguments beyond axis are not implemented".to_string(),
|
||||||
|
))?;
|
||||||
|
}
|
||||||
if let Some(axis) = axis {
|
if let Some(axis) = axis {
|
||||||
PyView {
|
PyView {
|
||||||
dtype: self.dtype.clone(),
|
dtype: self.dtype.clone(),
|
||||||
@@ -684,12 +737,30 @@ impl PyView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// get the minimum overall or along a given axis
|
/// get the minimum overall or along a given axis
|
||||||
#[pyo3(signature = (axis = None), text_signature = "axis: str | int")]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[pyo3(signature = (axis=None, dtype=None, out=None, keepdims=false, initial=0, r#where=true), text_signature = "axis: str | int")]
|
||||||
fn min<'py>(
|
fn min<'py>(
|
||||||
&self,
|
&self,
|
||||||
py: Python<'py>,
|
py: Python<'py>,
|
||||||
axis: Option<Bound<'py, PyAny>>,
|
axis: Option<Bound<'py, PyAny>>,
|
||||||
|
dtype: Option<Bound<'py, PyAny>>,
|
||||||
|
out: Option<Bound<'py, PyAny>>,
|
||||||
|
keepdims: bool,
|
||||||
|
initial: Option<usize>,
|
||||||
|
r#where: bool,
|
||||||
) -> PyResult<Bound<'py, PyAny>> {
|
) -> PyResult<Bound<'py, PyAny>> {
|
||||||
|
if let Some(i) = initial
|
||||||
|
&& i != 0
|
||||||
|
{
|
||||||
|
Err(Error::NotImplemented(
|
||||||
|
"arguments beyond axis are not implemented".to_string(),
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
if dtype.is_some() || out.is_some() || keepdims || !r#where {
|
||||||
|
Err(Error::NotImplemented(
|
||||||
|
"arguments beyond axis are not implemented".to_string(),
|
||||||
|
))?;
|
||||||
|
}
|
||||||
if let Some(axis) = axis {
|
if let Some(axis) = axis {
|
||||||
PyView {
|
PyView {
|
||||||
dtype: self.dtype.clone(),
|
dtype: self.dtype.clone(),
|
||||||
@@ -716,13 +787,21 @@ impl PyView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get the mean overall or along a given axis
|
#[pyo3(signature = (axis=None, dtype=None, out=None, keepdims=false, *, r#where=true), text_signature = "axis: str | int")]
|
||||||
#[pyo3(signature = (axis = None), text_signature = "axis: str | int")]
|
|
||||||
fn mean<'py>(
|
fn mean<'py>(
|
||||||
&self,
|
&self,
|
||||||
py: Python<'py>,
|
py: Python<'py>,
|
||||||
axis: Option<Bound<'py, PyAny>>,
|
axis: Option<Bound<'py, PyAny>>,
|
||||||
|
dtype: Option<Bound<'py, PyAny>>,
|
||||||
|
out: Option<Bound<'py, PyAny>>,
|
||||||
|
keepdims: bool,
|
||||||
|
r#where: bool,
|
||||||
) -> PyResult<Bound<'py, PyAny>> {
|
) -> PyResult<Bound<'py, PyAny>> {
|
||||||
|
if dtype.is_some() || out.is_some() || keepdims || !r#where {
|
||||||
|
Err(Error::NotImplemented(
|
||||||
|
"arguments beyond axis are not implemented".to_string(),
|
||||||
|
))?;
|
||||||
|
}
|
||||||
if let Some(axis) = axis {
|
if let Some(axis) = axis {
|
||||||
let dtype = if let PixelType::F32 = self.dtype {
|
let dtype = if let PixelType::F32 = self.dtype {
|
||||||
PixelType::F32
|
PixelType::F32
|
||||||
@@ -744,12 +823,30 @@ impl PyView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// get the sum overall or along a given axis
|
/// get the sum overall or along a given axis
|
||||||
#[pyo3(signature = (axis = None), text_signature = "axis: str | int")]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[pyo3(signature = (axis=None, dtype=None, out=None, keepdims=false, initial=0, r#where=true), text_signature = "axis: str | int")]
|
||||||
fn sum<'py>(
|
fn sum<'py>(
|
||||||
&self,
|
&self,
|
||||||
py: Python<'py>,
|
py: Python<'py>,
|
||||||
axis: Option<Bound<'py, PyAny>>,
|
axis: Option<Bound<'py, PyAny>>,
|
||||||
|
dtype: Option<Bound<'py, PyAny>>,
|
||||||
|
out: Option<Bound<'py, PyAny>>,
|
||||||
|
keepdims: bool,
|
||||||
|
initial: Option<usize>,
|
||||||
|
r#where: bool,
|
||||||
) -> PyResult<Bound<'py, PyAny>> {
|
) -> PyResult<Bound<'py, PyAny>> {
|
||||||
|
if let Some(i) = initial
|
||||||
|
&& i != 0
|
||||||
|
{
|
||||||
|
Err(Error::NotImplemented(
|
||||||
|
"arguments beyond axis are not implemented".to_string(),
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
if dtype.is_some() || out.is_some() || keepdims || !r#where {
|
||||||
|
Err(Error::NotImplemented(
|
||||||
|
"arguments beyond axis are not implemented".to_string(),
|
||||||
|
))?;
|
||||||
|
}
|
||||||
let dtype = match self.dtype {
|
let dtype = match self.dtype {
|
||||||
PixelType::I8 => PixelType::I16,
|
PixelType::I8 => PixelType::I16,
|
||||||
PixelType::U8 => PixelType::U16,
|
PixelType::U8 => PixelType::U16,
|
||||||
@@ -864,29 +961,29 @@ impl PyView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn ndbioimage_file() -> anyhow::Result<PathBuf> {
|
pub(crate) fn ndbioimage_file() -> PathBuf {
|
||||||
let file = Python::with_gil(|py| {
|
let file = Python::attach(|py| {
|
||||||
py.import("ndbioimage")
|
py.import("ndbioimage")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.filename()
|
.filename()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string()
|
.to_string()
|
||||||
});
|
});
|
||||||
Ok(PathBuf::from(file))
|
PathBuf::from(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pymodule]
|
#[pyfunction]
|
||||||
#[pyo3(name = "ndbioimage_rs")]
|
|
||||||
fn ndbioimage_rs(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
||||||
m.add_class::<PyView>()?;
|
|
||||||
m.add_class::<ViewConstructor>()?;
|
|
||||||
|
|
||||||
#[pyfn(m)]
|
|
||||||
#[pyo3(name = "download_bioformats")]
|
#[pyo3(name = "download_bioformats")]
|
||||||
fn py_download_bioformats(gpl_formats: bool) -> PyResult<()> {
|
fn py_download_bioformats(gpl_formats: bool) -> PyResult<()> {
|
||||||
download_bioformats(gpl_formats)?;
|
download_bioformats(gpl_formats)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pymodule]
|
||||||
|
#[pyo3(name = "ndbioimage_rs")]
|
||||||
|
fn ndbioimage_rs(m: &Bound<PyModule>) -> PyResult<()> {
|
||||||
|
m.add_class::<PyView>()?;
|
||||||
|
m.add_class::<ViewConstructor>()?;
|
||||||
|
m.add_function(wrap_pyfunction!(py_download_bioformats, m)?)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::axes::Axis;
|
use crate::axes::Axis;
|
||||||
use crate::bioformats;
|
use crate::bioformats;
|
||||||
use crate::bioformats::{DebugTools, ImageReader, MetadataTools};
|
use crate::bioformats::{DebugTools, ImageReader, MetadataTools};
|
||||||
|
use crate::error::Error;
|
||||||
use crate::view::View;
|
use crate::view::View;
|
||||||
use anyhow::{Error, Result, anyhow};
|
|
||||||
use ndarray::{Array2, Ix5, s};
|
use ndarray::{Array2, Ix5, s};
|
||||||
use num::{FromPrimitive, Zero};
|
use num::{FromPrimitive, Zero};
|
||||||
use ome_metadata::Ome;
|
use ome_metadata::Ome;
|
||||||
@@ -15,16 +15,16 @@ use std::str::FromStr;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use thread_local::ThreadLocal;
|
use thread_local::ThreadLocal;
|
||||||
|
|
||||||
pub fn split_path_and_series<P>(path: P) -> Result<(PathBuf, Option<usize>)>
|
pub fn split_path_and_series<P>(path: P) -> Result<(PathBuf, Option<usize>), Error>
|
||||||
where
|
where
|
||||||
P: Into<PathBuf>,
|
P: Into<PathBuf>,
|
||||||
{
|
{
|
||||||
let path = path.into();
|
let path = path.into();
|
||||||
let file_name = path
|
let file_name = path
|
||||||
.file_name()
|
.file_name()
|
||||||
.ok_or_else(|| anyhow!("No file name"))?
|
.ok_or(Error::InvalidFileName)?
|
||||||
.to_str()
|
.to_str()
|
||||||
.ok_or_else(|| anyhow!("No file name"))?;
|
.ok_or(Error::InvalidFileName)?;
|
||||||
if file_name.to_lowercase().starts_with("pos") {
|
if file_name.to_lowercase().starts_with("pos") {
|
||||||
if let Some(series) = file_name.get(3..) {
|
if let Some(series) = file_name.get(3..) {
|
||||||
if let Ok(series) = series.parse::<usize>() {
|
if let Ok(series) = series.parse::<usize>() {
|
||||||
@@ -72,7 +72,7 @@ impl TryFrom<i32> for PixelType {
|
|||||||
10 => Ok(PixelType::I128),
|
10 => Ok(PixelType::I128),
|
||||||
11 => Ok(PixelType::U128),
|
11 => Ok(PixelType::U128),
|
||||||
12 => Ok(PixelType::F128),
|
12 => Ok(PixelType::F128),
|
||||||
_ => Err(anyhow::anyhow!("Unknown pixel type {}", value)),
|
_ => Err(Error::UnknownPixelType(value.to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,7 @@ impl FromStr for PixelType {
|
|||||||
"int128" | "i128" => Ok(PixelType::I128),
|
"int128" | "i128" => Ok(PixelType::I128),
|
||||||
"uint128" | "u128" => Ok(PixelType::U128),
|
"uint128" | "u128" => Ok(PixelType::U128),
|
||||||
"extended" | "f128" => Ok(PixelType::F128),
|
"extended" | "f128" => Ok(PixelType::F128),
|
||||||
_ => Err(anyhow::anyhow!("Unknown pixel type {}", s)),
|
_ => Err(Error::UnknownPixelType(s.to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,73 +163,73 @@ where
|
|||||||
let arr = match self {
|
let arr = match self {
|
||||||
Frame::I8(v) => v.mapv_into_any(|x| {
|
Frame::I8(v) => v.mapv_into_any(|x| {
|
||||||
T::from_i8(x).unwrap_or_else(|| {
|
T::from_i8(x).unwrap_or_else(|| {
|
||||||
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
|
err = Err(Error::Cast(x.to_string(), type_name::<T>().to_string()));
|
||||||
T::zero()
|
T::zero()
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
Frame::U8(v) => v.mapv_into_any(|x| {
|
Frame::U8(v) => v.mapv_into_any(|x| {
|
||||||
T::from_u8(x).unwrap_or_else(|| {
|
T::from_u8(x).unwrap_or_else(|| {
|
||||||
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
|
err = Err(Error::Cast(x.to_string(), type_name::<T>().to_string()));
|
||||||
T::zero()
|
T::zero()
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
Frame::I16(v) => v.mapv_into_any(|x| {
|
Frame::I16(v) => v.mapv_into_any(|x| {
|
||||||
T::from_i16(x).unwrap_or_else(|| {
|
T::from_i16(x).unwrap_or_else(|| {
|
||||||
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
|
err = Err(Error::Cast(x.to_string(), type_name::<T>().to_string()));
|
||||||
T::zero()
|
T::zero()
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
Frame::U16(v) => v.mapv_into_any(|x| {
|
Frame::U16(v) => v.mapv_into_any(|x| {
|
||||||
T::from_u16(x).unwrap_or_else(|| {
|
T::from_u16(x).unwrap_or_else(|| {
|
||||||
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
|
err = Err(Error::Cast(x.to_string(), type_name::<T>().to_string()));
|
||||||
T::zero()
|
T::zero()
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
Frame::I32(v) => v.mapv_into_any(|x| {
|
Frame::I32(v) => v.mapv_into_any(|x| {
|
||||||
T::from_i32(x).unwrap_or_else(|| {
|
T::from_i32(x).unwrap_or_else(|| {
|
||||||
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
|
err = Err(Error::Cast(x.to_string(), type_name::<T>().to_string()));
|
||||||
T::zero()
|
T::zero()
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
Frame::U32(v) => v.mapv_into_any(|x| {
|
Frame::U32(v) => v.mapv_into_any(|x| {
|
||||||
T::from_u32(x).unwrap_or_else(|| {
|
T::from_u32(x).unwrap_or_else(|| {
|
||||||
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
|
err = Err(Error::Cast(x.to_string(), type_name::<T>().to_string()));
|
||||||
T::zero()
|
T::zero()
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
Frame::F32(v) => v.mapv_into_any(|x| {
|
Frame::F32(v) => v.mapv_into_any(|x| {
|
||||||
T::from_f32(x).unwrap_or_else(|| {
|
T::from_f32(x).unwrap_or_else(|| {
|
||||||
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
|
err = Err(Error::Cast(x.to_string(), type_name::<T>().to_string()));
|
||||||
T::zero()
|
T::zero()
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
Frame::F64(v) | Frame::F128(v) => v.mapv_into_any(|x| {
|
Frame::F64(v) | Frame::F128(v) => v.mapv_into_any(|x| {
|
||||||
T::from_f64(x).unwrap_or_else(|| {
|
T::from_f64(x).unwrap_or_else(|| {
|
||||||
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
|
err = Err(Error::Cast(x.to_string(), type_name::<T>().to_string()));
|
||||||
T::zero()
|
T::zero()
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
Frame::I64(v) => v.mapv_into_any(|x| {
|
Frame::I64(v) => v.mapv_into_any(|x| {
|
||||||
T::from_i64(x).unwrap_or_else(|| {
|
T::from_i64(x).unwrap_or_else(|| {
|
||||||
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
|
err = Err(Error::Cast(x.to_string(), type_name::<T>().to_string()));
|
||||||
T::zero()
|
T::zero()
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
Frame::U64(v) => v.mapv_into_any(|x| {
|
Frame::U64(v) => v.mapv_into_any(|x| {
|
||||||
T::from_u64(x).unwrap_or_else(|| {
|
T::from_u64(x).unwrap_or_else(|| {
|
||||||
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
|
err = Err(Error::Cast(x.to_string(), type_name::<T>().to_string()));
|
||||||
T::zero()
|
T::zero()
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
Frame::I128(v) => v.mapv_into_any(|x| {
|
Frame::I128(v) => v.mapv_into_any(|x| {
|
||||||
T::from_i128(x).unwrap_or_else(|| {
|
T::from_i128(x).unwrap_or_else(|| {
|
||||||
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
|
err = Err(Error::Cast(x.to_string(), type_name::<T>().to_string()));
|
||||||
T::zero()
|
T::zero()
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
Frame::U128(v) => v.mapv_into_any(|x| {
|
Frame::U128(v) => v.mapv_into_any(|x| {
|
||||||
T::from_u128(x).unwrap_or_else(|| {
|
T::from_u128(x).unwrap_or_else(|| {
|
||||||
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
|
err = Err(Error::Cast(x.to_string(), type_name::<T>().to_string()));
|
||||||
T::zero()
|
T::zero()
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
@@ -305,7 +305,7 @@ impl Debug for Reader {
|
|||||||
|
|
||||||
impl Reader {
|
impl Reader {
|
||||||
/// Create a new reader for the image file at a path, and open series #.
|
/// Create a new reader for the image file at a path, and open series #.
|
||||||
pub fn new<P>(path: P, series: usize) -> Result<Self>
|
pub fn new<P>(path: P, series: usize) -> Result<Self, Error>
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
@@ -333,7 +333,7 @@ impl Reader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get ome metadata as ome structure
|
/// Get ome metadata as ome structure
|
||||||
pub fn get_ome(&self) -> Result<Ome> {
|
pub fn get_ome(&self) -> Result<Ome, Error> {
|
||||||
let mut ome = self.ome_xml()?.parse::<Ome>()?;
|
let mut ome = self.ome_xml()?.parse::<Ome>()?;
|
||||||
if let Some(image) = ome.image.as_ref() {
|
if let Some(image) = ome.image.as_ref() {
|
||||||
if image.len() > 1 {
|
if image.len() > 1 {
|
||||||
@@ -344,11 +344,11 @@ impl Reader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get ome metadata as xml string
|
/// Get ome metadata as xml string
|
||||||
pub fn get_ome_xml(&self) -> Result<String> {
|
pub fn get_ome_xml(&self) -> Result<String, Error> {
|
||||||
self.ome_xml()
|
self.ome_xml()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinterleave(&self, bytes: Vec<u8>, channel: usize) -> Result<Vec<u8>> {
|
fn deinterleave(&self, bytes: Vec<u8>, channel: usize) -> Result<Vec<u8>, Error> {
|
||||||
let chunk_size = match self.pixel_type {
|
let chunk_size = match self.pixel_type {
|
||||||
PixelType::I8 => 1,
|
PixelType::I8 => 1,
|
||||||
PixelType::U8 => 1,
|
PixelType::U8 => 1,
|
||||||
@@ -373,7 +373,8 @@ impl Reader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve fame at channel c, slize z and time t.
|
/// Retrieve fame at channel c, slize z and time t.
|
||||||
pub fn get_frame(&self, c: usize, z: usize, t: usize) -> Result<Frame> {
|
#[allow(clippy::if_same_then_else)]
|
||||||
|
pub fn get_frame(&self, c: usize, z: usize, t: usize) -> Result<Frame, Error> {
|
||||||
let bytes = if self.is_rgb()? && self.is_interleaved()? {
|
let bytes = if self.is_rgb()? && self.is_interleaved()? {
|
||||||
let index = self.get_index(z as i32, 0, t as i32)?;
|
let index = self.get_index(z as i32, 0, t as i32)?;
|
||||||
self.deinterleave(self.open_bytes(index)?, c)?
|
self.deinterleave(self.open_bytes(index)?, c)?
|
||||||
@@ -382,7 +383,7 @@ impl Reader {
|
|||||||
let index = channel_separator.get_index(z as i32, c as i32, t as i32)?;
|
let index = channel_separator.get_index(z as i32, c as i32, t as i32)?;
|
||||||
channel_separator.open_bytes(index)?
|
channel_separator.open_bytes(index)?
|
||||||
} else if self.is_indexed()? {
|
} else if self.is_indexed()? {
|
||||||
let index = self.get_index(z as i32, 0, t as i32)?;
|
let index = self.get_index(z as i32, c as i32, t as i32)?;
|
||||||
self.open_bytes(index)?
|
self.open_bytes(index)?
|
||||||
// TODO: apply LUT
|
// TODO: apply LUT
|
||||||
// let _bytes_lut = match self.pixel_type {
|
// let _bytes_lut = match self.pixel_type {
|
||||||
@@ -401,7 +402,7 @@ impl Reader {
|
|||||||
self.bytes_to_frame(bytes)
|
self.bytes_to_frame(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bytes_to_frame(&self, bytes: Vec<u8>) -> Result<Frame> {
|
fn bytes_to_frame(&self, bytes: Vec<u8>) -> Result<Frame, Error> {
|
||||||
macro_rules! get_frame {
|
macro_rules! get_frame {
|
||||||
($t:tt, <$n:expr) => {
|
($t:tt, <$n:expr) => {
|
||||||
Ok(Frame::from(Array2::from_shape_vec(
|
Ok(Frame::from(Array2::from_shape_vec(
|
||||||
|
|||||||
50
src/stats.rs
50
src/stats.rs
@@ -1,14 +1,14 @@
|
|||||||
use anyhow::{Result, anyhow};
|
use crate::error::Error;
|
||||||
use ndarray::{Array, ArrayD, ArrayView, Axis, Dimension, RemoveAxis};
|
use ndarray::{Array, ArrayD, ArrayView, Axis, Dimension, RemoveAxis};
|
||||||
|
|
||||||
/// a trait to define the min, max, sum and mean operations along an axis
|
/// a trait to define the min, max, sum and mean operations along an axis
|
||||||
pub trait MinMax {
|
pub trait MinMax {
|
||||||
type Output;
|
type Output;
|
||||||
|
|
||||||
fn max(self, axis: usize) -> Result<Self::Output>;
|
fn max(self, axis: usize) -> Result<Self::Output, Error>;
|
||||||
fn min(self, axis: usize) -> Result<Self::Output>;
|
fn min(self, axis: usize) -> Result<Self::Output, Error>;
|
||||||
fn sum(self, axis: usize) -> Result<Self::Output>;
|
fn sum(self, axis: usize) -> Result<Self::Output, Error>;
|
||||||
fn mean(self, axis: usize) -> Result<Self::Output>;
|
fn mean(self, axis: usize) -> Result<Self::Output, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_frame_stats_float_view {
|
macro_rules! impl_frame_stats_float_view {
|
||||||
@@ -20,7 +20,7 @@ macro_rules! impl_frame_stats_float_view {
|
|||||||
{
|
{
|
||||||
type Output = Array<$t, D::Smaller>;
|
type Output = Array<$t, D::Smaller>;
|
||||||
|
|
||||||
fn max(self, axis: usize) -> Result<Self::Output> {
|
fn max(self, axis: usize) -> Result<Self::Output, Error> {
|
||||||
let a: Vec<_> = self
|
let a: Vec<_> = self
|
||||||
.lanes(Axis(axis))
|
.lanes(Axis(axis))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -34,7 +34,7 @@ macro_rules! impl_frame_stats_float_view {
|
|||||||
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
|
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn min(self, axis: usize) -> Result<Self::Output> {
|
fn min(self, axis: usize) -> Result<Self::Output, Error> {
|
||||||
let a: Vec<_> = self
|
let a: Vec<_> = self
|
||||||
.lanes(Axis(axis))
|
.lanes(Axis(axis))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -48,12 +48,12 @@ macro_rules! impl_frame_stats_float_view {
|
|||||||
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
|
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sum(self, axis: usize) -> Result<Self::Output> {
|
fn sum(self, axis: usize) -> Result<Self::Output, Error> {
|
||||||
Ok(self.sum_axis(Axis(axis)))
|
Ok(self.sum_axis(Axis(axis)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mean(self, axis: usize) -> Result<Self::Output> {
|
fn mean(self, axis: usize) -> Result<Self::Output, Error> {
|
||||||
self.mean_axis(Axis(axis)).ok_or_else(|| anyhow!("no mean"))
|
self.mean_axis(Axis(axis)).ok_or(Error::NoMean)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
@@ -69,7 +69,7 @@ macro_rules! impl_frame_stats_int_view {
|
|||||||
{
|
{
|
||||||
type Output = Array<$t, D::Smaller>;
|
type Output = Array<$t, D::Smaller>;
|
||||||
|
|
||||||
fn max(self, axis: usize) -> Result<Self::Output> {
|
fn max(self, axis: usize) -> Result<Self::Output, Error> {
|
||||||
let a: Vec<_> = self
|
let a: Vec<_> = self
|
||||||
.lanes(Axis(axis))
|
.lanes(Axis(axis))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -80,7 +80,7 @@ macro_rules! impl_frame_stats_int_view {
|
|||||||
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
|
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn min(self, axis: usize) -> Result<Self::Output> {
|
fn min(self, axis: usize) -> Result<Self::Output, Error> {
|
||||||
let a: Vec<_> = self
|
let a: Vec<_> = self
|
||||||
.lanes(Axis(axis))
|
.lanes(Axis(axis))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -91,12 +91,12 @@ macro_rules! impl_frame_stats_int_view {
|
|||||||
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
|
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sum(self, axis: usize) -> Result<Self::Output> {
|
fn sum(self, axis: usize) -> Result<Self::Output, Error> {
|
||||||
Ok(self.sum_axis(Axis(axis)))
|
Ok(self.sum_axis(Axis(axis)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mean(self, axis: usize) -> Result<Self::Output> {
|
fn mean(self, axis: usize) -> Result<Self::Output, Error> {
|
||||||
self.mean_axis(Axis(axis)).ok_or_else(|| anyhow!("no mean"))
|
self.mean_axis(Axis(axis)).ok_or(Error::NoMean)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
@@ -112,7 +112,7 @@ macro_rules! impl_frame_stats_float {
|
|||||||
{
|
{
|
||||||
type Output = Array<$t, D::Smaller>;
|
type Output = Array<$t, D::Smaller>;
|
||||||
|
|
||||||
fn max(self, axis: usize) -> Result<Self::Output> {
|
fn max(self, axis: usize) -> Result<Self::Output, Error> {
|
||||||
let a: Vec<_> = self
|
let a: Vec<_> = self
|
||||||
.lanes(Axis(axis))
|
.lanes(Axis(axis))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -126,7 +126,7 @@ macro_rules! impl_frame_stats_float {
|
|||||||
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
|
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn min(self, axis: usize) -> Result<Self::Output> {
|
fn min(self, axis: usize) -> Result<Self::Output, Error> {
|
||||||
let a: Vec<_> = self
|
let a: Vec<_> = self
|
||||||
.lanes(Axis(axis))
|
.lanes(Axis(axis))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -140,12 +140,12 @@ macro_rules! impl_frame_stats_float {
|
|||||||
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
|
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sum(self, axis: usize) -> Result<Self::Output> {
|
fn sum(self, axis: usize) -> Result<Self::Output, Error> {
|
||||||
Ok(self.sum_axis(Axis(axis)))
|
Ok(self.sum_axis(Axis(axis)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mean(self, axis: usize) -> Result<Self::Output> {
|
fn mean(self, axis: usize) -> Result<Self::Output, Error> {
|
||||||
self.mean_axis(Axis(axis)).ok_or_else(|| anyhow!("no mean"))
|
self.mean_axis(Axis(axis)).ok_or(Error::NoMean)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
@@ -161,7 +161,7 @@ macro_rules! impl_frame_stats_int {
|
|||||||
{
|
{
|
||||||
type Output = Array<$t, D::Smaller>;
|
type Output = Array<$t, D::Smaller>;
|
||||||
|
|
||||||
fn max(self, axis: usize) -> Result<Self::Output> {
|
fn max(self, axis: usize) -> Result<Self::Output, Error> {
|
||||||
let a: Vec<_> = self
|
let a: Vec<_> = self
|
||||||
.lanes(Axis(axis))
|
.lanes(Axis(axis))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -172,7 +172,7 @@ macro_rules! impl_frame_stats_int {
|
|||||||
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
|
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn min(self, axis: usize) -> Result<Self::Output> {
|
fn min(self, axis: usize) -> Result<Self::Output, Error> {
|
||||||
let a: Vec<_> = self
|
let a: Vec<_> = self
|
||||||
.lanes(Axis(axis))
|
.lanes(Axis(axis))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -183,12 +183,12 @@ macro_rules! impl_frame_stats_int {
|
|||||||
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
|
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sum(self, axis: usize) -> Result<Self::Output> {
|
fn sum(self, axis: usize) -> Result<Self::Output, Error> {
|
||||||
Ok(self.sum_axis(Axis(axis)))
|
Ok(self.sum_axis(Axis(axis)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mean(self, axis: usize) -> Result<Self::Output> {
|
fn mean(self, axis: usize) -> Result<Self::Output, Error> {
|
||||||
self.mean_axis(Axis(axis)).ok_or_else(|| anyhow!("no mean"))
|
self.mean_axis(Axis(axis)).ok_or(Error::NoMean)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
|
|||||||
24
src/tiff.rs
24
src/tiff.rs
@@ -1,9 +1,9 @@
|
|||||||
use crate::colors::Color;
|
use crate::colors::Color;
|
||||||
|
use crate::error::Error;
|
||||||
use crate::metadata::Metadata;
|
use crate::metadata::Metadata;
|
||||||
use crate::reader::PixelType;
|
use crate::reader::PixelType;
|
||||||
use crate::stats::MinMax;
|
use crate::stats::MinMax;
|
||||||
use crate::view::{Number, View};
|
use crate::view::{Number, View};
|
||||||
use anyhow::{Result, anyhow};
|
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use itertools::iproduct;
|
use itertools::iproduct;
|
||||||
use ndarray::{Array0, Array1, Array2, ArrayD, Dimension};
|
use ndarray::{Array0, Array1, Array2, ArrayD, Dimension};
|
||||||
@@ -37,7 +37,7 @@ impl TiffOptions {
|
|||||||
compression: Option<Compression>,
|
compression: Option<Compression>,
|
||||||
colors: Vec<String>,
|
colors: Vec<String>,
|
||||||
overwrite: bool,
|
overwrite: bool,
|
||||||
) -> Result<Self> {
|
) -> Result<Self, Error> {
|
||||||
let mut options = Self {
|
let mut options = Self {
|
||||||
bar: None,
|
bar: None,
|
||||||
compression: compression.unwrap_or(Compression::Zstd(10)),
|
compression: compression.unwrap_or(Compression::Zstd(10)),
|
||||||
@@ -54,7 +54,7 @@ impl TiffOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// show a progress bar while saving tiff
|
/// show a progress bar while saving tiff
|
||||||
pub fn enable_bar(&mut self) -> Result<()> {
|
pub fn enable_bar(&mut self) -> Result<(), Error> {
|
||||||
self.bar = Some(ProgressStyle::with_template(
|
self.bar = Some(ProgressStyle::with_template(
|
||||||
"{spinner:.green} [{elapsed_precise}, {percent}%] [{wide_bar:.green/lime}] {pos:>7}/{len:7} ({eta_precise}, {per_sec:<5})",
|
"{spinner:.green} [{elapsed_precise}, {percent}%] [{wide_bar:.green/lime}] {pos:>7}/{len:7} ({eta_precise}, {per_sec:<5})",
|
||||||
)?.progress_chars("▰▱▱"));
|
)?.progress_chars("▰▱▱"));
|
||||||
@@ -81,11 +81,11 @@ impl TiffOptions {
|
|||||||
self.compression = Compression::Deflate
|
self.compression = Compression::Deflate
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_colors(&mut self, colors: &[String]) -> Result<()> {
|
pub fn set_colors(&mut self, colors: &[String]) -> Result<(), Error> {
|
||||||
let colors = colors
|
let colors = colors
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| c.parse::<Color>())
|
.map(|c| c.parse::<Color>())
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>, Error>>()?;
|
||||||
self.colors = Some(colors.into_iter().map(|c| c.to_rgb()).collect());
|
self.colors = Some(colors.into_iter().map(|c| c.to_rgb()).collect());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ where
|
|||||||
D: Dimension,
|
D: Dimension,
|
||||||
{
|
{
|
||||||
/// save as tiff with a certain type
|
/// save as tiff with a certain type
|
||||||
pub fn save_as_tiff_with_type<T, P>(&self, path: P, options: &TiffOptions) -> Result<()>
|
pub fn save_as_tiff_with_type<T, P>(&self, path: P, options: &TiffOptions) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
T: Bytes + Number + Send + Sync,
|
T: Bytes + Number + Send + Sync,
|
||||||
@@ -113,7 +113,7 @@ where
|
|||||||
if options.overwrite {
|
if options.overwrite {
|
||||||
std::fs::remove_file(&path)?;
|
std::fs::remove_file(&path)?;
|
||||||
} else {
|
} else {
|
||||||
return Err(anyhow!("File {} already exists", path.display()));
|
return Err(Error::FileAlreadyExists(path.display().to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let size_c = self.size_c();
|
let size_c = self.size_c();
|
||||||
@@ -145,10 +145,10 @@ where
|
|||||||
bar.inc(1);
|
bar.inc(1);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow::anyhow!("tiff is locked"))
|
Err(Error::TiffLock)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Result<()>>()?;
|
.collect::<Result<(), Error>>()?;
|
||||||
bar.finish();
|
bar.finish();
|
||||||
} else {
|
} else {
|
||||||
iproduct!(0..size_c, 0..size_z, 0..size_t)
|
iproduct!(0..size_c, 0..size_z, 0..size_t)
|
||||||
@@ -159,16 +159,16 @@ where
|
|||||||
tiff.save(&self.get_frame::<T, _>(c, z, t)?, c, z, t)?;
|
tiff.save(&self.get_frame::<T, _>(c, z, t)?, c, z, t)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow::anyhow!("tiff is locked"))
|
Err(Error::TiffLock)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Result<()>>()?;
|
.collect::<Result<(), Error>>()?;
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// save as tiff with whatever pixel type the view has
|
/// save as tiff with whatever pixel type the view has
|
||||||
pub fn save_as_tiff<P>(&self, path: P, options: &TiffOptions) -> Result<()>
|
pub fn save_as_tiff<P>(&self, path: P, options: &TiffOptions) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
|
|||||||
114
src/view.rs
114
src/view.rs
@@ -1,8 +1,8 @@
|
|||||||
use crate::axes::{Ax, Axis, Operation, Slice, SliceInfoElemDef, slice_info};
|
use crate::axes::{Ax, Axis, Operation, Slice, SliceInfoElemDef, slice_info};
|
||||||
|
use crate::error::Error;
|
||||||
use crate::metadata::Metadata;
|
use crate::metadata::Metadata;
|
||||||
use crate::reader::Reader;
|
use crate::reader::Reader;
|
||||||
use crate::stats::MinMax;
|
use crate::stats::MinMax;
|
||||||
use anyhow::{Error, Result, anyhow};
|
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use itertools::{Itertools, iproduct};
|
use itertools::{Itertools, iproduct};
|
||||||
use ndarray::{
|
use ndarray::{
|
||||||
@@ -22,27 +22,27 @@ use std::ops::{AddAssign, Deref, Div};
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
fn idx_bnd(idx: isize, bnd: isize) -> Result<isize> {
|
fn idx_bnd(idx: isize, bnd: isize) -> Result<isize, Error> {
|
||||||
if idx < -bnd {
|
if idx < -bnd {
|
||||||
Err(anyhow!("Index {} out of bounds {}", idx, bnd))
|
Err(Error::OutOfBounds(idx, bnd))
|
||||||
} else if idx < 0 {
|
} else if idx < 0 {
|
||||||
Ok(bnd - idx)
|
Ok(bnd - idx)
|
||||||
} else if idx < bnd {
|
} else if idx < bnd {
|
||||||
Ok(idx)
|
Ok(idx)
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("Index {} out of bounds {}", idx, bnd))
|
Err(Error::OutOfBounds(idx, bnd))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn slc_bnd(idx: isize, bnd: isize) -> Result<isize> {
|
fn slc_bnd(idx: isize, bnd: isize) -> Result<isize, Error> {
|
||||||
if idx < -bnd {
|
if idx < -bnd {
|
||||||
Err(anyhow!("Index {} out of bounds {}", idx, bnd))
|
Err(Error::OutOfBounds(idx, bnd))
|
||||||
} else if idx < 0 {
|
} else if idx < 0 {
|
||||||
Ok(bnd - idx)
|
Ok(bnd - idx)
|
||||||
} else if idx <= bnd {
|
} else if idx <= bnd {
|
||||||
Ok(idx)
|
Ok(idx)
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("Index {} out of bounds {}", idx, bnd))
|
Err(Error::OutOfBounds(idx, bnd))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) fn new_with_axes(reader: Arc<Reader>, axes: Vec<Axis>) -> Result<Self> {
|
pub(crate) fn new_with_axes(reader: Arc<Reader>, axes: Vec<Axis>) -> Result<Self, Error> {
|
||||||
let mut slice = Vec::new();
|
let mut slice = Vec::new();
|
||||||
for axis in axes.iter() {
|
for axis in axes.iter() {
|
||||||
match axis {
|
match axis {
|
||||||
@@ -134,11 +134,7 @@ impl<D: Dimension> View<D> {
|
|||||||
Axis::New => 1,
|
Axis::New => 1,
|
||||||
};
|
};
|
||||||
if size > 1 {
|
if size > 1 {
|
||||||
return Err(anyhow!(
|
return Err(Error::OutOfBoundsAxis(format!("{:?}", axis), size));
|
||||||
"Axis {:?} has length {}, but was not included",
|
|
||||||
axis,
|
|
||||||
size
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
slice.push(SliceInfoElem::Index(0));
|
slice.push(SliceInfoElem::Index(0));
|
||||||
axes.push(axis);
|
axes.push(axis);
|
||||||
@@ -180,7 +176,7 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// change the dimension into a concrete dimension
|
/// change the dimension into a concrete dimension
|
||||||
pub fn into_dimensionality<D2: Dimension>(self) -> Result<View<D2>> {
|
pub fn into_dimensionality<D2: Dimension>(self) -> Result<View<D2>, Error> {
|
||||||
if let Some(d) = D2::NDIM {
|
if let Some(d) = D2::NDIM {
|
||||||
if d == self.ndim() {
|
if d == self.ndim() {
|
||||||
Ok(View {
|
Ok(View {
|
||||||
@@ -191,7 +187,7 @@ impl<D: Dimension> View<D> {
|
|||||||
dimensionality: PhantomData,
|
dimensionality: PhantomData,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("Dimensionality mismatch: {} != {}", d, self.ndim()))
|
Err(Error::DimensionalityMismatch(d, self.ndim()))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(View {
|
Ok(View {
|
||||||
@@ -235,7 +231,7 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// remove axes of size 1
|
/// remove axes of size 1
|
||||||
pub fn squeeze(&self) -> Result<View<IxDyn>> {
|
pub fn squeeze(&self) -> Result<View<IxDyn>, Error> {
|
||||||
let view = self.clone().into_dyn();
|
let view = self.clone().into_dyn();
|
||||||
let slice: Vec<_> = self
|
let slice: Vec<_> = self
|
||||||
.shape()
|
.shape()
|
||||||
@@ -369,7 +365,7 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// swap two axes
|
/// swap two axes
|
||||||
pub fn swap_axes<A: Ax>(&self, axis0: A, axis1: A) -> Result<Self> {
|
pub fn swap_axes<A: Ax>(&self, axis0: A, axis1: A) -> Result<Self, Error> {
|
||||||
let idx0 = axis0.pos_op(&self.axes, &self.slice, &self.op_axes())?;
|
let idx0 = axis0.pos_op(&self.axes, &self.slice, &self.op_axes())?;
|
||||||
let idx1 = axis1.pos_op(&self.axes, &self.slice, &self.op_axes())?;
|
let idx1 = axis1.pos_op(&self.axes, &self.slice, &self.op_axes())?;
|
||||||
let mut slice = self.slice.to_vec();
|
let mut slice = self.slice.to_vec();
|
||||||
@@ -380,7 +376,7 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// subset of gives axes will be reordered in given order
|
/// subset of gives axes will be reordered in given order
|
||||||
pub fn permute_axes<A: Ax>(&self, axes: &[A]) -> Result<Self> {
|
pub fn permute_axes<A: Ax>(&self, axes: &[A]) -> Result<Self, Error> {
|
||||||
let idx: Vec<usize> = axes
|
let idx: Vec<usize> = axes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|a| a.pos_op(&self.axes, &self.slice, &self.op_axes()).unwrap())
|
.map(|a| a.pos_op(&self.axes, &self.slice, &self.op_axes()).unwrap())
|
||||||
@@ -397,7 +393,7 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// reverse the order of the axes
|
/// reverse the order of the axes
|
||||||
pub fn transpose(&self) -> Result<Self> {
|
pub fn transpose(&self) -> Result<Self, Error> {
|
||||||
Ok(View::new(
|
Ok(View::new(
|
||||||
self.reader.clone(),
|
self.reader.clone(),
|
||||||
self.slice.iter().rev().cloned().collect(),
|
self.slice.iter().rev().cloned().collect(),
|
||||||
@@ -406,7 +402,7 @@ impl<D: Dimension> View<D> {
|
|||||||
.with_operations(self.operations.clone()))
|
.with_operations(self.operations.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate<A: Ax>(&self, axis: A, operation: Operation) -> Result<View<D::Smaller>> {
|
fn operate<A: Ax>(&self, axis: A, operation: Operation) -> Result<View<D::Smaller>, Error> {
|
||||||
let pos = axis.pos_op(&self.axes, &self.slice, &self.op_axes())?;
|
let pos = axis.pos_op(&self.axes, &self.slice, &self.op_axes())?;
|
||||||
let ax = self.axes[pos];
|
let ax = self.axes[pos];
|
||||||
let (axes, slice, operations) = if Axis::New == ax {
|
let (axes, slice, operations) = if Axis::New == ax {
|
||||||
@@ -423,7 +419,7 @@ impl<D: Dimension> View<D> {
|
|||||||
self.operations.clone(),
|
self.operations.clone(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return Err(anyhow!("axis {}: {} is already operated on!", pos, ax));
|
return Err(Error::AxisAlreadyOperated(pos, ax.to_string()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut operations = self.operations.clone();
|
let mut operations = self.operations.clone();
|
||||||
@@ -434,32 +430,32 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// maximum along axis
|
/// maximum along axis
|
||||||
pub fn max_proj<A: Ax>(&self, axis: A) -> Result<View<D::Smaller>> {
|
pub fn max_proj<A: Ax>(&self, axis: A) -> Result<View<D::Smaller>, Error> {
|
||||||
self.operate(axis, Operation::Max)
|
self.operate(axis, Operation::Max)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// minimum along axis
|
/// minimum along axis
|
||||||
pub fn min_proj<A: Ax>(&self, axis: A) -> Result<View<D::Smaller>> {
|
pub fn min_proj<A: Ax>(&self, axis: A) -> Result<View<D::Smaller>, Error> {
|
||||||
self.operate(axis, Operation::Min)
|
self.operate(axis, Operation::Min)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// sum along axis
|
/// sum along axis
|
||||||
pub fn sum_proj<A: Ax>(&self, axis: A) -> Result<View<D::Smaller>> {
|
pub fn sum_proj<A: Ax>(&self, axis: A) -> Result<View<D::Smaller>, Error> {
|
||||||
self.operate(axis, Operation::Sum)
|
self.operate(axis, Operation::Sum)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// mean along axis
|
/// mean along axis
|
||||||
pub fn mean_proj<A: Ax>(&self, axis: A) -> Result<View<D::Smaller>> {
|
pub fn mean_proj<A: Ax>(&self, axis: A) -> Result<View<D::Smaller>, Error> {
|
||||||
self.operate(axis, Operation::Mean)
|
self.operate(axis, Operation::Mean)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// created a new sliced view
|
/// created a new sliced view
|
||||||
pub fn slice<I>(&self, info: I) -> Result<View<I::OutDim>>
|
pub fn slice<I>(&self, info: I) -> Result<View<I::OutDim>, Error>
|
||||||
where
|
where
|
||||||
I: SliceArg<D>,
|
I: SliceArg<D>,
|
||||||
{
|
{
|
||||||
if self.slice.out_ndim() < info.in_ndim() {
|
if self.slice.out_ndim() < info.in_ndim() {
|
||||||
return Err(Error::msg("not enough free dimensions"));
|
return Err(Error::NotEnoughFreeDimensions);
|
||||||
}
|
}
|
||||||
let info = info.as_ref();
|
let info = info.as_ref();
|
||||||
let mut n_idx = 0;
|
let mut n_idx = 0;
|
||||||
@@ -495,11 +491,7 @@ impl<D: Dimension> View<D> {
|
|||||||
};
|
};
|
||||||
let new_step = (step * info_step).abs();
|
let new_step = (step * info_step).abs();
|
||||||
if new_start > end {
|
if new_start > end {
|
||||||
return Err(anyhow!(
|
return Err(Error::OutOfBounds(*info_start, (end - start) / step));
|
||||||
"Index {} out of bounds {}",
|
|
||||||
info_start,
|
|
||||||
(end - start) / step
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
new_slice.push(SliceInfoElem::Slice {
|
new_slice.push(SliceInfoElem::Slice {
|
||||||
start: new_start,
|
start: new_start,
|
||||||
@@ -521,11 +513,7 @@ impl<D: Dimension> View<D> {
|
|||||||
};
|
};
|
||||||
let end = end.expect("slice has no end");
|
let end = end.expect("slice has no end");
|
||||||
if i >= end {
|
if i >= end {
|
||||||
return Err(anyhow!(
|
return Err(Error::OutOfBounds(i, (end - start) / step));
|
||||||
"Index {} out of bounds {}",
|
|
||||||
i,
|
|
||||||
(end - start) / step
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
new_slice.push(SliceInfoElem::Index(i));
|
new_slice.push(SliceInfoElem::Index(i));
|
||||||
new_axes.push(*a.expect("axis should exist when slice exists"));
|
new_axes.push(*a.expect("axis should exist when slice exists"));
|
||||||
@@ -534,7 +522,7 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
(Some(SliceInfoElem::Slice { start, .. }), Some(SliceInfoElem::NewAxis)) => {
|
(Some(SliceInfoElem::Slice { start, .. }), Some(SliceInfoElem::NewAxis)) => {
|
||||||
if *start != 0 {
|
if *start != 0 {
|
||||||
return Err(anyhow!("Index {} out of bounds 1", start));
|
return Err(Error::OutOfBounds(*start, 1));
|
||||||
}
|
}
|
||||||
new_slice.push(SliceInfoElem::NewAxis);
|
new_slice.push(SliceInfoElem::NewAxis);
|
||||||
new_axes.push(Axis::New);
|
new_axes.push(Axis::New);
|
||||||
@@ -543,7 +531,7 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
(Some(SliceInfoElem::Index(k)), Some(SliceInfoElem::NewAxis)) => {
|
(Some(SliceInfoElem::Index(k)), Some(SliceInfoElem::NewAxis)) => {
|
||||||
if *k != 0 {
|
if *k != 0 {
|
||||||
return Err(anyhow!("Index {} out of bounds 1", k));
|
return Err(Error::OutOfBounds(*k, 1));
|
||||||
}
|
}
|
||||||
n_idx += 1;
|
n_idx += 1;
|
||||||
r_idx += 1;
|
r_idx += 1;
|
||||||
@@ -587,7 +575,7 @@ impl<D: Dimension> View<D> {
|
|||||||
|
|
||||||
/// resets axes to cztyx order, with all 5 axes present,
|
/// resets axes to cztyx order, with all 5 axes present,
|
||||||
/// inserts new axes in place of axes under operation (max_proj etc.)
|
/// inserts new axes in place of axes under operation (max_proj etc.)
|
||||||
pub fn reset_axes(&self) -> Result<View<Ix5>> {
|
pub fn reset_axes(&self) -> Result<View<Ix5>, Error> {
|
||||||
let mut axes = Vec::new();
|
let mut axes = Vec::new();
|
||||||
let mut slice = Vec::new();
|
let mut slice = Vec::new();
|
||||||
|
|
||||||
@@ -615,7 +603,7 @@ impl<D: Dimension> View<D> {
|
|||||||
|
|
||||||
/// slice, but slice elements are in cztyx order, all cztyx must be given,
|
/// slice, but slice elements are in cztyx order, all cztyx must be given,
|
||||||
/// but axes not present in view will be ignored, view axes are reordered in cztyx order
|
/// but axes not present in view will be ignored, view axes are reordered in cztyx order
|
||||||
pub fn slice_cztyx<I>(&self, info: I) -> Result<View<I::OutDim>>
|
pub fn slice_cztyx<I>(&self, info: I) -> Result<View<I::OutDim>, Error>
|
||||||
where
|
where
|
||||||
I: SliceArg<Ix5>,
|
I: SliceArg<Ix5>,
|
||||||
{
|
{
|
||||||
@@ -623,7 +611,7 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// the pixel intensity at a given index
|
/// the pixel intensity at a given index
|
||||||
pub fn item_at<T>(&self, index: &[isize]) -> Result<T>
|
pub fn item_at<T>(&self, index: &[isize]) -> Result<T, Error>
|
||||||
where
|
where
|
||||||
T: Number,
|
T: Number,
|
||||||
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
@@ -637,7 +625,7 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// collect the view into an ndarray
|
/// collect the view into an ndarray
|
||||||
pub fn as_array<T>(&self) -> Result<Array<T, D>>
|
pub fn as_array<T>(&self) -> Result<Array<T, D>, Error>
|
||||||
where
|
where
|
||||||
T: Number,
|
T: Number,
|
||||||
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
@@ -648,7 +636,7 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// collect the view into a dynamic-dimension ndarray
|
/// collect the view into a dynamic-dimension ndarray
|
||||||
pub fn as_array_dyn<T>(&self) -> Result<ArrayD<T>>
|
pub fn as_array_dyn<T>(&self) -> Result<ArrayD<T>, Error>
|
||||||
where
|
where
|
||||||
T: Number,
|
T: Number,
|
||||||
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
@@ -889,7 +877,7 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// turn the view into a 1d array
|
/// turn the view into a 1d array
|
||||||
pub fn flatten<T>(&self) -> Result<Array1<T>>
|
pub fn flatten<T>(&self) -> Result<Array1<T>, Error>
|
||||||
where
|
where
|
||||||
T: Number,
|
T: Number,
|
||||||
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
@@ -900,7 +888,7 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// turn the data into a byte vector
|
/// turn the data into a byte vector
|
||||||
pub fn to_bytes<T>(&self) -> Result<Vec<u8>>
|
pub fn to_bytes<T>(&self) -> Result<Vec<u8>, Error>
|
||||||
where
|
where
|
||||||
T: Number + ToBytesVec,
|
T: Number + ToBytesVec,
|
||||||
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
@@ -915,7 +903,7 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// retrieve a single frame at czt, sliced accordingly
|
/// retrieve a single frame at czt, sliced accordingly
|
||||||
pub fn get_frame<T, N>(&self, c: N, z: N, t: N) -> Result<Array2<T>>
|
pub fn get_frame<T, N>(&self, c: N, z: N, t: N) -> Result<Array2<T>, Error>
|
||||||
where
|
where
|
||||||
T: Number,
|
T: Number,
|
||||||
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
@@ -925,17 +913,17 @@ impl<D: Dimension> View<D> {
|
|||||||
{
|
{
|
||||||
let c = c
|
let c = c
|
||||||
.to_isize()
|
.to_isize()
|
||||||
.ok_or_else(|| anyhow!("cannot convert {} into isize", c))?;
|
.ok_or_else(|| Error::Cast(c.to_string(), "isize".to_string()))?;
|
||||||
let z = z
|
let z = z
|
||||||
.to_isize()
|
.to_isize()
|
||||||
.ok_or_else(|| anyhow!("cannot convert {} into isize", z))?;
|
.ok_or_else(|| Error::Cast(z.to_string(), "isize".to_string()))?;
|
||||||
let t = t
|
let t = t
|
||||||
.to_isize()
|
.to_isize()
|
||||||
.ok_or_else(|| anyhow!("cannot convert {} into isize", t))?;
|
.ok_or_else(|| Error::Cast(t.to_string(), "isize".to_string()))?;
|
||||||
self.slice_cztyx(s![c, z, t, .., ..])?.as_array()
|
self.slice_cztyx(s![c, z, t, .., ..])?.as_array()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_stat<T>(&self, operation: Operation) -> Result<T>
|
fn get_stat<T>(&self, operation: Operation) -> Result<T, Error>
|
||||||
where
|
where
|
||||||
T: Number + Sum,
|
T: Number + Sum,
|
||||||
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
@@ -958,14 +946,14 @@ impl<D: Dimension> View<D> {
|
|||||||
Operation::Mean => {
|
Operation::Mean => {
|
||||||
arr.flatten().into_iter().sum::<T>()
|
arr.flatten().into_iter().sum::<T>()
|
||||||
/ T::from_usize(arr.len()).ok_or_else(|| {
|
/ T::from_usize(arr.len()).ok_or_else(|| {
|
||||||
anyhow!("cannot convert {} into {}", arr.len(), type_name::<T>())
|
Error::Cast(arr.len().to_string(), type_name::<T>().to_string())
|
||||||
})?
|
})?
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// maximum intensity
|
/// maximum intensity
|
||||||
pub fn max<T>(&self) -> Result<T>
|
pub fn max<T>(&self) -> Result<T, Error>
|
||||||
where
|
where
|
||||||
T: Number + Sum,
|
T: Number + Sum,
|
||||||
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
@@ -976,7 +964,7 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// minimum intensity
|
/// minimum intensity
|
||||||
pub fn min<T>(&self) -> Result<T>
|
pub fn min<T>(&self) -> Result<T, Error>
|
||||||
where
|
where
|
||||||
T: Number + Sum,
|
T: Number + Sum,
|
||||||
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
@@ -987,7 +975,7 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// sum intensity
|
/// sum intensity
|
||||||
pub fn sum<T>(&self) -> Result<T>
|
pub fn sum<T>(&self) -> Result<T, Error>
|
||||||
where
|
where
|
||||||
T: Number + Sum,
|
T: Number + Sum,
|
||||||
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
@@ -998,7 +986,7 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// mean intensity
|
/// mean intensity
|
||||||
pub fn mean<T>(&self) -> Result<T>
|
pub fn mean<T>(&self) -> Result<T, Error>
|
||||||
where
|
where
|
||||||
T: Number + Sum,
|
T: Number + Sum,
|
||||||
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
@@ -1009,7 +997,7 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// gives a helpful summary of the recorded experiment
|
/// gives a helpful summary of the recorded experiment
|
||||||
pub fn summary(&self) -> Result<String> {
|
pub fn summary(&self) -> Result<String, Error> {
|
||||||
let mut s = "".to_string();
|
let mut s = "".to_string();
|
||||||
s.push_str(&format!("path/filename: {}\n", self.path.display()));
|
s.push_str(&format!("path/filename: {}\n", self.path.display()));
|
||||||
s.push_str(&format!("series/pos: {}\n", self.series));
|
s.push_str(&format!("series/pos: {}\n", self.series));
|
||||||
@@ -1072,7 +1060,7 @@ where
|
|||||||
|
|
||||||
/// trait to define a function to retrieve the only item in a 0d array
|
/// trait to define a function to retrieve the only item in a 0d array
|
||||||
pub trait Item {
|
pub trait Item {
|
||||||
fn item<T>(&self) -> Result<T>
|
fn item<T>(&self) -> Result<T, Error>
|
||||||
where
|
where
|
||||||
T: Number,
|
T: Number,
|
||||||
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
@@ -1081,7 +1069,7 @@ pub trait Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl View<Ix5> {
|
impl View<Ix5> {
|
||||||
pub fn from_path<P>(path: P, series: usize) -> Result<Self>
|
pub fn from_path<P>(path: P, series: usize) -> Result<Self, Error>
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
@@ -1100,18 +1088,14 @@ impl View<Ix5> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Item for View<Ix0> {
|
impl Item for View<Ix0> {
|
||||||
fn item<T>(&self) -> Result<T>
|
fn item<T>(&self) -> Result<T, Error>
|
||||||
where
|
where
|
||||||
T: Number,
|
T: Number,
|
||||||
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
Array1<T>: MinMax<Output = Array0<T>>,
|
Array1<T>: MinMax<Output = Array0<T>>,
|
||||||
Array2<T>: MinMax<Output = Array1<T>>,
|
Array2<T>: MinMax<Output = Array1<T>>,
|
||||||
{
|
{
|
||||||
Ok(self
|
Ok(self.as_array()?.first().ok_or(Error::EmptyView)?.clone())
|
||||||
.as_array()?
|
|
||||||
.first()
|
|
||||||
.ok_or_else(|| anyhow!("Empty view"))?
|
|
||||||
.clone())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,5 +37,5 @@ def test_slicing(s, image, array):
|
|||||||
assert s_im == s_a
|
assert s_im == s_a
|
||||||
else:
|
else:
|
||||||
assert isinstance(s_im, Imread)
|
assert isinstance(s_im, Imread)
|
||||||
assert s_im.shape == s_a.shape
|
assert tuple(s_im.shape) == s_a.shape
|
||||||
assert np.all(s_im == s_a)
|
assert np.all(s_im == s_a)
|
||||||
|
|||||||
Reference in New Issue
Block a user