- bump bioformats to 8.3.0
- rust: command line binary, save as mp4, save as tiff, ome metadata, more methods for View, bugfixes, less unsafe code - python: ome as dict
This commit is contained in:
37
Cargo.toml
37
Cargo.toml
@@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ndbioimage"
|
name = "ndbioimage"
|
||||||
version = "2025.4.1"
|
version = "2025.8.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
rust-version = "1.78.0"
|
rust-version = "1.85.1"
|
||||||
authors = ["Wim Pomp <w.pomp@nki.nl>"]
|
authors = ["Wim Pomp <w.pomp@nki.nl>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Read bio image formats using the bio-formats java package."
|
description = "Read bio image formats using the bio-formats java package."
|
||||||
@@ -19,20 +19,28 @@ name = "ndbioimage"
|
|||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.98"
|
anyhow = { version = "1.0.99", features = ["backtrace"] }
|
||||||
|
clap = { version = "4.5.45", features = ["derive"] }
|
||||||
|
ffmpeg-sidecar = { version = "2.1.0", optional = true }
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
indexmap = { version = "2.9.0", features = ["serde"] }
|
indexmap = { version = "2.0.0", features = ["serde"] }
|
||||||
|
indicatif = { version = "0.18.0", features = ["rayon"], optional = true }
|
||||||
j4rs = "0.22.0"
|
j4rs = "0.22.0"
|
||||||
ndarray = { version = "0.16.1", features = ["serde"] }
|
ndarray = { version = "0.16.1", features = ["serde"] }
|
||||||
num = "0.4.3"
|
num = "0.4.3"
|
||||||
numpy = { version = "0.24.0", optional = true }
|
numpy = { version = "0.25.0", optional = true }
|
||||||
|
ordered-float = "5.0.0"
|
||||||
|
rayon = { version = "1.11.0", optional = true }
|
||||||
serde = { version = "1.0.219", features = ["rc"] }
|
serde = { version = "1.0.219", features = ["rc"] }
|
||||||
serde_json = { version = "1.0.140", optional = true }
|
serde_json = { version = "1.0.143", optional = true }
|
||||||
serde_with = "3.12.0"
|
serde_with = "3.12.0"
|
||||||
thread_local = "1.1.8"
|
tiffwrite = { version = "2025.5.0", optional = true}
|
||||||
|
thread_local = "1.1.9"
|
||||||
|
ome-metadata = "0.2.2"
|
||||||
|
lazy_static = "1.5.0"
|
||||||
|
|
||||||
[dependencies.pyo3]
|
[dependencies.pyo3]
|
||||||
version = "0.24.2"
|
version = "0.25.1"
|
||||||
features = ["extension-module", "abi3-py310", "generate-import-lib", "anyhow"]
|
features = ["extension-module", "abi3-py310", "generate-import-lib", "anyhow"]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
@@ -40,10 +48,17 @@ optional = true
|
|||||||
rayon = "1.10.0"
|
rayon = "1.10.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
anyhow = "1.0.98"
|
anyhow = "1.0.99"
|
||||||
j4rs = "0.22.0"
|
j4rs = "0.22.0"
|
||||||
|
ffmpeg-sidecar = "2.1.0"
|
||||||
retry = "2.1.0"
|
retry = "2.1.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
python = ["dep:pyo3", "dep:numpy", "dep:serde_json"]
|
# Enables formats for which code in bioformats with a GPL license is needed
|
||||||
gpl-formats = []
|
gpl-formats = []
|
||||||
|
# Enables python ffi using pyO3
|
||||||
|
python = ["dep:pyo3", "dep:numpy", "dep:serde_json"]
|
||||||
|
# Enables writing as tiff
|
||||||
|
tiff = ["dep:tiffwrite", "dep:indicatif", "dep:rayon"]
|
||||||
|
# Enables writing as mp4 using ffmpeg
|
||||||
|
movie = ["dep:ffmpeg-sidecar"]
|
||||||
19
build.rs
19
build.rs
@@ -1,5 +1,5 @@
|
|||||||
#[cfg(not(feature = "python"))]
|
#[cfg(not(feature = "python"))]
|
||||||
use j4rs::{errors::J4RsError, JvmBuilder, MavenArtifact, MavenArtifactRepo, MavenSettings};
|
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};
|
||||||
@@ -7,16 +7,21 @@ use retry::{delay, delay::Exponential, retry};
|
|||||||
#[cfg(feature = "python")]
|
#[cfg(feature = "python")]
|
||||||
use j4rs::Jvm;
|
use j4rs::Jvm;
|
||||||
|
|
||||||
|
#[cfg(feature = "movie")]
|
||||||
|
use ffmpeg_sidecar::download::auto_download;
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
println!("cargo::rerun-if-changed=build.rs");
|
println!("cargo::rerun-if-changed=build.rs");
|
||||||
|
|
||||||
#[cfg(not(feature = "python"))]
|
|
||||||
if std::env::var("DOCS_RS").is_err() {
|
if std::env::var("DOCS_RS").is_err() {
|
||||||
|
#[cfg(feature = "movie")]
|
||||||
|
auto_download()?;
|
||||||
|
|
||||||
|
#[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,
|
||||||
)?
|
)?;
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "python")]
|
#[cfg(feature = "python")]
|
||||||
{
|
{
|
||||||
@@ -48,6 +53,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -55,15 +61,16 @@ fn main() -> anyhow::Result<()> {
|
|||||||
#[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()
|
||||||
|
.skip_setting_native_lib()
|
||||||
.with_maven_settings(MavenSettings::new(vec![MavenArtifactRepo::from(
|
.with_maven_settings(MavenSettings::new(vec![MavenArtifactRepo::from(
|
||||||
"openmicroscopy::https://artifacts.openmicroscopy.org/artifactory/ome.releases",
|
"openmicroscopy::https://artifacts.openmicroscopy.org/artifactory/ome.releases",
|
||||||
)]))
|
)]))
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
jvm.deploy_artifact(&MavenArtifact::from("ome:bioformats_package:8.1.0"))?;
|
jvm.deploy_artifact(&MavenArtifact::from("ome:bioformats_package:8.3.0"))?;
|
||||||
|
|
||||||
#[cfg(feature = "gpl-formats")]
|
#[cfg(feature = "gpl-formats")]
|
||||||
jvm.deploy_artifact(&MavenArtifact::from("ome:formats-gpl:8.1.0"))?;
|
jvm.deploy_artifact(&MavenArtifact::from("ome:formats-gpl:8.3.0"))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
39
py/ndbioimage/ome.py
Normal file
39
py/ndbioimage/ome.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from ome_metadata import ome_metadata_rs as rs # noqa
|
||||||
|
from collections import UserDict, UserList
|
||||||
|
|
||||||
|
|
||||||
|
class Ome(UserDict):
|
||||||
|
@staticmethod
|
||||||
|
def from_xml(xml: str) -> Ome:
|
||||||
|
"""Create the OME structure from an XML string"""
|
||||||
|
new = Ome()
|
||||||
|
new.update(rs.ome(str(xml)))
|
||||||
|
return new
|
||||||
|
|
||||||
|
def __dir__(self) -> list[str]:
|
||||||
|
return list(self.keys()) + list(super().__dir__())
|
||||||
|
|
||||||
|
def __getattr__(self, key: str) -> Ome | OmeList | int | float | str:
|
||||||
|
try:
|
||||||
|
new = self.__getitem__(key)
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(f"'Ome' object has no attribute '{key}'")
|
||||||
|
if isinstance(new, dict):
|
||||||
|
return Ome(**new)
|
||||||
|
elif isinstance(new, list):
|
||||||
|
return OmeList(new)
|
||||||
|
else:
|
||||||
|
return new
|
||||||
|
|
||||||
|
|
||||||
|
class OmeList(UserList):
|
||||||
|
def __getitem__(self, item: int) -> Ome | OmeList | int | float | str:
|
||||||
|
new = super().__getitem__(item)
|
||||||
|
if isinstance(new, dict):
|
||||||
|
return Ome(**new)
|
||||||
|
elif isinstance(new, list):
|
||||||
|
return OmeList(new)
|
||||||
|
else:
|
||||||
|
return new
|
||||||
@@ -27,16 +27,9 @@ classifiers = [
|
|||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"numpy",
|
"numpy",
|
||||||
"pandas",
|
|
||||||
"tiffwrite",
|
"tiffwrite",
|
||||||
"ome-types",
|
"ome-metadata >= 0.2.1",
|
||||||
"pint",
|
|
||||||
"tqdm",
|
"tqdm",
|
||||||
"lxml",
|
|
||||||
"pyyaml",
|
|
||||||
"parfor",
|
|
||||||
"SimpleITK-SimpleElastix",
|
|
||||||
"scikit-image",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
|||||||
24
src/axes.rs
24
src/axes.rs
@@ -1,8 +1,9 @@
|
|||||||
use crate::stats::MinMax;
|
use crate::stats::MinMax;
|
||||||
use anyhow::{anyhow, Error, Result};
|
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};
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
@@ -51,6 +52,20 @@ impl FromStr for Axis {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Axis {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let s = match self {
|
||||||
|
Axis::C => "C",
|
||||||
|
Axis::Z => "Z",
|
||||||
|
Axis::T => "T",
|
||||||
|
Axis::Y => "Y",
|
||||||
|
Axis::X => "X",
|
||||||
|
Axis::New => "N",
|
||||||
|
};
|
||||||
|
write!(f, "{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Ax for Axis {
|
impl Ax for Axis {
|
||||||
fn n(&self) -> usize {
|
fn n(&self) -> usize {
|
||||||
*self as usize
|
*self as usize
|
||||||
@@ -60,7 +75,10 @@ impl Ax for Axis {
|
|||||||
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("Axis not found in axes"))
|
Err(Error::msg(format!(
|
||||||
|
"Axis {:?} not found in axes {:?}",
|
||||||
|
self, axes
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +114,7 @@ impl Ax for usize {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
assert!(*self < idx.len(), "self: {}, idx: {:?}", self, idx);
|
debug_assert!(*self < idx.len(), "self: {}, idx: {:?}", self, idx);
|
||||||
Ok(idx[*self])
|
Ok(idx[*self])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,21 @@ fn jvm() -> Rc<Jvm> {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.to_path_buf();
|
.to_path_buf();
|
||||||
|
|
||||||
let class_path = path.parent().unwrap();
|
let class_path = if path.join("jassets").exists() {
|
||||||
|
path.as_path()
|
||||||
|
} else {
|
||||||
|
path.parent().unwrap()
|
||||||
|
};
|
||||||
|
if !class_path.join("jassets").exists() {
|
||||||
|
panic!(
|
||||||
|
"jassets directory does not exist in {}",
|
||||||
|
class_path.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Rc::new(
|
Rc::new(
|
||||||
JvmBuilder::new()
|
JvmBuilder::new()
|
||||||
|
.skip_setting_native_lib()
|
||||||
.with_base_path(class_path.to_str().unwrap())
|
.with_base_path(class_path.to_str().unwrap())
|
||||||
.build()
|
.build()
|
||||||
.expect("Failed to build JVM"),
|
.expect("Failed to build JVM"),
|
||||||
@@ -33,11 +45,25 @@ fn jvm() -> Rc<Jvm> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn download_bioformats(gpl_formats: bool) -> Result<()> {
|
||||||
#[cfg(feature = "python")]
|
#[cfg(feature = "python")]
|
||||||
pub(crate) fn download_bioformats(gpl_formats: bool) -> Result<()> {
|
let path = crate::py::ndbioimage_file()?;
|
||||||
let path = crate::py::ndbioimage_file().unwrap();
|
|
||||||
|
#[cfg(not(feature = "python"))]
|
||||||
|
let path = std::env::current_exe()
|
||||||
|
.unwrap()
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.to_path_buf();
|
||||||
|
|
||||||
let class_path = path.parent().unwrap();
|
let class_path = path.parent().unwrap();
|
||||||
|
let jassets = class_path.join("jassets");
|
||||||
|
if !jassets.exists() {
|
||||||
|
std::fs::create_dir_all(jassets)?;
|
||||||
|
}
|
||||||
|
println!("installing jassets in {}", class_path.display());
|
||||||
let jvm = JvmBuilder::new()
|
let jvm = JvmBuilder::new()
|
||||||
|
.skip_setting_native_lib()
|
||||||
.with_base_path(class_path.to_str().unwrap())
|
.with_base_path(class_path.to_str().unwrap())
|
||||||
.with_maven_settings(j4rs::MavenSettings::new(vec![
|
.with_maven_settings(j4rs::MavenSettings::new(vec![
|
||||||
j4rs::MavenArtifactRepo::from(
|
j4rs::MavenArtifactRepo::from(
|
||||||
@@ -46,10 +72,10 @@ pub(crate) fn download_bioformats(gpl_formats: bool) -> Result<()> {
|
|||||||
]))
|
]))
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
jvm.deploy_artifact(&j4rs::MavenArtifact::from("ome:bioformats_package:8.1.0"))?;
|
jvm.deploy_artifact(&j4rs::MavenArtifact::from("ome:bioformats_package:8.3.0"))?;
|
||||||
|
|
||||||
if gpl_formats {
|
if gpl_formats {
|
||||||
jvm.deploy_artifact(&j4rs::MavenArtifact::from("ome:formats-gpl:8.1.0"))?;
|
jvm.deploy_artifact(&j4rs::MavenArtifact::from("ome:formats-gpl:8.3.0"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
207
src/colors.rs
Normal file
207
src/colors.rs
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
use anyhow::{Error, Result};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref COLORS: HashMap<String, String> = {
|
||||||
|
HashMap::from([
|
||||||
|
("b".to_string(), "#0000FF".to_string()),
|
||||||
|
("g".to_string(), "#008000".to_string()),
|
||||||
|
("r".to_string(), "#FF0000".to_string()),
|
||||||
|
("c".to_string(), "#00BFBF".to_string()),
|
||||||
|
("m".to_string(), "#BF00BF".to_string()),
|
||||||
|
("y".to_string(), "#BFBF00".to_string()),
|
||||||
|
("k".to_string(), "#000000".to_string()),
|
||||||
|
("w".to_string(), "#FFFFFF".to_string()),
|
||||||
|
("aliceblue".to_string(), "#F0F8FF".to_string()),
|
||||||
|
("antiquewhite".to_string(), "#FAEBD7".to_string()),
|
||||||
|
("aqua".to_string(), "#00FFFF".to_string()),
|
||||||
|
("aquamarine".to_string(), "#7FFFD4".to_string()),
|
||||||
|
("azure".to_string(), "#F0FFFF".to_string()),
|
||||||
|
("beige".to_string(), "#F5F5DC".to_string()),
|
||||||
|
("bisque".to_string(), "#FFE4C4".to_string()),
|
||||||
|
("black".to_string(), "#000000".to_string()),
|
||||||
|
("blanchedalmond".to_string(), "#FFEBCD".to_string()),
|
||||||
|
("blue".to_string(), "#0000FF".to_string()),
|
||||||
|
("blueviolet".to_string(), "#8A2BE2".to_string()),
|
||||||
|
("brown".to_string(), "#A52A2A".to_string()),
|
||||||
|
("burlywood".to_string(), "#DEB887".to_string()),
|
||||||
|
("cadetblue".to_string(), "#5F9EA0".to_string()),
|
||||||
|
("chartreuse".to_string(), "#7FFF00".to_string()),
|
||||||
|
("chocolate".to_string(), "#D2691E".to_string()),
|
||||||
|
("coral".to_string(), "#FF7F50".to_string()),
|
||||||
|
("cornflowerblue".to_string(), "#6495ED".to_string()),
|
||||||
|
("cornsilk".to_string(), "#FFF8DC".to_string()),
|
||||||
|
("crimson".to_string(), "#DC143C".to_string()),
|
||||||
|
("cyan".to_string(), "#00FFFF".to_string()),
|
||||||
|
("darkblue".to_string(), "#00008B".to_string()),
|
||||||
|
("darkcyan".to_string(), "#008B8B".to_string()),
|
||||||
|
("darkgoldenrod".to_string(), "#B8860B".to_string()),
|
||||||
|
("darkgray".to_string(), "#A9A9A9".to_string()),
|
||||||
|
("darkgreen".to_string(), "#006400".to_string()),
|
||||||
|
("darkgrey".to_string(), "#A9A9A9".to_string()),
|
||||||
|
("darkkhaki".to_string(), "#BDB76B".to_string()),
|
||||||
|
("darkmagenta".to_string(), "#8B008B".to_string()),
|
||||||
|
("darkolivegreen".to_string(), "#556B2F".to_string()),
|
||||||
|
("darkorange".to_string(), "#FF8C00".to_string()),
|
||||||
|
("darkorchid".to_string(), "#9932CC".to_string()),
|
||||||
|
("darkred".to_string(), "#8B0000".to_string()),
|
||||||
|
("darksalmon".to_string(), "#E9967A".to_string()),
|
||||||
|
("darkseagreen".to_string(), "#8FBC8F".to_string()),
|
||||||
|
("darkslateblue".to_string(), "#483D8B".to_string()),
|
||||||
|
("darkslategray".to_string(), "#2F4F4F".to_string()),
|
||||||
|
("darkslategrey".to_string(), "#2F4F4F".to_string()),
|
||||||
|
("darkturquoise".to_string(), "#00CED1".to_string()),
|
||||||
|
("darkviolet".to_string(), "#9400D3".to_string()),
|
||||||
|
("deeppink".to_string(), "#FF1493".to_string()),
|
||||||
|
("deepskyblue".to_string(), "#00BFFF".to_string()),
|
||||||
|
("dimgray".to_string(), "#696969".to_string()),
|
||||||
|
("dimgrey".to_string(), "#696969".to_string()),
|
||||||
|
("dodgerblue".to_string(), "#1E90FF".to_string()),
|
||||||
|
("firebrick".to_string(), "#B22222".to_string()),
|
||||||
|
("floralwhite".to_string(), "#FFFAF0".to_string()),
|
||||||
|
("forestgreen".to_string(), "#228B22".to_string()),
|
||||||
|
("fuchsia".to_string(), "#FF00FF".to_string()),
|
||||||
|
("gainsboro".to_string(), "#DCDCDC".to_string()),
|
||||||
|
("ghostwhite".to_string(), "#F8F8FF".to_string()),
|
||||||
|
("gold".to_string(), "#FFD700".to_string()),
|
||||||
|
("goldenrod".to_string(), "#DAA520".to_string()),
|
||||||
|
("gray".to_string(), "#808080".to_string()),
|
||||||
|
("green".to_string(), "#008000".to_string()),
|
||||||
|
("greenyellow".to_string(), "#ADFF2F".to_string()),
|
||||||
|
("grey".to_string(), "#808080".to_string()),
|
||||||
|
("honeydew".to_string(), "#F0FFF0".to_string()),
|
||||||
|
("hotpink".to_string(), "#FF69B4".to_string()),
|
||||||
|
("indianred".to_string(), "#CD5C5C".to_string()),
|
||||||
|
("indigo".to_string(), "#4B0082".to_string()),
|
||||||
|
("ivory".to_string(), "#FFFFF0".to_string()),
|
||||||
|
("khaki".to_string(), "#F0E68C".to_string()),
|
||||||
|
("lavender".to_string(), "#E6E6FA".to_string()),
|
||||||
|
("lavenderblush".to_string(), "#FFF0F5".to_string()),
|
||||||
|
("lawngreen".to_string(), "#7CFC00".to_string()),
|
||||||
|
("lemonchiffon".to_string(), "#FFFACD".to_string()),
|
||||||
|
("lightblue".to_string(), "#ADD8E6".to_string()),
|
||||||
|
("lightcoral".to_string(), "#F08080".to_string()),
|
||||||
|
("lightcyan".to_string(), "#E0FFFF".to_string()),
|
||||||
|
("lightgoldenrodyellow".to_string(), "#FAFAD2".to_string()),
|
||||||
|
("lightgray".to_string(), "#D3D3D3".to_string()),
|
||||||
|
("lightgreen".to_string(), "#90EE90".to_string()),
|
||||||
|
("lightgrey".to_string(), "#D3D3D3".to_string()),
|
||||||
|
("lightpink".to_string(), "#FFB6C1".to_string()),
|
||||||
|
("lightsalmon".to_string(), "#FFA07A".to_string()),
|
||||||
|
("lightseagreen".to_string(), "#20B2AA".to_string()),
|
||||||
|
("lightskyblue".to_string(), "#87CEFA".to_string()),
|
||||||
|
("lightslategray".to_string(), "#778899".to_string()),
|
||||||
|
("lightslategrey".to_string(), "#778899".to_string()),
|
||||||
|
("lightsteelblue".to_string(), "#B0C4DE".to_string()),
|
||||||
|
("lightyellow".to_string(), "#FFFFE0".to_string()),
|
||||||
|
("lime".to_string(), "#00FF00".to_string()),
|
||||||
|
("limegreen".to_string(), "#32CD32".to_string()),
|
||||||
|
("linen".to_string(), "#FAF0E6".to_string()),
|
||||||
|
("magenta".to_string(), "#FF00FF".to_string()),
|
||||||
|
("maroon".to_string(), "#800000".to_string()),
|
||||||
|
("mediumaquamarine".to_string(), "#66CDAA".to_string()),
|
||||||
|
("mediumblue".to_string(), "#0000CD".to_string()),
|
||||||
|
("mediumorchid".to_string(), "#BA55D3".to_string()),
|
||||||
|
("mediumpurple".to_string(), "#9370DB".to_string()),
|
||||||
|
("mediumseagreen".to_string(), "#3CB371".to_string()),
|
||||||
|
("mediumslateblue".to_string(), "#7B68EE".to_string()),
|
||||||
|
("mediumspringgreen".to_string(), "#00FA9A".to_string()),
|
||||||
|
("mediumturquoise".to_string(), "#48D1CC".to_string()),
|
||||||
|
("mediumvioletred".to_string(), "#C71585".to_string()),
|
||||||
|
("midnightblue".to_string(), "#191970".to_string()),
|
||||||
|
("mintcream".to_string(), "#F5FFFA".to_string()),
|
||||||
|
("mistyrose".to_string(), "#FFE4E1".to_string()),
|
||||||
|
("moccasin".to_string(), "#FFE4B5".to_string()),
|
||||||
|
("navajowhite".to_string(), "#FFDEAD".to_string()),
|
||||||
|
("navy".to_string(), "#000080".to_string()),
|
||||||
|
("oldlace".to_string(), "#FDF5E6".to_string()),
|
||||||
|
("olive".to_string(), "#808000".to_string()),
|
||||||
|
("olivedrab".to_string(), "#6B8E23".to_string()),
|
||||||
|
("orange".to_string(), "#FFA500".to_string()),
|
||||||
|
("orangered".to_string(), "#FF4500".to_string()),
|
||||||
|
("orchid".to_string(), "#DA70D6".to_string()),
|
||||||
|
("palegoldenrod".to_string(), "#EEE8AA".to_string()),
|
||||||
|
("palegreen".to_string(), "#98FB98".to_string()),
|
||||||
|
("paleturquoise".to_string(), "#AFEEEE".to_string()),
|
||||||
|
("palevioletred".to_string(), "#DB7093".to_string()),
|
||||||
|
("papayawhip".to_string(), "#FFEFD5".to_string()),
|
||||||
|
("peachpuff".to_string(), "#FFDAB9".to_string()),
|
||||||
|
("peru".to_string(), "#CD853F".to_string()),
|
||||||
|
("pink".to_string(), "#FFC0CB".to_string()),
|
||||||
|
("plum".to_string(), "#DDA0DD".to_string()),
|
||||||
|
("powderblue".to_string(), "#B0E0E6".to_string()),
|
||||||
|
("purple".to_string(), "#800080".to_string()),
|
||||||
|
("rebeccapurple".to_string(), "#663399".to_string()),
|
||||||
|
("red".to_string(), "#FF0000".to_string()),
|
||||||
|
("rosybrown".to_string(), "#BC8F8F".to_string()),
|
||||||
|
("royalblue".to_string(), "#4169E1".to_string()),
|
||||||
|
("saddlebrown".to_string(), "#8B4513".to_string()),
|
||||||
|
("salmon".to_string(), "#FA8072".to_string()),
|
||||||
|
("sandybrown".to_string(), "#F4A460".to_string()),
|
||||||
|
("seagreen".to_string(), "#2E8B57".to_string()),
|
||||||
|
("seashell".to_string(), "#FFF5EE".to_string()),
|
||||||
|
("sienna".to_string(), "#A0522D".to_string()),
|
||||||
|
("silver".to_string(), "#C0C0C0".to_string()),
|
||||||
|
("skyblue".to_string(), "#87CEEB".to_string()),
|
||||||
|
("slateblue".to_string(), "#6A5ACD".to_string()),
|
||||||
|
("slategray".to_string(), "#708090".to_string()),
|
||||||
|
("slategrey".to_string(), "#708090".to_string()),
|
||||||
|
("snow".to_string(), "#FFFAFA".to_string()),
|
||||||
|
("springgreen".to_string(), "#00FF7F".to_string()),
|
||||||
|
("steelblue".to_string(), "#4682B4".to_string()),
|
||||||
|
("tan".to_string(), "#D2B48C".to_string()),
|
||||||
|
("teal".to_string(), "#008080".to_string()),
|
||||||
|
("thistle".to_string(), "#D8BFD8".to_string()),
|
||||||
|
("tomato".to_string(), "#FF6347".to_string()),
|
||||||
|
("turquoise".to_string(), "#40E0D0".to_string()),
|
||||||
|
("violet".to_string(), "#EE82EE".to_string()),
|
||||||
|
("wheat".to_string(), "#F5DEB3".to_string()),
|
||||||
|
("white".to_string(), "#FFFFFF".to_string()),
|
||||||
|
("whitesmoke".to_string(), "#F5F5F5".to_string()),
|
||||||
|
("yellow".to_string(), "#FFFF00".to_string()),
|
||||||
|
("yellowgreen".to_string(), "#9ACD32".to_string()),
|
||||||
|
])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Color {
|
||||||
|
r: u8,
|
||||||
|
g: u8,
|
||||||
|
b: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Color {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self> {
|
||||||
|
let s = if !s.starts_with("#") {
|
||||||
|
if let Some(s) = COLORS.get(s) {
|
||||||
|
s
|
||||||
|
} else {
|
||||||
|
return Err(Error::msg(format!("invalid color: {}", s)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s
|
||||||
|
};
|
||||||
|
let r = u8::from_str_radix(&s[1..3], 16)?;
|
||||||
|
let g = u8::from_str_radix(&s[3..5], 16)?;
|
||||||
|
let b = u8::from_str_radix(&s[5..], 16)?;
|
||||||
|
Ok(Self { r, g, b })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Color {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Color {
|
||||||
|
pub fn to_rgb(&self) -> Vec<u8> {
|
||||||
|
vec![self.r, self.g, self.b]
|
||||||
|
}
|
||||||
|
}
|
||||||
126
src/lib.rs
126
src/lib.rs
@@ -1,22 +1,31 @@
|
|||||||
mod bioformats;
|
mod bioformats;
|
||||||
|
|
||||||
pub mod axes;
|
pub mod axes;
|
||||||
|
pub mod metadata;
|
||||||
#[cfg(feature = "python")]
|
#[cfg(feature = "python")]
|
||||||
mod py;
|
mod py;
|
||||||
pub mod reader;
|
pub mod reader;
|
||||||
pub mod stats;
|
pub mod stats;
|
||||||
pub mod view;
|
pub mod view;
|
||||||
|
|
||||||
|
pub mod colors;
|
||||||
|
#[cfg(feature = "movie")]
|
||||||
|
pub mod movie;
|
||||||
|
#[cfg(feature = "tiff")]
|
||||||
|
pub mod tiff;
|
||||||
|
|
||||||
|
pub use bioformats::download_bioformats;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::stats::MinMax;
|
|
||||||
use ndarray::{Array, Array4, Array5, NewAxis};
|
|
||||||
use rayon::prelude::*;
|
|
||||||
|
|
||||||
use crate::axes::Axis;
|
use crate::axes::Axis;
|
||||||
use crate::reader::{Frame, Reader};
|
use crate::reader::{Frame, Reader};
|
||||||
|
use crate::stats::MinMax;
|
||||||
|
use crate::view::Item;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use ndarray::{s, Array2};
|
use ndarray::{Array, Array4, Array5, NewAxis};
|
||||||
|
use ndarray::{Array2, s};
|
||||||
|
use rayon::prelude::*;
|
||||||
|
|
||||||
fn open(file: &str) -> Result<Reader> {
|
fn open(file: &str) -> Result<Reader> {
|
||||||
let path = std::env::current_dir()?
|
let path = std::env::current_dir()?
|
||||||
@@ -137,7 +146,6 @@ mod tests {
|
|||||||
let a = a.slice(s![0, ..5, 0, NewAxis, 100..200, ..]);
|
let a = a.slice(s![0, ..5, 0, NewAxis, 100..200, ..]);
|
||||||
let v = view.slice(s![0, ..5, 0, NewAxis, 100..200, ..])?;
|
let v = view.slice(s![0, ..5, 0, NewAxis, 100..200, ..])?;
|
||||||
assert_eq!(v.shape(), a.shape());
|
assert_eq!(v.shape(), a.shape());
|
||||||
println!("\nshape: {:?}", v.shape());
|
|
||||||
let a = a.slice(s![NewAxis, .., .., NewAxis, .., .., NewAxis]);
|
let a = a.slice(s![NewAxis, .., .., NewAxis, .., .., NewAxis]);
|
||||||
let v = v.slice(s![NewAxis, .., .., NewAxis, .., .., NewAxis])?;
|
let v = v.slice(s![NewAxis, .., .., NewAxis, .., .., NewAxis])?;
|
||||||
assert_eq!(v.shape(), a.shape());
|
assert_eq!(v.shape(), a.shape());
|
||||||
@@ -160,10 +168,7 @@ mod tests {
|
|||||||
assert_eq!(view.shape(), a.shape());
|
assert_eq!(view.shape(), a.shape());
|
||||||
let b: Array5<usize> = view.clone().try_into()?;
|
let b: Array5<usize> = view.clone().try_into()?;
|
||||||
assert_eq!(b.shape(), a.shape());
|
assert_eq!(b.shape(), a.shape());
|
||||||
|
|
||||||
println!("{:?}", view.axes());
|
|
||||||
let view = view.permute_axes(&[Axis::X, Axis::Z, Axis::Y])?;
|
let view = view.permute_axes(&[Axis::X, Axis::Z, Axis::Y])?;
|
||||||
println!("{:?}", view.axes());
|
|
||||||
let a = a.permuted_axes([4, 1, 2, 0, 3]);
|
let a = a.permuted_axes([4, 1, 2, 0, 3]);
|
||||||
assert_eq!(view.shape(), a.shape());
|
assert_eq!(view.shape(), a.shape());
|
||||||
let b: Array5<usize> = view.clone().try_into()?;
|
let b: Array5<usize> = view.clone().try_into()?;
|
||||||
@@ -259,4 +264,107 @@ mod tests {
|
|||||||
assert_eq!(d.shape(), [1024, 1024]);
|
assert_eq!(d.shape(), [1024, 1024]);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn item() -> Result<()> {
|
||||||
|
let file = "1xp53-01-AP1.czi";
|
||||||
|
let reader = open(file)?;
|
||||||
|
let view = reader.view();
|
||||||
|
let a = view.slice(s![.., 0, 0, 0, 0])?;
|
||||||
|
let b = a.slice(s![0])?;
|
||||||
|
let item = b.item::<usize>()?;
|
||||||
|
assert_eq!(item, 2);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn slice_cztyx() -> Result<()> {
|
||||||
|
let file = "1xp53-01-AP1.czi";
|
||||||
|
let reader = open(file)?;
|
||||||
|
let view = reader.view().max_proj(Axis::Z)?.into_dyn();
|
||||||
|
println!("view.axes: {:?}", view.get_axes());
|
||||||
|
println!("view.slice: {:?}", view.get_slice());
|
||||||
|
let r = view.reset_axes()?;
|
||||||
|
println!("r.axes: {:?}", r.get_axes());
|
||||||
|
println!("r.slice: {:?}", r.get_slice());
|
||||||
|
let a = view.slice_cztyx(s![0, 0, 0, .., ..])?;
|
||||||
|
println!("a.axes: {:?}", a.get_axes());
|
||||||
|
println!("a.slice: {:?}", a.get_slice());
|
||||||
|
assert_eq!(a.axes(), [Axis::Y, Axis::X]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reset_axes() -> Result<()> {
|
||||||
|
let file = "1xp53-01-AP1.czi";
|
||||||
|
let reader = open(file)?;
|
||||||
|
let view = reader.view().max_proj(Axis::Z)?;
|
||||||
|
let view = view.reset_axes()?;
|
||||||
|
assert_eq!(view.axes(), [Axis::C, Axis::New, Axis::T, Axis::Y, Axis::X]);
|
||||||
|
let a = view.as_array::<f64>()?;
|
||||||
|
assert_eq!(a.ndim(), 5);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reset_axes2() -> Result<()> {
|
||||||
|
let file = "Experiment-2029.czi";
|
||||||
|
let reader = open(file)?;
|
||||||
|
let view = reader.view().squeeze()?;
|
||||||
|
let a = view.reset_axes()?;
|
||||||
|
assert_eq!(a.axes(), [Axis::C, Axis::Z, Axis::T, Axis::Y, Axis::X]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reset_axes3() -> Result<()> {
|
||||||
|
let file = "Experiment-2029.czi";
|
||||||
|
let reader = open(file)?;
|
||||||
|
let view4 = reader.view().squeeze()?;
|
||||||
|
let view = view4.max_proj(Axis::Z)?.into_dyn();
|
||||||
|
let slice = view.slice_cztyx(s![0, .., .., .., ..])?.into_dyn();
|
||||||
|
let a = slice.as_array::<u16>()?;
|
||||||
|
assert_eq!(slice.shape(), [1, 10, 1280, 1280]);
|
||||||
|
assert_eq!(a.shape(), [1, 10, 1280, 1280]);
|
||||||
|
let r = slice.reset_axes()?;
|
||||||
|
let b = r.as_array::<u16>()?;
|
||||||
|
assert_eq!(r.shape(), [1, 1, 10, 1280, 1280]);
|
||||||
|
assert_eq!(b.shape(), [1, 1, 10, 1280, 1280]);
|
||||||
|
let q = slice.max_proj(Axis::C)?.max_proj(Axis::T)?;
|
||||||
|
let c = q.as_array::<f64>()?;
|
||||||
|
assert_eq!(q.shape(), [1, 1280, 1280]);
|
||||||
|
assert_eq!(c.shape(), [1, 1280, 1280]);
|
||||||
|
let p = q.reset_axes()?;
|
||||||
|
let d = p.as_array::<u16>()?;
|
||||||
|
println!("axes: {:?}", p.get_axes());
|
||||||
|
println!("operations: {:?}", p.get_operations());
|
||||||
|
println!("slice: {:?}", p.get_slice());
|
||||||
|
assert_eq!(p.shape(), [1, 1, 1, 1280, 1280]);
|
||||||
|
assert_eq!(d.shape(), [1, 1, 1, 1280, 1280]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn max() -> Result<()> {
|
||||||
|
let file = "Experiment-2029.czi";
|
||||||
|
let reader = open(file)?;
|
||||||
|
let view = reader.view();
|
||||||
|
let m = view.max_proj(Axis::T)?;
|
||||||
|
let a = m.as_array::<u16>()?;
|
||||||
|
assert_eq!(m.shape(), [2, 1, 1280, 1280]);
|
||||||
|
assert_eq!(a.shape(), [2, 1, 1280, 1280]);
|
||||||
|
let mc = view.max_proj(Axis::C)?;
|
||||||
|
let a = mc.as_array::<u16>()?;
|
||||||
|
assert_eq!(mc.shape(), [1, 10, 1280, 1280]);
|
||||||
|
assert_eq!(a.shape(), [1, 10, 1280, 1280]);
|
||||||
|
let mz = mc.max_proj(Axis::Z)?;
|
||||||
|
let a = mz.as_array::<u16>()?;
|
||||||
|
assert_eq!(mz.shape(), [10, 1280, 1280]);
|
||||||
|
assert_eq!(a.shape(), [10, 1280, 1280]);
|
||||||
|
let mt = mz.max_proj(Axis::T)?;
|
||||||
|
let a = mt.as_array::<u16>()?;
|
||||||
|
assert_eq!(mt.shape(), [1280, 1280]);
|
||||||
|
assert_eq!(a.shape(), [1280, 1280]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
110
src/main.rs
Normal file
110
src/main.rs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
#[cfg(feature = "movie")]
|
||||||
|
use ndbioimage::movie::MovieOptions;
|
||||||
|
use ndbioimage::reader::split_path_and_series;
|
||||||
|
#[cfg(feature = "tiff")]
|
||||||
|
use ndbioimage::tiff::TiffOptions;
|
||||||
|
use ndbioimage::view::View;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(arg_required_else_help = true, version, about, long_about = None, propagate_version = true)]
|
||||||
|
struct Cli {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: Commands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum Commands {
|
||||||
|
/// Print some metadata
|
||||||
|
Info {
|
||||||
|
#[arg(value_name = "FILE", num_args(1..))]
|
||||||
|
file: Vec<PathBuf>,
|
||||||
|
},
|
||||||
|
/// Save the image as tiff file
|
||||||
|
#[cfg(feature = "tiff")]
|
||||||
|
Tiff {
|
||||||
|
#[arg(value_name = "FILE", num_args(1..))]
|
||||||
|
file: Vec<PathBuf>,
|
||||||
|
#[arg(short, long, value_name = "COLOR", num_args(1..))]
|
||||||
|
colors: Vec<String>,
|
||||||
|
#[arg(short, long, value_name = "OVERWRITE")]
|
||||||
|
overwrite: bool,
|
||||||
|
},
|
||||||
|
/// Save the image as mp4 file
|
||||||
|
#[cfg(feature = "movie")]
|
||||||
|
Movie {
|
||||||
|
#[arg(value_name = "FILE", num_args(1..))]
|
||||||
|
file: Vec<PathBuf>,
|
||||||
|
#[arg(short, long, value_name = "Velocity", default_value = "3.6")]
|
||||||
|
velocity: f64,
|
||||||
|
#[arg(short, long, value_name = "BRIGHTNESS")]
|
||||||
|
brightness: Vec<f64>,
|
||||||
|
#[arg(short, long, value_name = "SCALE", default_value = "1.0")]
|
||||||
|
scale: f64,
|
||||||
|
#[arg(short, long, value_name = "COLOR", num_args(1..))]
|
||||||
|
colors: Vec<String>,
|
||||||
|
#[arg(short, long, value_name = "OVERWRITE")]
|
||||||
|
overwrite: bool,
|
||||||
|
},
|
||||||
|
/// Download the BioFormats jar into the correct folder
|
||||||
|
DownloadBioFormats {
|
||||||
|
#[arg(short, long, value_name = "GPL_FORMATS")]
|
||||||
|
gpl_formats: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn main() -> Result<()> {
|
||||||
|
let cli = Cli::parse();
|
||||||
|
match &cli.command {
|
||||||
|
Commands::Info { file } => {
|
||||||
|
for f in file {
|
||||||
|
let (path, series) = split_path_and_series(f)?;
|
||||||
|
let view = View::from_path(path, series.unwrap_or(0))?.squeeze()?;
|
||||||
|
println!("{}", view.summary()?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "tiff")]
|
||||||
|
Commands::Tiff {
|
||||||
|
file,
|
||||||
|
colors,
|
||||||
|
overwrite,
|
||||||
|
} => {
|
||||||
|
let mut options = TiffOptions::new(true, None, colors.clone(), *overwrite)?;
|
||||||
|
options.enable_bar()?;
|
||||||
|
for f in file {
|
||||||
|
let (path, series) = split_path_and_series(f)?;
|
||||||
|
let view = View::from_path(path, series.unwrap_or(0))?;
|
||||||
|
view.save_as_tiff(f.with_extension("tiff"), &options)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "movie")]
|
||||||
|
Commands::Movie {
|
||||||
|
file,
|
||||||
|
velocity: speed,
|
||||||
|
brightness,
|
||||||
|
scale,
|
||||||
|
colors,
|
||||||
|
overwrite,
|
||||||
|
} => {
|
||||||
|
let options = MovieOptions::new(
|
||||||
|
*speed,
|
||||||
|
brightness.to_vec(),
|
||||||
|
*scale,
|
||||||
|
colors.to_vec(),
|
||||||
|
*overwrite,
|
||||||
|
)?;
|
||||||
|
for f in file {
|
||||||
|
let (path, series) = split_path_and_series(f)?;
|
||||||
|
let view = View::from_path(path, series.unwrap_or(0))?;
|
||||||
|
view.save_as_movie(f.with_extension("mp4"), &options)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Commands::DownloadBioFormats { gpl_formats } => {
|
||||||
|
ndbioimage::download_bioformats(*gpl_formats)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
372
src/metadata.rs
Normal file
372
src/metadata.rs
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
use anyhow::{Result, anyhow};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use ome_metadata::Ome;
|
||||||
|
use ome_metadata::ome::{
|
||||||
|
BinningType, Convert, Image, Instrument, Objective, Pixels, UnitsLength, UnitsTime,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Metadata for Ome {
|
||||||
|
fn get_instrument(&self) -> Option<&Instrument> {
|
||||||
|
let instrument_id = self.get_image()?.instrument_ref.as_ref()?.id.clone();
|
||||||
|
self.instrument
|
||||||
|
.as_ref()?
|
||||||
|
.iter()
|
||||||
|
.find(|i| i.id == instrument_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_image(&self) -> Option<&Image> {
|
||||||
|
if let Some(image) = &self.image {
|
||||||
|
if !image.is_empty() {
|
||||||
|
Some(&image[0])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Metadata {
|
||||||
|
fn get_instrument(&self) -> Option<&Instrument>;
|
||||||
|
fn get_image(&self) -> Option<&Image>;
|
||||||
|
|
||||||
|
fn get_pixels(&self) -> Option<&Pixels> {
|
||||||
|
if let Some(image) = self.get_image() {
|
||||||
|
Some(&image.pixels)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_objective(&self) -> Option<&Objective> {
|
||||||
|
let objective_id = self.get_image()?.objective_settings.as_ref()?.id.clone();
|
||||||
|
self.get_instrument()?
|
||||||
|
.objective
|
||||||
|
.iter()
|
||||||
|
.find(|o| o.id == objective_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_tube_lens(&self) -> Option<Objective> {
|
||||||
|
Some(Objective {
|
||||||
|
manufacturer: None,
|
||||||
|
model: Some("Unknown".to_string()),
|
||||||
|
serial_number: None,
|
||||||
|
lot_number: None,
|
||||||
|
id: "TubeLens:1".to_string(),
|
||||||
|
correction: None,
|
||||||
|
immersion: None,
|
||||||
|
lens_na: None,
|
||||||
|
nominal_magnification: Some(1.0),
|
||||||
|
calibrated_magnification: None,
|
||||||
|
working_distance: None,
|
||||||
|
working_distance_unit: UnitsLength::um,
|
||||||
|
iris: None,
|
||||||
|
annotation_ref: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// shape of the data along cztyx axes
|
||||||
|
fn shape(&self) -> Result<(usize, usize, usize, usize, usize)> {
|
||||||
|
if let Some(pixels) = self.get_pixels() {
|
||||||
|
Ok((
|
||||||
|
pixels.size_c as usize,
|
||||||
|
pixels.size_z as usize,
|
||||||
|
pixels.size_t as usize,
|
||||||
|
pixels.size_y as usize,
|
||||||
|
pixels.size_x as usize,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("No image or pixels found"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// pixel size in nm
|
||||||
|
fn pixel_size(&self) -> Result<Option<f64>> {
|
||||||
|
if let Some(pixels) = self.get_pixels() {
|
||||||
|
match (pixels.physical_size_x, pixels.physical_size_y) {
|
||||||
|
(Some(x), Some(y)) => Ok(Some(
|
||||||
|
(pixels
|
||||||
|
.physical_size_x_unit
|
||||||
|
.convert(&UnitsLength::nm, x as f64)?
|
||||||
|
+ pixels
|
||||||
|
.physical_size_y_unit
|
||||||
|
.convert(&UnitsLength::nm, y as f64)?)
|
||||||
|
/ 2f64,
|
||||||
|
)),
|
||||||
|
(Some(x), None) => Ok(Some(
|
||||||
|
pixels
|
||||||
|
.physical_size_x_unit
|
||||||
|
.convert(&UnitsLength::nm, x as f64)?
|
||||||
|
.powi(2),
|
||||||
|
)),
|
||||||
|
(None, Some(y)) => Ok(Some(
|
||||||
|
pixels
|
||||||
|
.physical_size_y_unit
|
||||||
|
.convert(&UnitsLength::nm, y as f64)?
|
||||||
|
.powi(2),
|
||||||
|
)),
|
||||||
|
_ => Ok(None),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// distance between planes in z-stack in nm
|
||||||
|
fn delta_z(&self) -> Result<Option<f64>> {
|
||||||
|
if let Some(pixels) = self.get_pixels() {
|
||||||
|
if let Some(z) = pixels.physical_size_z {
|
||||||
|
return Ok(Some(
|
||||||
|
pixels
|
||||||
|
.physical_size_z_unit
|
||||||
|
.convert(&UnitsLength::nm, z as f64)?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// time interval in seconds for time-lapse images
|
||||||
|
fn time_interval(&self) -> Result<Option<f64>> {
|
||||||
|
if let Some(pixels) = self.get_pixels() {
|
||||||
|
if let Some(plane) = &pixels.plane {
|
||||||
|
if let Some(t) = plane.iter().map(|p| p.the_t).max() {
|
||||||
|
if t > 0 {
|
||||||
|
let plane_a = plane
|
||||||
|
.iter()
|
||||||
|
.find(|p| (p.the_c == 0) && (p.the_z == 0) && (p.the_t == 0));
|
||||||
|
let plane_b = plane
|
||||||
|
.iter()
|
||||||
|
.find(|p| (p.the_c == 0) && (p.the_z == 0) && (p.the_t == t));
|
||||||
|
if let (Some(a), Some(b)) = (plane_a, plane_b) {
|
||||||
|
if let (Some(a_t), Some(b_t)) = (a.delta_t, b.delta_t) {
|
||||||
|
return Ok(Some(
|
||||||
|
(b.delta_t_unit.convert(&UnitsTime::s, b_t as f64)?
|
||||||
|
- a.delta_t_unit.convert(&UnitsTime::s, a_t as f64)?)
|
||||||
|
.abs()
|
||||||
|
/ (t as f64),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// exposure time for channel, z=0 and t=0
|
||||||
|
fn exposure_time(&self, channel: usize) -> Result<Option<f64>> {
|
||||||
|
let c = channel as i32;
|
||||||
|
if let Some(pixels) = self.get_pixels() {
|
||||||
|
if let Some(plane) = &pixels.plane {
|
||||||
|
if let Some(p) = plane
|
||||||
|
.iter()
|
||||||
|
.find(|p| (p.the_c == c) && (p.the_z == 0) && (p.the_t == 0))
|
||||||
|
{
|
||||||
|
if let Some(t) = p.exposure_time {
|
||||||
|
return Ok(Some(p.exposure_time_unit.convert(&UnitsTime::s, t as f64)?));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn binning(&self, channel: usize) -> Option<usize> {
|
||||||
|
match self
|
||||||
|
.get_pixels()?
|
||||||
|
.channel
|
||||||
|
.get(channel)?
|
||||||
|
.detector_settings
|
||||||
|
.as_ref()?
|
||||||
|
.binning
|
||||||
|
.as_ref()?
|
||||||
|
{
|
||||||
|
BinningType::_1X1 => Some(1),
|
||||||
|
BinningType::_2X2 => Some(2),
|
||||||
|
BinningType::_4X4 => Some(4),
|
||||||
|
BinningType::_8X8 => Some(8),
|
||||||
|
BinningType::Other => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn laser_wavelengths(&self, channel: usize) -> Result<Option<f64>> {
|
||||||
|
if let Some(pixels) = self.get_pixels() {
|
||||||
|
if let Some(channel) = pixels.channel.get(channel) {
|
||||||
|
if let Some(w) = channel.excitation_wavelength {
|
||||||
|
return Ok(Some(
|
||||||
|
channel
|
||||||
|
.excitation_wavelength_unit
|
||||||
|
.convert(&UnitsLength::nm, w as f64)?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn laser_powers(&self, channel: usize) -> Result<Option<f64>> {
|
||||||
|
if let Some(pixels) = self.get_pixels() {
|
||||||
|
if let Some(channel) = pixels.channel.get(channel) {
|
||||||
|
if let Some(ls) = &channel.light_source_settings {
|
||||||
|
if let Some(a) = ls.attenuation {
|
||||||
|
return if (0. ..=1.).contains(&a) {
|
||||||
|
Ok(Some(1f64 - (a as f64)))
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Invalid attenuation value"))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn objective_name(&self) -> Option<String> {
|
||||||
|
Some(self.get_objective()?.model.as_ref()?.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn magnification(&self) -> Option<f64> {
|
||||||
|
Some(
|
||||||
|
(self.get_objective()?.nominal_magnification? as f64)
|
||||||
|
* (self.get_tube_lens()?.nominal_magnification? as f64),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tube_lens_name(&self) -> Option<String> {
|
||||||
|
self.get_tube_lens()?.model.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_set_name(&self, channel: usize) -> Option<String> {
|
||||||
|
let filter_set_id = self
|
||||||
|
.get_pixels()?
|
||||||
|
.channel
|
||||||
|
.get(channel)?
|
||||||
|
.filter_set_ref
|
||||||
|
.as_ref()?
|
||||||
|
.id
|
||||||
|
.clone();
|
||||||
|
self.get_instrument()
|
||||||
|
.as_ref()?
|
||||||
|
.filter_set
|
||||||
|
.iter()
|
||||||
|
.find(|f| f.id == filter_set_id)?
|
||||||
|
.model
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gain(&self, channel: usize) -> Option<f64> {
|
||||||
|
if let Some(pixels) = self.get_pixels() {
|
||||||
|
Some(
|
||||||
|
*pixels
|
||||||
|
.channel
|
||||||
|
.get(channel)?
|
||||||
|
.detector_settings
|
||||||
|
.as_ref()?
|
||||||
|
.gain
|
||||||
|
.as_ref()? as f64,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn summary(&self) -> Result<String> {
|
||||||
|
let size_c = if let Some(pixels) = self.get_pixels() {
|
||||||
|
pixels.channel.len()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let mut s = "".to_string();
|
||||||
|
if let Ok(Some(pixel_size)) = self.pixel_size() {
|
||||||
|
s.push_str(&format!("pixel size: {pixel_size:.2} nm\n"));
|
||||||
|
}
|
||||||
|
if let Ok(Some(delta_z)) = self.delta_z() {
|
||||||
|
s.push_str(&format!("z-interval: {delta_z:.2} nm\n"))
|
||||||
|
}
|
||||||
|
if let Ok(Some(time_interval)) = self.time_interval() {
|
||||||
|
s.push_str(&format!("time interval: {time_interval:.2} s\n"))
|
||||||
|
}
|
||||||
|
let exposure_time = (0..size_c)
|
||||||
|
.map(|c| self.exposure_time(c))
|
||||||
|
.collect::<Result<Vec<_>>>()?
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !exposure_time.is_empty() {
|
||||||
|
s.push_str(&format!(
|
||||||
|
"exposure time: {}\n",
|
||||||
|
exposure_time.into_iter().join(" | ")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if let Some(magnification) = self.magnification() {
|
||||||
|
s.push_str(&format!("magnification: {magnification}x\n"))
|
||||||
|
}
|
||||||
|
if let Some(objective_name) = self.objective_name() {
|
||||||
|
s.push_str(&format!("objective: {objective_name}\n"))
|
||||||
|
}
|
||||||
|
if let Some(tube_lens_name) = self.tube_lens_name() {
|
||||||
|
s.push_str(&format!("tube lens: {tube_lens_name}\n"))
|
||||||
|
}
|
||||||
|
let filter_set_name = (0..size_c)
|
||||||
|
.map(|c| self.filter_set_name(c))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !filter_set_name.is_empty() {
|
||||||
|
s.push_str(&format!(
|
||||||
|
"filter set: {}\n",
|
||||||
|
filter_set_name.into_iter().join(" | ")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let gain = (0..size_c)
|
||||||
|
.map(|c| self.gain(c))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !gain.is_empty() {
|
||||||
|
s.push_str(&format!("gain: {}\n", gain.into_iter().join(" ")));
|
||||||
|
}
|
||||||
|
let laser_wavelengths = (0..size_c)
|
||||||
|
.map(|c| self.laser_wavelengths(c))
|
||||||
|
.collect::<Result<Vec<_>>>()?
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !laser_wavelengths.is_empty() {
|
||||||
|
s.push_str(&format!(
|
||||||
|
"laser colors: {} nm\n",
|
||||||
|
laser_wavelengths.into_iter().join(" | ")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let laser_powers = (0..size_c)
|
||||||
|
.map(|c| self.laser_powers(c))
|
||||||
|
.collect::<Result<Vec<_>>>()?
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !laser_powers.is_empty() {
|
||||||
|
s.push_str(&format!(
|
||||||
|
"laser powers: {}\n",
|
||||||
|
laser_powers.into_iter().join(" | ")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let binning = (0..size_c)
|
||||||
|
.map(|c| self.binning(c))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !binning.is_empty() {
|
||||||
|
s.push_str(&format!(
|
||||||
|
"binning: {}\n",
|
||||||
|
binning.into_iter().join(" | ")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(s.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
258
src/movie.rs
Normal file
258
src/movie.rs
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
use crate::axes::Axis;
|
||||||
|
use crate::colors::Color;
|
||||||
|
use crate::view::View;
|
||||||
|
use anyhow::{Result, anyhow};
|
||||||
|
use ffmpeg_sidecar::command::FfmpegCommand;
|
||||||
|
use ffmpeg_sidecar::download::auto_download;
|
||||||
|
use ffmpeg_sidecar::event::{FfmpegEvent, LogLevel};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use ndarray::{Array2, Array3, Dimension, IxDyn, s, stack};
|
||||||
|
use ordered_float::OrderedFloat;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
pub struct MovieOptions {
|
||||||
|
velocity: f64,
|
||||||
|
brightness: Vec<f64>,
|
||||||
|
scale: f64,
|
||||||
|
colors: Option<Vec<Vec<u8>>>,
|
||||||
|
overwrite: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MovieOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
velocity: 3.6,
|
||||||
|
brightness: Vec::new(),
|
||||||
|
scale: 1.0,
|
||||||
|
colors: None,
|
||||||
|
overwrite: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MovieOptions {
|
||||||
|
pub fn new(
|
||||||
|
velocity: f64,
|
||||||
|
brightness: Vec<f64>,
|
||||||
|
scale: f64,
|
||||||
|
colors: Vec<String>,
|
||||||
|
overwrite: bool,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let colors = if colors.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let colors = colors
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.parse::<Color>())
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
Some(colors.into_iter().map(|c| c.to_rgb()).collect())
|
||||||
|
};
|
||||||
|
Ok(Self {
|
||||||
|
velocity,
|
||||||
|
brightness,
|
||||||
|
scale,
|
||||||
|
colors,
|
||||||
|
overwrite,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_velocity(&mut self, velocity: f64) {
|
||||||
|
self.velocity = velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_brightness(&mut self, brightness: Vec<f64>) {
|
||||||
|
self.brightness = brightness;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_scale(&mut self, scale: f64) {
|
||||||
|
self.scale = scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_colors(&mut self, colors: &[String]) -> Result<()> {
|
||||||
|
let colors = colors
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.parse::<Color>())
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
self.colors = Some(colors.into_iter().map(|c| c.to_rgb()).collect());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_overwrite(&mut self, overwrite: bool) {
|
||||||
|
self.overwrite = overwrite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_ab(tyx: View<IxDyn>) -> Result<(f64, f64)> {
|
||||||
|
let s = tyx
|
||||||
|
.as_array::<f64>()?
|
||||||
|
.iter()
|
||||||
|
.filter_map(|&i| {
|
||||||
|
if i == 0.0 || !i.is_finite() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(OrderedFloat::from(i))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sorted_unstable()
|
||||||
|
.map(f64::from)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let n = s.len();
|
||||||
|
let mut a = s[n / 100];
|
||||||
|
let mut b = s[n - n / 100 - 1];
|
||||||
|
if a == b {
|
||||||
|
a = s[0];
|
||||||
|
b = s[n - 1];
|
||||||
|
}
|
||||||
|
if a == b {
|
||||||
|
a = 1.0;
|
||||||
|
b = 1.0;
|
||||||
|
}
|
||||||
|
Ok((a, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cframe(frame: Array2<f64>, color: &[u8], a: f64, b: f64) -> Array3<f64> {
|
||||||
|
let frame = (frame - a) / (b - a);
|
||||||
|
let color = color
|
||||||
|
.iter()
|
||||||
|
.map(|&c| (c as f64) / 255.0)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let frame = color
|
||||||
|
.iter()
|
||||||
|
.map(|&c| (c * &frame).to_owned())
|
||||||
|
.collect::<Vec<Array2<f64>>>();
|
||||||
|
let view = frame.iter().map(|c| c.view()).collect::<Vec<_>>();
|
||||||
|
stack(ndarray::Axis(0), &view).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D> View<D>
|
||||||
|
where
|
||||||
|
D: Dimension,
|
||||||
|
{
|
||||||
|
pub fn save_as_movie<P>(&self, path: P, options: &MovieOptions) -> Result<()>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let path = path.as_ref().to_path_buf();
|
||||||
|
if path.exists() {
|
||||||
|
if options.overwrite {
|
||||||
|
std::fs::remove_file(&path)?;
|
||||||
|
} else {
|
||||||
|
return Err(anyhow!("File {} already exists", path.display()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let view = self.max_proj(Axis::Z)?.reset_axes()?;
|
||||||
|
let velocity = options.velocity;
|
||||||
|
let mut brightness = options.brightness.clone();
|
||||||
|
let scale = options.scale;
|
||||||
|
let shape = view.shape();
|
||||||
|
let size_c = shape[0];
|
||||||
|
let size_t = shape[2];
|
||||||
|
let size_x = shape[3];
|
||||||
|
let size_y = 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);
|
||||||
|
|
||||||
|
while brightness.len() < size_c {
|
||||||
|
brightness.push(1.0);
|
||||||
|
}
|
||||||
|
let mut colors = if let Some(colors) = options.colors.as_ref() {
|
||||||
|
colors.to_vec()
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
while colors.len() < size_c {
|
||||||
|
colors.push(vec![255, 255, 255]);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto_download()?;
|
||||||
|
let mut movie = FfmpegCommand::new()
|
||||||
|
.args([
|
||||||
|
"-f",
|
||||||
|
"rawvideo",
|
||||||
|
"-pix_fmt",
|
||||||
|
"gray",
|
||||||
|
"-s",
|
||||||
|
&format!("{}x{}", size_x, size_y),
|
||||||
|
])
|
||||||
|
.input("-")
|
||||||
|
.args([
|
||||||
|
"-vcodec",
|
||||||
|
"libx264",
|
||||||
|
"-preset",
|
||||||
|
"veryslow",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-r",
|
||||||
|
"7",
|
||||||
|
"-vf",
|
||||||
|
&format!("setpts={velocity}*PTS,scale={shape_x}:{shape_y}:flags=neighbor"),
|
||||||
|
])
|
||||||
|
.output(path.to_str().expect("path cannot be converted to string"))
|
||||||
|
.spawn()?;
|
||||||
|
let mut stdin = movie.take_stdin().unwrap();
|
||||||
|
|
||||||
|
let ab = (0..size_c)
|
||||||
|
.map(|c| match view.slice(s![c, .., .., .., ..]) {
|
||||||
|
Ok(slice) => get_ab(slice.into_dyn()),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
for t in 0..size_t {
|
||||||
|
let mut frame = Array3::<f64>::zeros((3, size_y, size_y));
|
||||||
|
for c in 0..size_c {
|
||||||
|
frame = frame
|
||||||
|
+ cframe(
|
||||||
|
view.get_frame(c, 0, t).unwrap(),
|
||||||
|
&colors[c],
|
||||||
|
ab[c].0,
|
||||||
|
ab[c].1 / brightness[c],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let frame = frame.mapv(|i| {
|
||||||
|
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();
|
||||||
|
stdin.write_all(&bytes).unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
movie.iter()?.for_each(|e| match e {
|
||||||
|
FfmpegEvent::Log(LogLevel::Error, e) => println!("Error: {}", e),
|
||||||
|
FfmpegEvent::Progress(p) => println!("Progress: {} / 00:00:15", p.time),
|
||||||
|
_ => {}
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::reader::Reader;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn movie() -> Result<()> {
|
||||||
|
let file = "1xp53-01-AP1.czi";
|
||||||
|
let path = std::env::current_dir()?
|
||||||
|
.join("tests")
|
||||||
|
.join("files")
|
||||||
|
.join(file);
|
||||||
|
let reader = Reader::new(&path, 0)?;
|
||||||
|
let view = reader.view();
|
||||||
|
let mut options = MovieOptions::default();
|
||||||
|
options.set_overwrite(true);
|
||||||
|
view.save_as_movie("/home/wim/tmp/movie.mp4", &options)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
361
src/py.rs
361
src/py.rs
@@ -1,16 +1,21 @@
|
|||||||
use crate::axes::Axis;
|
use crate::axes::Axis;
|
||||||
use crate::bioformats::download_bioformats;
|
use crate::bioformats::download_bioformats;
|
||||||
|
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::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
|
use itertools::Itertools;
|
||||||
use ndarray::{Ix0, IxDyn, SliceInfoElem};
|
use ndarray::{Ix0, IxDyn, SliceInfoElem};
|
||||||
use numpy::IntoPyArray;
|
use numpy::IntoPyArray;
|
||||||
|
use ome_metadata::Ome;
|
||||||
|
use pyo3::IntoPyObjectExt;
|
||||||
|
use pyo3::exceptions::PyNotImplementedError;
|
||||||
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 pyo3::IntoPyObjectExt;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{from_str, to_string};
|
use serde_json::{from_str, to_string};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[pyclass(module = "ndbioimage.ndbioimage_rs")]
|
#[pyclass(module = "ndbioimage.ndbioimage_rs")]
|
||||||
struct ViewConstructor;
|
struct ViewConstructor;
|
||||||
@@ -22,12 +27,10 @@ impl ViewConstructor {
|
|||||||
Self
|
Self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn __getstate__(&self) -> (u8,) {
|
fn __getnewargs__<'py>(&self, py: Python<'py>) -> Bound<'py, PyTuple> {
|
||||||
(0,)
|
PyTuple::empty(py)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn __setstate__(&self, _state: (u8,)) {}
|
|
||||||
|
|
||||||
#[staticmethod]
|
#[staticmethod]
|
||||||
fn __call__(state: String) -> PyResult<PyView> {
|
fn __call__(state: String) -> PyResult<PyView> {
|
||||||
if let Ok(new) = from_str(&state) {
|
if let Ok(new) = from_str(&state) {
|
||||||
@@ -40,32 +43,122 @@ impl ViewConstructor {
|
|||||||
|
|
||||||
#[pyclass(subclass, module = "ndbioimage.ndbioimage_rs")]
|
#[pyclass(subclass, module = "ndbioimage.ndbioimage_rs")]
|
||||||
#[pyo3(name = "View")]
|
#[pyo3(name = "View")]
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
struct PyView {
|
struct PyView {
|
||||||
view: View<IxDyn>,
|
view: View<IxDyn>,
|
||||||
dtype: PixelType,
|
dtype: PixelType,
|
||||||
|
ome: Arc<Ome>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
impl PyView {
|
impl PyView {
|
||||||
#[new]
|
|
||||||
#[pyo3(signature = (path, series = 0, dtype = "uint16"))]
|
|
||||||
/// 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)
|
||||||
fn new(path: &str, series: usize, dtype: &str) -> PyResult<Self> {
|
#[new]
|
||||||
let mut path = PathBuf::from(path);
|
#[pyo3(signature = (path, series = 0, dtype = "uint16", axes = "cztyx"))]
|
||||||
|
fn new(path: Bound<'_, PyAny>, series: usize, dtype: &str, axes: &str) -> PyResult<Self> {
|
||||||
|
if path.is_instance_of::<Self>() {
|
||||||
|
Ok(path.downcast_into::<Self>()?.extract::<Self>()?)
|
||||||
|
} else {
|
||||||
|
let mut path = PathBuf::from(path.downcast_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();
|
||||||
if file.path().is_file() & (p.extension() == Some("tif".as_ref())) {
|
if file.path().is_file() && (p.extension() == Some("tif".as_ref())) {
|
||||||
path = p;
|
path = p;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Self {
|
let axes = axes
|
||||||
view: Reader::new(&path, series as i32)?.view().into_dyn(),
|
.chars()
|
||||||
dtype: dtype.parse()?,
|
.map(|a| a.to_string().parse())
|
||||||
|
.collect::<Result<Vec<Axis>>>()?;
|
||||||
|
let reader = Reader::new(&path, series)?;
|
||||||
|
let view = View::new_with_axes(Arc::new(reader), axes)?;
|
||||||
|
let dtype = dtype.parse()?;
|
||||||
|
let ome = Arc::new(view.get_ome()?);
|
||||||
|
Ok(Self { view, dtype, ome })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn squeeze<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
|
||||||
|
let view = self.view.squeeze()?;
|
||||||
|
if view.ndim() == 0 {
|
||||||
|
Ok(match self.dtype {
|
||||||
|
PixelType::I8 => view
|
||||||
|
.into_dimensionality::<Ix0>()?
|
||||||
|
.item::<i8>()?
|
||||||
|
.into_pyobject(py)?
|
||||||
|
.into_any(),
|
||||||
|
PixelType::U8 => view
|
||||||
|
.into_dimensionality::<Ix0>()?
|
||||||
|
.item::<u8>()?
|
||||||
|
.into_pyobject(py)?
|
||||||
|
.into_any(),
|
||||||
|
PixelType::I16 => view
|
||||||
|
.into_dimensionality::<Ix0>()?
|
||||||
|
.item::<i16>()?
|
||||||
|
.into_pyobject(py)?
|
||||||
|
.into_any(),
|
||||||
|
PixelType::U16 => view
|
||||||
|
.into_dimensionality::<Ix0>()?
|
||||||
|
.item::<u16>()?
|
||||||
|
.into_pyobject(py)?
|
||||||
|
.into_any(),
|
||||||
|
PixelType::I32 => view
|
||||||
|
.into_dimensionality::<Ix0>()?
|
||||||
|
.item::<i32>()?
|
||||||
|
.into_pyobject(py)?
|
||||||
|
.into_any(),
|
||||||
|
PixelType::U32 => view
|
||||||
|
.into_dimensionality::<Ix0>()?
|
||||||
|
.item::<u32>()?
|
||||||
|
.into_pyobject(py)?
|
||||||
|
.into_any(),
|
||||||
|
PixelType::I64 => view
|
||||||
|
.into_dimensionality::<Ix0>()?
|
||||||
|
.item::<i64>()?
|
||||||
|
.into_pyobject(py)?
|
||||||
|
.into_any(),
|
||||||
|
PixelType::U64 => view
|
||||||
|
.into_dimensionality::<Ix0>()?
|
||||||
|
.item::<u64>()?
|
||||||
|
.into_pyobject(py)?
|
||||||
|
.into_any(),
|
||||||
|
PixelType::I128 => view
|
||||||
|
.into_dimensionality::<Ix0>()?
|
||||||
|
.item::<i64>()?
|
||||||
|
.into_pyobject(py)?
|
||||||
|
.into_any(),
|
||||||
|
PixelType::U128 => view
|
||||||
|
.into_dimensionality::<Ix0>()?
|
||||||
|
.item::<u64>()?
|
||||||
|
.into_pyobject(py)?
|
||||||
|
.into_any(),
|
||||||
|
PixelType::F32 => view
|
||||||
|
.into_dimensionality::<Ix0>()?
|
||||||
|
.item::<f32>()?
|
||||||
|
.into_pyobject(py)?
|
||||||
|
.into_any(),
|
||||||
|
PixelType::F64 => view
|
||||||
|
.into_dimensionality::<Ix0>()?
|
||||||
|
.item::<f64>()?
|
||||||
|
.into_pyobject(py)?
|
||||||
|
.into_any(),
|
||||||
|
PixelType::F128 => view
|
||||||
|
.into_dimensionality::<Ix0>()?
|
||||||
|
.item::<f64>()?
|
||||||
|
.into_pyobject(py)?
|
||||||
|
.into_any(),
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
PyView {
|
||||||
|
view,
|
||||||
|
dtype: self.dtype.clone(),
|
||||||
|
ome: self.ome.clone(),
|
||||||
|
}
|
||||||
|
.into_bound_py_any(py)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// close the file: does nothing as this is handled automatically
|
/// close the file: does nothing as this is handled automatically
|
||||||
@@ -73,11 +166,18 @@ impl PyView {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy(&self) -> PyView {
|
/// change the data type of the view: (u)int(8/16/32) or float(32/64)
|
||||||
PyView {
|
fn as_type(&self, dtype: &str) -> PyResult<PyView> {
|
||||||
|
Ok(PyView {
|
||||||
view: self.view.clone(),
|
view: self.view.clone(),
|
||||||
dtype: self.dtype.clone(),
|
dtype: dtype.parse()?,
|
||||||
|
ome: self.ome.clone(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// change the data type of the view: (u)int(8/16/32) or float(32/64)
|
||||||
|
fn astype(&self, dtype: &str) -> PyResult<PyView> {
|
||||||
|
self.as_type(dtype)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// slice the view and return a new view or a single number
|
/// slice the view and return a new view or a single number
|
||||||
@@ -216,11 +316,33 @@ impl PyView {
|
|||||||
PyView {
|
PyView {
|
||||||
view,
|
view,
|
||||||
dtype: self.dtype.clone(),
|
dtype: self.dtype.clone(),
|
||||||
|
ome: self.ome.clone(),
|
||||||
}
|
}
|
||||||
.into_bound_py_any(py)
|
.into_bound_py_any(py)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pyo3(signature = (dtype = None))]
|
||||||
|
fn __array__<'py>(&self, py: Python<'py>, dtype: Option<&str>) -> PyResult<Bound<'py, PyAny>> {
|
||||||
|
if let Some(dtype) = dtype {
|
||||||
|
self.as_type(dtype)?.as_array(py)
|
||||||
|
} else {
|
||||||
|
self.as_array(py)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __contains__(&self, _item: Bound<'_, PyAny>) -> PyResult<bool> {
|
||||||
|
Err(PyNotImplementedError::new_err("contains not implemented"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __enter__<'p>(slf: PyRef<'p, Self>, _py: Python<'p>) -> PyResult<PyRef<'p, Self>> {
|
||||||
|
Ok(slf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __exit__(&self) -> PyResult<()> {
|
||||||
|
self.close()
|
||||||
|
}
|
||||||
|
|
||||||
fn __reduce__(&self) -> PyResult<(ViewConstructor, (String,))> {
|
fn __reduce__(&self) -> PyResult<(ViewConstructor, (String,))> {
|
||||||
if let Ok(s) = to_string(self) {
|
if let Ok(s) = to_string(self) {
|
||||||
Ok((ViewConstructor, (s,)))
|
Ok((ViewConstructor, (s,)))
|
||||||
@@ -229,6 +351,34 @@ impl PyView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn __copy__(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
view: self.view.clone(),
|
||||||
|
dtype: self.dtype.clone(),
|
||||||
|
ome: self.ome.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
view: self.view.clone(),
|
||||||
|
dtype: self.dtype.clone(),
|
||||||
|
ome: self.ome.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __len__(&self) -> PyResult<usize> {
|
||||||
|
Ok(self.view.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __repr__(&self) -> PyResult<String> {
|
||||||
|
Ok(self.view.summary()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __str__(&self) -> PyResult<String> {
|
||||||
|
Ok(self.view.path.display().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
/// retrieve a single frame at czt, sliced accordingly
|
/// retrieve a single frame at czt, sliced accordingly
|
||||||
fn get_frame<'py>(
|
fn get_frame<'py>(
|
||||||
&self,
|
&self,
|
||||||
@@ -240,73 +390,113 @@ impl PyView {
|
|||||||
Ok(match self.dtype {
|
Ok(match self.dtype {
|
||||||
PixelType::I8 => self
|
PixelType::I8 => self
|
||||||
.view
|
.view
|
||||||
.get_frame::<i8>(c, z, t)?
|
.get_frame::<i8, _>(c, z, t)?
|
||||||
.into_pyarray(py)
|
.into_pyarray(py)
|
||||||
.into_any(),
|
.into_any(),
|
||||||
PixelType::U8 => self
|
PixelType::U8 => self
|
||||||
.view
|
.view
|
||||||
.get_frame::<u8>(c, z, t)?
|
.get_frame::<u8, _>(c, z, t)?
|
||||||
.into_pyarray(py)
|
.into_pyarray(py)
|
||||||
.into_any(),
|
.into_any(),
|
||||||
PixelType::I16 => self
|
PixelType::I16 => self
|
||||||
.view
|
.view
|
||||||
.get_frame::<i16>(c, z, t)?
|
.get_frame::<i16, _>(c, z, t)?
|
||||||
.into_pyarray(py)
|
.into_pyarray(py)
|
||||||
.into_any(),
|
.into_any(),
|
||||||
PixelType::U16 => self
|
PixelType::U16 => self
|
||||||
.view
|
.view
|
||||||
.get_frame::<u16>(c, z, t)?
|
.get_frame::<u16, _>(c, z, t)?
|
||||||
.into_pyarray(py)
|
.into_pyarray(py)
|
||||||
.into_any(),
|
.into_any(),
|
||||||
PixelType::I32 => self
|
PixelType::I32 => self
|
||||||
.view
|
.view
|
||||||
.get_frame::<i32>(c, z, t)?
|
.get_frame::<i32, _>(c, z, t)?
|
||||||
.into_pyarray(py)
|
.into_pyarray(py)
|
||||||
.into_any(),
|
.into_any(),
|
||||||
PixelType::U32 => self
|
PixelType::U32 => self
|
||||||
.view
|
.view
|
||||||
.get_frame::<u32>(c, z, t)?
|
.get_frame::<u32, _>(c, z, t)?
|
||||||
.into_pyarray(py)
|
.into_pyarray(py)
|
||||||
.into_any(),
|
.into_any(),
|
||||||
PixelType::F32 => self
|
PixelType::F32 => self
|
||||||
.view
|
.view
|
||||||
.get_frame::<f32>(c, z, t)?
|
.get_frame::<f32, _>(c, z, t)?
|
||||||
.into_pyarray(py)
|
.into_pyarray(py)
|
||||||
.into_any(),
|
.into_any(),
|
||||||
PixelType::F64 => self
|
PixelType::F64 => self
|
||||||
.view
|
.view
|
||||||
.get_frame::<f64>(c, z, t)?
|
.get_frame::<f64, _>(c, z, t)?
|
||||||
.into_pyarray(py)
|
.into_pyarray(py)
|
||||||
.into_any(),
|
.into_any(),
|
||||||
PixelType::I64 => self
|
PixelType::I64 => self
|
||||||
.view
|
.view
|
||||||
.get_frame::<i64>(c, z, t)?
|
.get_frame::<i64, _>(c, z, t)?
|
||||||
.into_pyarray(py)
|
.into_pyarray(py)
|
||||||
.into_any(),
|
.into_any(),
|
||||||
PixelType::U64 => self
|
PixelType::U64 => self
|
||||||
.view
|
.view
|
||||||
.get_frame::<u64>(c, z, t)?
|
.get_frame::<u64, _>(c, z, t)?
|
||||||
.into_pyarray(py)
|
.into_pyarray(py)
|
||||||
.into_any(),
|
.into_any(),
|
||||||
PixelType::I128 => self
|
PixelType::I128 => self
|
||||||
.view
|
.view
|
||||||
.get_frame::<i64>(c, z, t)?
|
.get_frame::<i64, _>(c, z, t)?
|
||||||
.into_pyarray(py)
|
.into_pyarray(py)
|
||||||
.into_any(),
|
.into_any(),
|
||||||
PixelType::U128 => self
|
PixelType::U128 => self
|
||||||
.view
|
.view
|
||||||
.get_frame::<u64>(c, z, t)?
|
.get_frame::<u64, _>(c, z, t)?
|
||||||
.into_pyarray(py)
|
.into_pyarray(py)
|
||||||
.into_any(),
|
.into_any(),
|
||||||
PixelType::F128 => self
|
PixelType::F128 => self
|
||||||
.view
|
.view
|
||||||
.get_frame::<f64>(c, z, t)?
|
.get_frame::<f64, _>(c, z, t)?
|
||||||
.into_pyarray(py)
|
.into_pyarray(py)
|
||||||
.into_any(),
|
.into_any(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// retrieve the ome metadata as an xml string
|
fn flatten<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
|
||||||
|
Ok(match self.dtype {
|
||||||
|
PixelType::I8 => self.view.flatten::<i8>()?.into_pyarray(py).into_any(),
|
||||||
|
PixelType::U8 => self.view.flatten::<u8>()?.into_pyarray(py).into_any(),
|
||||||
|
PixelType::I16 => self.view.flatten::<i16>()?.into_pyarray(py).into_any(),
|
||||||
|
PixelType::U16 => self.view.flatten::<u16>()?.into_pyarray(py).into_any(),
|
||||||
|
PixelType::I32 => self.view.flatten::<i32>()?.into_pyarray(py).into_any(),
|
||||||
|
PixelType::U32 => self.view.flatten::<u32>()?.into_pyarray(py).into_any(),
|
||||||
|
PixelType::F32 => self.view.flatten::<f32>()?.into_pyarray(py).into_any(),
|
||||||
|
PixelType::F64 => self.view.flatten::<f64>()?.into_pyarray(py).into_any(),
|
||||||
|
PixelType::I64 => self.view.flatten::<i64>()?.into_pyarray(py).into_any(),
|
||||||
|
PixelType::U64 => self.view.flatten::<u64>()?.into_pyarray(py).into_any(),
|
||||||
|
PixelType::I128 => self.view.flatten::<i64>()?.into_pyarray(py).into_any(),
|
||||||
|
PixelType::U128 => self.view.flatten::<u64>()?.into_pyarray(py).into_any(),
|
||||||
|
PixelType::F128 => self.view.flatten::<f64>()?.into_pyarray(py).into_any(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_bytes(&self) -> PyResult<Vec<u8>> {
|
||||||
|
Ok(match self.dtype {
|
||||||
|
PixelType::I8 => self.view.to_bytes::<i8>()?,
|
||||||
|
PixelType::U8 => self.view.to_bytes::<u8>()?,
|
||||||
|
PixelType::I16 => self.view.to_bytes::<i16>()?,
|
||||||
|
PixelType::U16 => self.view.to_bytes::<u16>()?,
|
||||||
|
PixelType::I32 => self.view.to_bytes::<i32>()?,
|
||||||
|
PixelType::U32 => self.view.to_bytes::<u32>()?,
|
||||||
|
PixelType::F32 => self.view.to_bytes::<f32>()?,
|
||||||
|
PixelType::F64 => self.view.to_bytes::<f64>()?,
|
||||||
|
PixelType::I64 => self.view.to_bytes::<i64>()?,
|
||||||
|
PixelType::U64 => self.view.to_bytes::<u64>()?,
|
||||||
|
PixelType::I128 => self.view.to_bytes::<i64>()?,
|
||||||
|
PixelType::U128 => self.view.to_bytes::<u64>()?,
|
||||||
|
PixelType::F128 => self.view.to_bytes::<f64>()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tobytes(&self) -> PyResult<Vec<u8>> {
|
||||||
|
self.to_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// retrieve the ome metadata as an XML string
|
||||||
fn get_ome_xml(&self) -> PyResult<String> {
|
fn get_ome_xml(&self) -> PyResult<String> {
|
||||||
Ok(self.view.get_ome_xml()?)
|
Ok(self.view.get_ome_xml()?)
|
||||||
}
|
}
|
||||||
@@ -319,18 +509,14 @@ impl PyView {
|
|||||||
|
|
||||||
/// the series in the file
|
/// the series in the file
|
||||||
#[getter]
|
#[getter]
|
||||||
fn series(&self) -> PyResult<i32> {
|
fn series(&self) -> PyResult<usize> {
|
||||||
Ok(self.view.series)
|
Ok(self.view.series)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the axes in the view
|
/// the axes in the view
|
||||||
#[getter]
|
#[getter]
|
||||||
fn axes(&self) -> Vec<String> {
|
fn axes(&self) -> String {
|
||||||
self.view
|
self.view.axes().iter().map(|a| format!("{:?}", a)).join("")
|
||||||
.axes()
|
|
||||||
.iter()
|
|
||||||
.map(|a| format!("{:?}", a))
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the shape of the view
|
/// the shape of the view
|
||||||
@@ -391,6 +577,7 @@ impl PyView {
|
|||||||
Ok(PyView {
|
Ok(PyView {
|
||||||
view,
|
view,
|
||||||
dtype: self.dtype.clone(),
|
dtype: self.dtype.clone(),
|
||||||
|
ome: self.ome.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,9 +596,16 @@ impl PyView {
|
|||||||
Ok(PyView {
|
Ok(PyView {
|
||||||
view,
|
view,
|
||||||
dtype: self.dtype.clone(),
|
dtype: self.dtype.clone(),
|
||||||
|
ome: self.ome.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[getter]
|
||||||
|
fn T(&self) -> PyResult<Self> {
|
||||||
|
self.transpose(None)
|
||||||
|
}
|
||||||
|
|
||||||
/// collect data into a numpy array
|
/// collect data into a numpy array
|
||||||
fn as_array<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
|
fn as_array<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
|
||||||
Ok(match self.dtype {
|
Ok(match self.dtype {
|
||||||
@@ -431,14 +625,6 @@ impl PyView {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// change the data type of the view: (u)int(8/16/32) or float(32/64)
|
|
||||||
fn as_type(&self, dtype: String) -> PyResult<Self> {
|
|
||||||
Ok(PyView {
|
|
||||||
view: self.view.clone(),
|
|
||||||
dtype: dtype.parse()?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[getter]
|
#[getter]
|
||||||
fn get_dtype(&self) -> PyResult<&str> {
|
fn get_dtype(&self) -> PyResult<&str> {
|
||||||
Ok(match self.dtype {
|
Ok(match self.dtype {
|
||||||
@@ -475,6 +661,7 @@ impl PyView {
|
|||||||
PyView {
|
PyView {
|
||||||
dtype: self.dtype.clone(),
|
dtype: self.dtype.clone(),
|
||||||
view: self.view.max_proj(self.get_ax(axis)?)?,
|
view: self.view.max_proj(self.get_ax(axis)?)?,
|
||||||
|
ome: self.ome.clone(),
|
||||||
}
|
}
|
||||||
.into_bound_py_any(py)
|
.into_bound_py_any(py)
|
||||||
} else {
|
} else {
|
||||||
@@ -507,6 +694,7 @@ impl PyView {
|
|||||||
PyView {
|
PyView {
|
||||||
dtype: self.dtype.clone(),
|
dtype: self.dtype.clone(),
|
||||||
view: self.view.min_proj(self.get_ax(axis)?)?,
|
view: self.view.min_proj(self.get_ax(axis)?)?,
|
||||||
|
ome: self.ome.clone(),
|
||||||
}
|
}
|
||||||
.into_bound_py_any(py)
|
.into_bound_py_any(py)
|
||||||
} else {
|
} else {
|
||||||
@@ -544,6 +732,7 @@ impl PyView {
|
|||||||
PyView {
|
PyView {
|
||||||
dtype,
|
dtype,
|
||||||
view: self.view.mean_proj(self.get_ax(axis)?)?,
|
view: self.view.mean_proj(self.get_ax(axis)?)?,
|
||||||
|
ome: self.ome.clone(),
|
||||||
}
|
}
|
||||||
.into_bound_py_any(py)
|
.into_bound_py_any(py)
|
||||||
} else {
|
} else {
|
||||||
@@ -580,6 +769,7 @@ impl PyView {
|
|||||||
PyView {
|
PyView {
|
||||||
dtype,
|
dtype,
|
||||||
view: self.view.sum_proj(self.get_ax(axis)?)?,
|
view: self.view.sum_proj(self.get_ax(axis)?)?,
|
||||||
|
ome: self.ome.clone(),
|
||||||
}
|
}
|
||||||
.into_bound_py_any(py)
|
.into_bound_py_any(py)
|
||||||
} else {
|
} else {
|
||||||
@@ -595,6 +785,83 @@ impl PyView {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn z_stack(&self) -> PyResult<bool> {
|
||||||
|
if let Some(s) = self.view.size_ax(Axis::Z) {
|
||||||
|
Ok(s > 1)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn time_series(&self) -> PyResult<bool> {
|
||||||
|
if let Some(s) = self.view.size_ax(Axis::T) {
|
||||||
|
Ok(s > 1)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn pixel_size(&self) -> PyResult<Option<f64>> {
|
||||||
|
Ok(self.ome.pixel_size()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn delta_z(&self) -> PyResult<Option<f64>> {
|
||||||
|
Ok(self.ome.delta_z()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn time_interval(&self) -> PyResult<Option<f64>> {
|
||||||
|
Ok(self.ome.time_interval()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exposure_time(&self, channel: usize) -> PyResult<Option<f64>> {
|
||||||
|
Ok(self.ome.exposure_time(channel)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn binning(&self, channel: usize) -> Option<usize> {
|
||||||
|
self.ome.binning(channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn laser_wavelengths(&self, channel: usize) -> PyResult<Option<f64>> {
|
||||||
|
Ok(self.ome.laser_wavelengths(channel)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn laser_power(&self, channel: usize) -> PyResult<Option<f64>> {
|
||||||
|
Ok(self.ome.laser_powers(channel)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn objective_name(&self) -> Option<String> {
|
||||||
|
self.ome.objective_name()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn magnification(&self) -> Option<f64> {
|
||||||
|
self.ome.magnification()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn tube_lens_name(&self) -> Option<String> {
|
||||||
|
self.ome.tube_lens_name()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_set_name(&self, channel: usize) -> Option<String> {
|
||||||
|
self.ome.filter_set_name(channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gain(&self, channel: usize) -> Option<f64> {
|
||||||
|
self.ome.gain(channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// gives a helpful summary of the recorded experiment
|
||||||
|
fn summary(&self) -> PyResult<String> {
|
||||||
|
Ok(self.view.summary()?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn ndbioimage_file() -> anyhow::Result<PathBuf> {
|
pub(crate) fn ndbioimage_file() -> anyhow::Result<PathBuf> {
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ use crate::axes::Axis;
|
|||||||
use crate::bioformats;
|
use crate::bioformats;
|
||||||
use crate::bioformats::{DebugTools, ImageReader, MetadataTools};
|
use crate::bioformats::{DebugTools, ImageReader, MetadataTools};
|
||||||
use crate::view::View;
|
use crate::view::View;
|
||||||
use anyhow::{anyhow, Error, Result};
|
use anyhow::{Error, Result, anyhow};
|
||||||
use ndarray::{s, Array2, Ix5};
|
use ndarray::{Array2, Ix5, s};
|
||||||
use num::{FromPrimitive, Zero};
|
use num::{FromPrimitive, Zero};
|
||||||
|
use ome_metadata::Ome;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::any::type_name;
|
use std::any::type_name;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
@@ -14,6 +15,26 @@ 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>)>
|
||||||
|
where
|
||||||
|
P: Into<PathBuf>,
|
||||||
|
{
|
||||||
|
let path = path.into();
|
||||||
|
let file_name = path
|
||||||
|
.file_name()
|
||||||
|
.ok_or_else(|| anyhow!("No file name"))?
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| anyhow!("No file name"))?;
|
||||||
|
if file_name.to_lowercase().starts_with("pos") {
|
||||||
|
if let Some(series) = file_name.get(3..) {
|
||||||
|
if let Ok(series) = series.parse::<usize>() {
|
||||||
|
return Ok((path, Some(series)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((path, None))
|
||||||
|
}
|
||||||
|
|
||||||
/// Pixel types (u)int(8/16/32) or float(32/64), (u/i)(64/128) are not included in bioformats
|
/// Pixel types (u)int(8/16/32) or float(32/64), (u/i)(64/128) are not included in bioformats
|
||||||
#[allow(clippy::upper_case_acronyms)]
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
@@ -228,7 +249,7 @@ pub struct Reader {
|
|||||||
/// path to file
|
/// path to file
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
/// which (if more than 1) of the series in the file to open
|
/// which (if more than 1) of the series in the file to open
|
||||||
pub series: i32,
|
pub series: usize,
|
||||||
/// size x (horizontal)
|
/// size x (horizontal)
|
||||||
pub size_x: usize,
|
pub size_x: usize,
|
||||||
/// size y (vertical)
|
/// size y (vertical)
|
||||||
@@ -254,7 +275,7 @@ impl Deref for Reader {
|
|||||||
let ome_meta = meta_data_tools.create_ome_xml_metadata().unwrap();
|
let ome_meta = meta_data_tools.create_ome_xml_metadata().unwrap();
|
||||||
reader.set_metadata_store(ome_meta).unwrap();
|
reader.set_metadata_store(ome_meta).unwrap();
|
||||||
reader.set_id(self.path.to_str().unwrap()).unwrap();
|
reader.set_id(self.path.to_str().unwrap()).unwrap();
|
||||||
reader.set_series(self.series).unwrap();
|
reader.set_series(self.series as i32).unwrap();
|
||||||
reader
|
reader
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -284,11 +305,14 @@ 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(path: &Path, series: i32) -> Result<Self> {
|
pub fn new<P>(path: P, series: usize) -> Result<Self>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
DebugTools::set_root_level("ERROR")?;
|
DebugTools::set_root_level("ERROR")?;
|
||||||
let mut reader = Reader {
|
let mut reader = Reader {
|
||||||
image_reader: ThreadLocal::default(),
|
image_reader: ThreadLocal::default(),
|
||||||
path: PathBuf::from(path),
|
path: path.as_ref().to_path_buf(),
|
||||||
series,
|
series,
|
||||||
size_x: 0,
|
size_x: 0,
|
||||||
size_y: 0,
|
size_y: 0,
|
||||||
@@ -308,6 +332,17 @@ impl Reader {
|
|||||||
Ok(reader)
|
Ok(reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get ome metadata as ome structure
|
||||||
|
pub fn get_ome(&self) -> Result<Ome> {
|
||||||
|
let mut ome = self.ome_xml()?.parse::<Ome>()?;
|
||||||
|
if let Some(image) = ome.image.as_ref() {
|
||||||
|
if image.len() > 1 {
|
||||||
|
ome.image = Some(vec![image[self.series].clone()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ome)
|
||||||
|
}
|
||||||
|
|
||||||
/// 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> {
|
||||||
self.ome_xml()
|
self.ome_xml()
|
||||||
@@ -339,7 +374,7 @@ 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> {
|
pub fn get_frame(&self, c: usize, z: usize, t: usize) -> Result<Frame> {
|
||||||
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)?
|
||||||
} else if self.get_rgb_channel_count()? > 1 {
|
} else if self.get_rgb_channel_count()? > 1 {
|
||||||
@@ -418,7 +453,7 @@ impl Reader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get a slicable view on the image file
|
/// get a sliceable view on the image file
|
||||||
pub fn view(&self) -> View<Ix5> {
|
pub fn view(&self) -> View<Ix5> {
|
||||||
let slice = s![
|
let slice = s![
|
||||||
0..self.size_c,
|
0..self.size_c,
|
||||||
|
|||||||
10
src/stats.rs
10
src/stats.rs
@@ -1,4 +1,4 @@
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
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
|
||||||
@@ -196,6 +196,10 @@ macro_rules! impl_frame_stats_int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl_frame_stats_float_view!(f32, f64);
|
impl_frame_stats_float_view!(f32, f64);
|
||||||
impl_frame_stats_int_view!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize);
|
impl_frame_stats_int_view!(
|
||||||
|
u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize
|
||||||
|
);
|
||||||
impl_frame_stats_float!(f32, f64);
|
impl_frame_stats_float!(f32, f64);
|
||||||
impl_frame_stats_int!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize);
|
impl_frame_stats_int!(
|
||||||
|
u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize
|
||||||
|
);
|
||||||
|
|||||||
193
src/tiff.rs
Normal file
193
src/tiff.rs
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
use crate::colors::Color;
|
||||||
|
use crate::metadata::Metadata;
|
||||||
|
use crate::reader::PixelType;
|
||||||
|
use crate::stats::MinMax;
|
||||||
|
use crate::view::{Number, View};
|
||||||
|
use anyhow::{Result, anyhow};
|
||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
use itertools::iproduct;
|
||||||
|
use ndarray::{Array0, Array1, Array2, ArrayD, Dimension};
|
||||||
|
use rayon::prelude::*;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use tiffwrite::{Bytes, Colors, Compression, IJTiffFile};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TiffOptions {
|
||||||
|
bar: Option<ProgressStyle>,
|
||||||
|
compression: Compression,
|
||||||
|
colors: Option<Vec<Vec<u8>>>,
|
||||||
|
overwrite: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TiffOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
bar: None,
|
||||||
|
compression: Compression::Zstd(10),
|
||||||
|
colors: None,
|
||||||
|
overwrite: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffOptions {
|
||||||
|
pub fn new(
|
||||||
|
bar: bool,
|
||||||
|
compression: Option<Compression>,
|
||||||
|
colors: Vec<String>,
|
||||||
|
overwrite: bool,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let mut options = Self {
|
||||||
|
bar: None,
|
||||||
|
compression: compression.unwrap_or(Compression::Zstd(10)),
|
||||||
|
colors: None,
|
||||||
|
overwrite,
|
||||||
|
};
|
||||||
|
if bar {
|
||||||
|
options.enable_bar()?;
|
||||||
|
}
|
||||||
|
if !colors.is_empty() {
|
||||||
|
options.set_colors(&colors)?;
|
||||||
|
}
|
||||||
|
Ok(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// show a progress bar while saving tiff
|
||||||
|
pub fn enable_bar(&mut self) -> Result<()> {
|
||||||
|
self.bar = Some(ProgressStyle::with_template(
|
||||||
|
"{spinner:.green} [{elapsed_precise}, {percent}%] [{wide_bar:.green/lime}] {pos:>7}/{len:7} ({eta_precise}, {per_sec:<5})",
|
||||||
|
)?.progress_chars("▰▱▱"));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// do not show a progress bar while saving tiff
|
||||||
|
pub fn disable_bar(&mut self) {
|
||||||
|
self.bar = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// save tiff with zstd compression (default)
|
||||||
|
pub fn set_zstd_compression(&mut self) {
|
||||||
|
self.compression = Compression::Zstd(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// save tiff with zstd compression, choose a level between 7..=22
|
||||||
|
pub fn set_zstd_compression_level(&mut self, level: i32) {
|
||||||
|
self.compression = Compression::Zstd(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// save tiff with deflate compression
|
||||||
|
pub fn set_deflate_compression(&mut self) {
|
||||||
|
self.compression = Compression::Deflate
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_colors(&mut self, colors: &[String]) -> Result<()> {
|
||||||
|
let colors = colors
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.parse::<Color>())
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
self.colors = Some(colors.into_iter().map(|c| c.to_rgb()).collect());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_overwrite(&mut self, overwrite: bool) {
|
||||||
|
self.overwrite = overwrite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D> View<D>
|
||||||
|
where
|
||||||
|
D: Dimension,
|
||||||
|
{
|
||||||
|
/// save as tiff with a certain type
|
||||||
|
pub fn save_as_tiff_with_type<T, P>(&self, path: P, options: &TiffOptions) -> Result<()>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
T: Bytes + Number + Send + Sync,
|
||||||
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
|
Array1<T>: MinMax<Output = Array0<T>>,
|
||||||
|
Array2<T>: MinMax<Output = Array1<T>>,
|
||||||
|
{
|
||||||
|
let path = path.as_ref().to_path_buf();
|
||||||
|
if path.exists() {
|
||||||
|
if options.overwrite {
|
||||||
|
std::fs::remove_file(&path)?;
|
||||||
|
} else {
|
||||||
|
return Err(anyhow!("File {} already exists", path.display()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let size_c = self.size_c();
|
||||||
|
let size_z = self.size_z();
|
||||||
|
let size_t = self.size_t();
|
||||||
|
let mut tiff = IJTiffFile::new(path)?;
|
||||||
|
tiff.set_compression(options.compression.clone());
|
||||||
|
let ome = self.get_ome()?;
|
||||||
|
tiff.px_size = ome.pixel_size()?.map(|i| i / 1e3);
|
||||||
|
tiff.time_interval = ome.time_interval()?.map(|i| i / 1e3);
|
||||||
|
tiff.delta_z = ome.delta_z()?.map(|i| i / 1e3);
|
||||||
|
tiff.comment = Some(self.ome_xml()?);
|
||||||
|
if let Some(mut colors) = options.colors.clone() {
|
||||||
|
while colors.len() < self.size_c {
|
||||||
|
colors.push(vec![255, 255, 255]);
|
||||||
|
}
|
||||||
|
tiff.colors = Colors::Colors(colors);
|
||||||
|
}
|
||||||
|
let tiff = Arc::new(Mutex::new(tiff));
|
||||||
|
if let Some(style) = &options.bar {
|
||||||
|
let bar = ProgressBar::new((size_c as u64) * (size_z as u64) * (size_t as u64))
|
||||||
|
.with_style(style.clone());
|
||||||
|
iproduct!(0..size_c, 0..size_z, 0..size_t)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_par_iter()
|
||||||
|
.map(|(c, z, t)| {
|
||||||
|
if let Ok(mut tiff) = tiff.lock() {
|
||||||
|
tiff.save(&self.get_frame::<T, _>(c, z, t)?, c, z, t)?;
|
||||||
|
bar.inc(1);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(anyhow::anyhow!("tiff is locked"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<()>>()?;
|
||||||
|
bar.finish();
|
||||||
|
} else {
|
||||||
|
iproduct!(0..size_c, 0..size_z, 0..size_t)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_par_iter()
|
||||||
|
.map(|(c, z, t)| {
|
||||||
|
if let Ok(mut tiff) = tiff.lock() {
|
||||||
|
tiff.save(&self.get_frame::<T, _>(c, z, t)?, c, z, t)?;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(anyhow::anyhow!("tiff is locked"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<()>>()?;
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// save as tiff with whatever pixel type the view has
|
||||||
|
pub fn save_as_tiff<P>(&self, path: P, options: &TiffOptions) -> Result<()>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
match self.pixel_type {
|
||||||
|
PixelType::I8 => self.save_as_tiff_with_type::<i8, P>(path, options)?,
|
||||||
|
PixelType::U8 => self.save_as_tiff_with_type::<u8, P>(path, options)?,
|
||||||
|
PixelType::I16 => self.save_as_tiff_with_type::<i16, P>(path, options)?,
|
||||||
|
PixelType::U16 => self.save_as_tiff_with_type::<u16, P>(path, options)?,
|
||||||
|
PixelType::I32 => self.save_as_tiff_with_type::<i32, P>(path, options)?,
|
||||||
|
PixelType::U32 => self.save_as_tiff_with_type::<u32, P>(path, options)?,
|
||||||
|
PixelType::F32 => self.save_as_tiff_with_type::<f32, P>(path, options)?,
|
||||||
|
PixelType::F64 => self.save_as_tiff_with_type::<f64, P>(path, options)?,
|
||||||
|
PixelType::I64 => self.save_as_tiff_with_type::<i64, P>(path, options)?,
|
||||||
|
PixelType::U64 => self.save_as_tiff_with_type::<u64, P>(path, options)?,
|
||||||
|
PixelType::I128 => self.save_as_tiff_with_type::<i64, P>(path, options)?,
|
||||||
|
PixelType::U128 => self.save_as_tiff_with_type::<u64, P>(path, options)?,
|
||||||
|
PixelType::F128 => self.save_as_tiff_with_type::<f64, P>(path, options)?,
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
572
src/view.rs
572
src/view.rs
@@ -1,23 +1,25 @@
|
|||||||
use crate::axes::{slice_info, Ax, Axis, Operation, Slice, SliceInfoElemDef};
|
use crate::axes::{Ax, Axis, Operation, Slice, SliceInfoElemDef, slice_info};
|
||||||
|
use crate::metadata::Metadata;
|
||||||
use crate::reader::Reader;
|
use crate::reader::Reader;
|
||||||
use crate::stats::MinMax;
|
use crate::stats::MinMax;
|
||||||
use anyhow::{anyhow, Error, Result};
|
use anyhow::{Error, Result, anyhow};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use itertools::iproduct;
|
use itertools::{Itertools, iproduct};
|
||||||
use ndarray::{
|
use ndarray::{
|
||||||
s, Array, Array0, Array1, Array2, ArrayD, Dimension, IntoDimension, Ix0, Ix1, Ix2, Ix5, IxDyn,
|
Array, Array0, Array1, Array2, ArrayD, Dimension, IntoDimension, Ix0, Ix1, Ix2, Ix5, IxDyn,
|
||||||
SliceArg, SliceInfoElem,
|
SliceArg, SliceInfoElem, s,
|
||||||
};
|
};
|
||||||
use num::{Bounded, FromPrimitive, Zero};
|
use num::traits::ToBytes;
|
||||||
|
use num::{Bounded, FromPrimitive, ToPrimitive, Zero};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::serde_as;
|
use serde_with::serde_as;
|
||||||
use std::any::type_name;
|
use std::any::type_name;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
use std::iter::Sum;
|
use std::iter::Sum;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::mem::{transmute, transmute_copy};
|
|
||||||
use std::ops::{AddAssign, Deref, Div};
|
use std::ops::{AddAssign, Deref, Div};
|
||||||
use std::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> {
|
||||||
@@ -65,8 +67,10 @@ impl<T> Number for T where
|
|||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct View<D: Dimension> {
|
pub struct View<D: Dimension> {
|
||||||
reader: Arc<Reader>,
|
reader: Arc<Reader>,
|
||||||
|
/// same order as axes
|
||||||
#[serde_as(as = "Vec<SliceInfoElemDef>")]
|
#[serde_as(as = "Vec<SliceInfoElemDef>")]
|
||||||
slice: Vec<SliceInfoElem>,
|
slice: Vec<SliceInfoElem>,
|
||||||
|
/// always has all of cztyx with possibly some new axes added
|
||||||
axes: Vec<Axis>,
|
axes: Vec<Axis>,
|
||||||
operations: IndexMap<Axis, Operation>,
|
operations: IndexMap<Axis, Operation>,
|
||||||
dimensionality: PhantomData<D>,
|
dimensionality: PhantomData<D>,
|
||||||
@@ -83,13 +87,79 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn new_with_axes(reader: Arc<Reader>, axes: Vec<Axis>) -> Result<Self> {
|
||||||
|
let mut slice = Vec::new();
|
||||||
|
for axis in axes.iter() {
|
||||||
|
match axis {
|
||||||
|
Axis::C => slice.push(SliceInfoElem::Slice {
|
||||||
|
start: 0,
|
||||||
|
end: Some(reader.size_c as isize),
|
||||||
|
step: 1,
|
||||||
|
}),
|
||||||
|
Axis::Z => slice.push(SliceInfoElem::Slice {
|
||||||
|
start: 0,
|
||||||
|
end: Some(reader.size_z as isize),
|
||||||
|
step: 1,
|
||||||
|
}),
|
||||||
|
Axis::T => slice.push(SliceInfoElem::Slice {
|
||||||
|
start: 0,
|
||||||
|
end: Some(reader.size_t as isize),
|
||||||
|
step: 1,
|
||||||
|
}),
|
||||||
|
Axis::Y => slice.push(SliceInfoElem::Slice {
|
||||||
|
start: 0,
|
||||||
|
end: Some(reader.size_y as isize),
|
||||||
|
step: 1,
|
||||||
|
}),
|
||||||
|
Axis::X => slice.push(SliceInfoElem::Slice {
|
||||||
|
start: 0,
|
||||||
|
end: Some(reader.size_x as isize),
|
||||||
|
step: 1,
|
||||||
|
}),
|
||||||
|
Axis::New => {
|
||||||
|
slice.push(SliceInfoElem::NewAxis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut axes = axes.clone();
|
||||||
|
for axis in [Axis::C, Axis::Z, Axis::T, Axis::Y, Axis::X] {
|
||||||
|
if !axes.contains(&axis) {
|
||||||
|
let size = match axis {
|
||||||
|
Axis::C => reader.size_c,
|
||||||
|
Axis::Z => reader.size_z,
|
||||||
|
Axis::T => reader.size_t,
|
||||||
|
Axis::Y => reader.size_y,
|
||||||
|
Axis::X => reader.size_x,
|
||||||
|
Axis::New => 1,
|
||||||
|
};
|
||||||
|
if size > 1 {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Axis {:?} has length {}, but was not included",
|
||||||
|
axis,
|
||||||
|
size
|
||||||
|
));
|
||||||
|
}
|
||||||
|
slice.push(SliceInfoElem::Index(0));
|
||||||
|
axes.push(axis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
reader,
|
||||||
|
slice,
|
||||||
|
axes,
|
||||||
|
operations: IndexMap::new(),
|
||||||
|
dimensionality: PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// the file path
|
/// the file path
|
||||||
pub fn path(&self) -> &PathBuf {
|
pub fn path(&self) -> &PathBuf {
|
||||||
&self.reader.path
|
&self.reader.path
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the series in the file
|
/// the series in the file
|
||||||
pub fn series(&self) -> i32 {
|
pub fn series(&self) -> usize {
|
||||||
self.reader.series
|
self.reader.series
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,6 +209,11 @@ impl<D: Dimension> View<D> {
|
|||||||
&self.axes
|
&self.axes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn get_operations(&self) -> &IndexMap<Axis, Operation> {
|
||||||
|
&self.operations
|
||||||
|
}
|
||||||
|
|
||||||
/// the slice defining the view
|
/// the slice defining the view
|
||||||
pub fn get_slice(&self) -> &[SliceInfoElem] {
|
pub fn get_slice(&self) -> &[SliceInfoElem] {
|
||||||
&self.slice
|
&self.slice
|
||||||
@@ -159,6 +234,27 @@ impl<D: Dimension> View<D> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// remove axes of size 1
|
||||||
|
pub fn squeeze(&self) -> Result<View<IxDyn>> {
|
||||||
|
let view = self.clone().into_dyn();
|
||||||
|
let slice: Vec<_> = self
|
||||||
|
.shape()
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| {
|
||||||
|
if s == 1 {
|
||||||
|
SliceInfoElem::Index(0)
|
||||||
|
} else {
|
||||||
|
SliceInfoElem::Slice {
|
||||||
|
start: 0,
|
||||||
|
end: None,
|
||||||
|
step: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
view.slice(slice.as_slice())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn op_axes(&self) -> Vec<Axis> {
|
pub(crate) fn op_axes(&self) -> Vec<Axis> {
|
||||||
self.operations.keys().cloned().collect()
|
self.operations.keys().cloned().collect()
|
||||||
}
|
}
|
||||||
@@ -172,11 +268,27 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// the number of pixels in the first dimension
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.shape()[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.shape()[0] == 0
|
||||||
|
}
|
||||||
|
|
||||||
/// the number of pixels in the view
|
/// the number of pixels in the view
|
||||||
pub fn size(&self) -> usize {
|
pub fn size(&self) -> usize {
|
||||||
self.shape().into_iter().product()
|
self.shape().into_iter().product()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn size_ax(&self, ax: Axis) -> Option<usize> {
|
||||||
|
self.axes()
|
||||||
|
.iter()
|
||||||
|
.position(|a| *a == ax)
|
||||||
|
.map(|i| self.shape()[i])
|
||||||
|
}
|
||||||
|
|
||||||
/// the shape of the view
|
/// the shape of the view
|
||||||
pub fn shape(&self) -> Vec<usize> {
|
pub fn shape(&self) -> Vec<usize> {
|
||||||
let mut shape = Vec::<usize>::new();
|
let mut shape = Vec::<usize>::new();
|
||||||
@@ -202,6 +314,43 @@ impl<D: Dimension> View<D> {
|
|||||||
shape
|
shape
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn size_of(&self, axis: Axis) -> usize {
|
||||||
|
if let Some(axis_position) = self.axes.iter().position(|a| *a == axis) {
|
||||||
|
match self.slice[axis_position] {
|
||||||
|
SliceInfoElem::Slice { start, end, step } => {
|
||||||
|
if let Some(e) = end {
|
||||||
|
((e - start).max(0) / step) as usize
|
||||||
|
} else {
|
||||||
|
panic!("slice has no end")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => 1,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size_c(&self) -> usize {
|
||||||
|
self.size_of(Axis::C)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size_z(&self) -> usize {
|
||||||
|
self.size_of(Axis::Z)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size_t(&self) -> usize {
|
||||||
|
self.size_of(Axis::T)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size_y(&self) -> usize {
|
||||||
|
self.size_of(Axis::Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size_x(&self) -> usize {
|
||||||
|
self.size_of(Axis::X)
|
||||||
|
}
|
||||||
|
|
||||||
fn shape_all(&self) -> Vec<usize> {
|
fn shape_all(&self) -> Vec<usize> {
|
||||||
let mut shape = Vec::<usize>::new();
|
let mut shape = Vec::<usize>::new();
|
||||||
for s in self.slice.iter() {
|
for s in self.slice.iter() {
|
||||||
@@ -227,7 +376,7 @@ impl<D: Dimension> View<D> {
|
|||||||
slice.swap(idx0, idx1);
|
slice.swap(idx0, idx1);
|
||||||
let mut axes = self.axes.clone();
|
let mut axes = self.axes.clone();
|
||||||
axes.swap(idx0, idx1);
|
axes.swap(idx0, idx1);
|
||||||
Ok(View::new(self.reader.clone(), slice, axes))
|
Ok(View::new(self.reader.clone(), slice, axes).with_operations(self.operations.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// subset of gives axes will be reordered in given order
|
/// subset of gives axes will be reordered in given order
|
||||||
@@ -244,7 +393,7 @@ impl<D: Dimension> View<D> {
|
|||||||
slice[j] = self.slice[i];
|
slice[j] = self.slice[i];
|
||||||
axes[j] = self.axes[i];
|
axes[j] = self.axes[i];
|
||||||
}
|
}
|
||||||
Ok(View::new(self.reader.clone(), slice, axes))
|
Ok(View::new(self.reader.clone(), slice, axes).with_operations(self.operations.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// reverse the order of the axes
|
/// reverse the order of the axes
|
||||||
@@ -253,17 +402,35 @@ impl<D: Dimension> View<D> {
|
|||||||
self.reader.clone(),
|
self.reader.clone(),
|
||||||
self.slice.iter().rev().cloned().collect(),
|
self.slice.iter().rev().cloned().collect(),
|
||||||
self.axes.iter().rev().cloned().collect(),
|
self.axes.iter().rev().cloned().collect(),
|
||||||
))
|
)
|
||||||
|
.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>> {
|
||||||
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 mut operations = self.operations.clone();
|
let ax = self.axes[pos];
|
||||||
operations.insert(self.axes[pos], operation);
|
let (axes, slice, operations) = if Axis::New == ax {
|
||||||
Ok(
|
let mut axes = self.axes.clone();
|
||||||
View::new(self.reader.clone(), self.slice.clone(), self.axes.clone())
|
let mut slice = self.slice.clone();
|
||||||
.with_operations(operations),
|
axes.remove(pos);
|
||||||
|
slice.remove(pos);
|
||||||
|
(axes, slice, self.operations.clone())
|
||||||
|
} else if self.operations.contains_key(&ax) {
|
||||||
|
if D::NDIM.is_none() {
|
||||||
|
(
|
||||||
|
self.axes.clone(),
|
||||||
|
self.slice.clone(),
|
||||||
|
self.operations.clone(),
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
return Err(anyhow!("axis {}: {} is already operated on!", pos, ax));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut operations = self.operations.clone();
|
||||||
|
operations.insert(ax, operation);
|
||||||
|
(self.axes.clone(), self.slice.clone(), operations)
|
||||||
|
};
|
||||||
|
Ok(View::new(self.reader.clone(), slice, axes).with_operations(operations))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// maximum along axis
|
/// maximum along axis
|
||||||
@@ -300,51 +467,88 @@ impl<D: Dimension> View<D> {
|
|||||||
let mut new_slice = Vec::new();
|
let mut new_slice = Vec::new();
|
||||||
let mut new_axes = Vec::new();
|
let mut new_axes = Vec::new();
|
||||||
let reader_slice = self.slice.as_slice();
|
let reader_slice = self.slice.as_slice();
|
||||||
while (r_idx < reader_slice.len()) & (n_idx < info.len()) {
|
while (r_idx < reader_slice.len()) | (n_idx < info.len()) {
|
||||||
let n = &info[n_idx];
|
let n = info.get(n_idx);
|
||||||
let r = &reader_slice[r_idx];
|
let r = reader_slice.get(r_idx);
|
||||||
let a = &self.axes[r_idx];
|
let a = self.axes.get(r_idx);
|
||||||
if self.operations.contains_key(a) {
|
match a {
|
||||||
new_slice.push(*r);
|
Some(i) if self.operations.contains_key(i) => {
|
||||||
new_axes.push(*a);
|
new_slice.push(*r.expect("slice should exist for axes under operation"));
|
||||||
|
new_axes.push(*i);
|
||||||
r_idx += 1;
|
r_idx += 1;
|
||||||
} else {
|
}
|
||||||
match (n, r) {
|
_ => match (n, r) {
|
||||||
(
|
(
|
||||||
SliceInfoElem::Slice {
|
Some(SliceInfoElem::Slice {
|
||||||
start: info_start,
|
start: info_start,
|
||||||
end: info_end,
|
end: info_end,
|
||||||
step: info_step,
|
step: info_step,
|
||||||
},
|
}),
|
||||||
SliceInfoElem::Slice { start, end, step },
|
Some(SliceInfoElem::Slice { start, end, step }),
|
||||||
) => {
|
) => {
|
||||||
let new_start = start + info_start;
|
let new_start = start + info_start;
|
||||||
let new_end = match (info_end, end) {
|
let end = end.expect("slice has no end");
|
||||||
(Some(m), Some(n)) => *n.min(&(start + info_step * m)),
|
let new_end = if let Some(m) = info_end {
|
||||||
(None, Some(n)) => *n,
|
end.min(start + info_step * m)
|
||||||
_ => panic!("slice has no end"),
|
} else {
|
||||||
|
end
|
||||||
};
|
};
|
||||||
let new_step = (step * info_step).abs();
|
let new_step = (step * info_step).abs();
|
||||||
|
if new_start > end {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"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,
|
||||||
end: Some(new_end),
|
end: Some(new_end),
|
||||||
step: new_step,
|
step: new_step,
|
||||||
});
|
});
|
||||||
new_axes.push(*a);
|
new_axes.push(*a.expect("axis should exist when slice exists"));
|
||||||
n_idx += 1;
|
n_idx += 1;
|
||||||
r_idx += 1;
|
r_idx += 1;
|
||||||
}
|
}
|
||||||
(SliceInfoElem::Index(k), SliceInfoElem::Slice { start, end, step }) => {
|
(
|
||||||
if *k < 0 {
|
Some(SliceInfoElem::Index(k)),
|
||||||
new_slice.push(SliceInfoElem::Index(end.unwrap_or(0) + step.abs() * k))
|
Some(SliceInfoElem::Slice { start, end, step }),
|
||||||
|
) => {
|
||||||
|
let i = if *k < 0 {
|
||||||
|
end.unwrap_or(0) + step.abs() * k
|
||||||
} else {
|
} else {
|
||||||
new_slice.push(SliceInfoElem::Index(start + step.abs() * k));
|
start + step.abs() * k
|
||||||
|
};
|
||||||
|
let end = end.expect("slice has no end");
|
||||||
|
if i >= end {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Index {} out of bounds {}",
|
||||||
|
i,
|
||||||
|
(end - start) / step
|
||||||
|
));
|
||||||
}
|
}
|
||||||
new_axes.push(*a);
|
new_slice.push(SliceInfoElem::Index(i));
|
||||||
|
new_axes.push(*a.expect("axis should exist when slice exists"));
|
||||||
n_idx += 1;
|
n_idx += 1;
|
||||||
r_idx += 1;
|
r_idx += 1;
|
||||||
}
|
}
|
||||||
(SliceInfoElem::NewAxis, SliceInfoElem::NewAxis) => {
|
(Some(SliceInfoElem::Slice { start, .. }), Some(SliceInfoElem::NewAxis)) => {
|
||||||
|
if *start != 0 {
|
||||||
|
return Err(anyhow!("Index {} out of bounds 1", start));
|
||||||
|
}
|
||||||
|
new_slice.push(SliceInfoElem::NewAxis);
|
||||||
|
new_axes.push(Axis::New);
|
||||||
|
n_idx += 1;
|
||||||
|
r_idx += 1;
|
||||||
|
}
|
||||||
|
(Some(SliceInfoElem::Index(k)), Some(SliceInfoElem::NewAxis)) => {
|
||||||
|
if *k != 0 {
|
||||||
|
return Err(anyhow!("Index {} out of bounds 1", k));
|
||||||
|
}
|
||||||
|
n_idx += 1;
|
||||||
|
r_idx += 1;
|
||||||
|
}
|
||||||
|
(Some(SliceInfoElem::NewAxis), Some(SliceInfoElem::NewAxis)) => {
|
||||||
new_slice.push(SliceInfoElem::NewAxis);
|
new_slice.push(SliceInfoElem::NewAxis);
|
||||||
new_slice.push(SliceInfoElem::NewAxis);
|
new_slice.push(SliceInfoElem::NewAxis);
|
||||||
new_axes.push(Axis::New);
|
new_axes.push(Axis::New);
|
||||||
@@ -352,23 +556,22 @@ impl<D: Dimension> View<D> {
|
|||||||
n_idx += 1;
|
n_idx += 1;
|
||||||
r_idx += 1;
|
r_idx += 1;
|
||||||
}
|
}
|
||||||
(_, SliceInfoElem::NewAxis) => {
|
(Some(SliceInfoElem::NewAxis), _) => {
|
||||||
new_slice.push(SliceInfoElem::NewAxis);
|
|
||||||
new_axes.push(Axis::New);
|
|
||||||
n_idx += 1;
|
|
||||||
r_idx += 1;
|
|
||||||
}
|
|
||||||
(SliceInfoElem::NewAxis, _) => {
|
|
||||||
new_slice.push(SliceInfoElem::NewAxis);
|
new_slice.push(SliceInfoElem::NewAxis);
|
||||||
new_axes.push(Axis::New);
|
new_axes.push(Axis::New);
|
||||||
n_idx += 1;
|
n_idx += 1;
|
||||||
}
|
}
|
||||||
(_, SliceInfoElem::Index(k)) => {
|
(_, Some(SliceInfoElem::Index(k))) => {
|
||||||
new_slice.push(SliceInfoElem::Index(*k));
|
new_slice.push(SliceInfoElem::Index(*k));
|
||||||
new_axes.push(*a);
|
new_axes.push(*a.expect("axis should exist when slice exists"));
|
||||||
r_idx += 1;
|
r_idx += 1;
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("unreachable");
|
||||||
|
// n_idx += 1;
|
||||||
|
// r_idx += 1;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debug_assert_eq!(r_idx, reader_slice.len());
|
debug_assert_eq!(r_idx, reader_slice.len());
|
||||||
@@ -382,37 +585,50 @@ impl<D: Dimension> View<D> {
|
|||||||
.with_operations(self.operations.clone()))
|
.with_operations(self.operations.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// resets axes to cztyx order, with all 5 axes present,
|
||||||
|
/// inserts new axes in place of axes under operation (max_proj etc.)
|
||||||
|
pub fn reset_axes(&self) -> Result<View<Ix5>> {
|
||||||
|
let mut axes = Vec::new();
|
||||||
|
let mut slice = Vec::new();
|
||||||
|
|
||||||
|
for ax in [Axis::C, Axis::Z, Axis::T, Axis::Y, Axis::X] {
|
||||||
|
axes.push(ax);
|
||||||
|
let s = self.slice[ax.pos(&self.axes, &self.slice)?];
|
||||||
|
match s {
|
||||||
|
SliceInfoElem::Slice { .. } => slice.push(s),
|
||||||
|
SliceInfoElem::Index(i) => slice.push(SliceInfoElem::Slice {
|
||||||
|
start: i,
|
||||||
|
end: Some(i + 1),
|
||||||
|
step: 1,
|
||||||
|
}),
|
||||||
|
SliceInfoElem::NewAxis => {
|
||||||
|
panic!("slice should not be NewAxis when axis is one of cztyx")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.operations.contains_key(&ax) {
|
||||||
|
axes.push(Axis::New);
|
||||||
|
slice.push(SliceInfoElem::NewAxis)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(View::new(self.reader.clone(), slice, axes).with_operations(self.operations.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
/// 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>>
|
||||||
where
|
where
|
||||||
I: SliceArg<Ix5>,
|
I: SliceArg<Ix5>,
|
||||||
{
|
{
|
||||||
let axes = self.axes();
|
self.reset_axes()?.slice(info)?.into_dimensionality()
|
||||||
let slice: Vec<_> = info
|
|
||||||
.as_ref()
|
|
||||||
.iter()
|
|
||||||
.zip(self.axes.iter())
|
|
||||||
.filter_map(|(&s, ax)| if axes.contains(ax) { Some(s) } else { None })
|
|
||||||
.collect();
|
|
||||||
let new_axes: Vec<_> = [Axis::C, Axis::Z, Axis::T, Axis::Y, Axis::X]
|
|
||||||
.into_iter()
|
|
||||||
.filter(|ax| axes.contains(ax))
|
|
||||||
.collect();
|
|
||||||
self.clone()
|
|
||||||
.into_dyn()
|
|
||||||
.permute_axes(&new_axes)?
|
|
||||||
.slice(slice.as_slice())?
|
|
||||||
.into_dimensionality()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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>
|
||||||
where
|
where
|
||||||
T: Number,
|
T: Number,
|
||||||
ArrayD<T>: MinMax,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
Array1<T>: MinMax,
|
Array1<T>: MinMax<Output = Array0<T>>,
|
||||||
Array2<T>: MinMax,
|
Array2<T>: MinMax<Output = Array1<T>>,
|
||||||
{
|
{
|
||||||
let slice: Vec<_> = index.iter().map(|s| SliceInfoElem::Index(*s)).collect();
|
let slice: Vec<_> = index.iter().map(|s| SliceInfoElem::Index(*s)).collect();
|
||||||
let view = self.clone().into_dyn().slice(slice.as_slice())?;
|
let view = self.clone().into_dyn().slice(slice.as_slice())?;
|
||||||
@@ -424,9 +640,9 @@ impl<D: Dimension> View<D> {
|
|||||||
pub fn as_array<T>(&self) -> Result<Array<T, D>>
|
pub fn as_array<T>(&self) -> Result<Array<T, D>>
|
||||||
where
|
where
|
||||||
T: Number,
|
T: Number,
|
||||||
ArrayD<T>: MinMax,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
Array1<T>: MinMax,
|
Array1<T>: MinMax<Output = Array0<T>>,
|
||||||
Array2<T>: MinMax,
|
Array2<T>: MinMax<Output = Array1<T>>,
|
||||||
{
|
{
|
||||||
Ok(self.as_array_dyn()?.into_dimensionality()?)
|
Ok(self.as_array_dyn()?.into_dimensionality()?)
|
||||||
}
|
}
|
||||||
@@ -435,9 +651,9 @@ impl<D: Dimension> View<D> {
|
|||||||
pub fn as_array_dyn<T>(&self) -> Result<ArrayD<T>>
|
pub fn as_array_dyn<T>(&self) -> Result<ArrayD<T>>
|
||||||
where
|
where
|
||||||
T: Number,
|
T: Number,
|
||||||
ArrayD<T>: MinMax,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
Array1<T>: MinMax,
|
Array1<T>: MinMax<Output = Array0<T>>,
|
||||||
Array2<T>: MinMax,
|
Array2<T>: MinMax<Output = Array1<T>>,
|
||||||
{
|
{
|
||||||
let mut op_xy = IndexMap::new();
|
let mut op_xy = IndexMap::new();
|
||||||
if let Some((&ax, op)) = self.operations.first() {
|
if let Some((&ax, op)) = self.operations.first() {
|
||||||
@@ -461,9 +677,9 @@ impl<D: Dimension> View<D> {
|
|||||||
for (s, a) in self.slice.iter().zip(&self.axes) {
|
for (s, a) in self.slice.iter().zip(&self.axes) {
|
||||||
match s {
|
match s {
|
||||||
SliceInfoElem::Slice { start, end, step } => {
|
SliceInfoElem::Slice { start, end, step } => {
|
||||||
if let Some(e) = end {
|
let end = end.expect("slice has no end");
|
||||||
if !op_xy.contains_key(a) && !op_czt.contains_key(a) {
|
if !op_xy.contains_key(a) && !op_czt.contains_key(a) {
|
||||||
shape_out.push(((e - start).max(0) / step) as usize);
|
shape_out.push(((end - start).max(0) / step) as usize);
|
||||||
slice.push(SliceInfoElem::Slice {
|
slice.push(SliceInfoElem::Slice {
|
||||||
start: 0,
|
start: 0,
|
||||||
end: None,
|
end: None,
|
||||||
@@ -471,9 +687,6 @@ impl<D: Dimension> View<D> {
|
|||||||
});
|
});
|
||||||
ax_out.push(*a);
|
ax_out.push(*a);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
panic!("slice has no end")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
SliceInfoElem::Index(_) => {}
|
SliceInfoElem::Index(_) => {}
|
||||||
SliceInfoElem::NewAxis => {
|
SliceInfoElem::NewAxis => {
|
||||||
@@ -542,8 +755,10 @@ impl<D: Dimension> View<D> {
|
|||||||
|
|
||||||
let mut axes_out_idx = [None; 5];
|
let mut axes_out_idx = [None; 5];
|
||||||
for (i, ax) in ax_out.iter().enumerate() {
|
for (i, ax) in ax_out.iter().enumerate() {
|
||||||
|
if *ax < Axis::New {
|
||||||
axes_out_idx[*ax as usize] = Some(i);
|
axes_out_idx[*ax as usize] = Some(i);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (c, z, t) in iproduct!(&slice_reader[0], &slice_reader[1], &slice_reader[2]) {
|
for (c, z, t) in iproduct!(&slice_reader[0], &slice_reader[1], &slice_reader[2]) {
|
||||||
if let Some(i) = axes_out_idx[0] {
|
if let Some(i) = axes_out_idx[0] {
|
||||||
@@ -570,17 +785,14 @@ impl<D: Dimension> View<D> {
|
|||||||
let (&ax1, op1) = op_xy.get_index(1).unwrap();
|
let (&ax1, op1) = op_xy.get_index(1).unwrap();
|
||||||
let a = arr_frame.slice(xys).to_owned();
|
let a = arr_frame.slice(xys).to_owned();
|
||||||
let b = op0.operate(a, ax0 as usize - 3)?;
|
let b = op0.operate(a, ax0 as usize - 3)?;
|
||||||
let c: &Array1<T> = unsafe { transmute(&b) };
|
let c = op1.operate(b.to_owned(), ax1 as usize - 3)?;
|
||||||
let d = op1.operate(c.to_owned(), ax1 as usize - 3)?;
|
c.to_owned().into_dyn()
|
||||||
let e: &Array0<T> = unsafe { transmute(&d) };
|
|
||||||
e.to_owned().into_dyn()
|
|
||||||
} else if op_xy.contains_key(&Axis::X) || op_xy.contains_key(&Axis::Y) {
|
} else if op_xy.contains_key(&Axis::X) || op_xy.contains_key(&Axis::Y) {
|
||||||
let xys = slice_info::<Ix1>(&xy)?;
|
let xys = slice_info::<Ix1>(&xy)?;
|
||||||
let (&ax, op) = op_xy.first().unwrap();
|
let (&ax, op) = op_xy.first().unwrap();
|
||||||
let a = arr_frame.slice(xys).to_owned();
|
let a = arr_frame.slice(xys).to_owned();
|
||||||
let b = op.operate(a, ax as usize - 3)?;
|
let b = op.operate(a, ax as usize - 3)?;
|
||||||
let c: &Array0<T> = unsafe { transmute(&b) };
|
b.to_owned().into_dyn()
|
||||||
c.to_owned().into_dyn()
|
|
||||||
} else {
|
} else {
|
||||||
let xys = slice_info::<Ix0>(&xy)?;
|
let xys = slice_info::<Ix0>(&xy)?;
|
||||||
arr_frame.slice(xys).to_owned().into_dyn()
|
arr_frame.slice(xys).to_owned().into_dyn()
|
||||||
@@ -592,8 +804,7 @@ impl<D: Dimension> View<D> {
|
|||||||
let (&ax, op) = op_xy.first().unwrap();
|
let (&ax, op) = op_xy.first().unwrap();
|
||||||
let a = arr_frame.slice(xys).to_owned();
|
let a = arr_frame.slice(xys).to_owned();
|
||||||
let b = op.operate(a, ax as usize - 3)?;
|
let b = op.operate(a, ax as usize - 3)?;
|
||||||
let c: &Array1<T> = unsafe { transmute(&b) };
|
b.to_owned().into_dyn()
|
||||||
c.to_owned().into_dyn()
|
|
||||||
} else {
|
} else {
|
||||||
let xys = slice_info::<Ix1>(&xy)?;
|
let xys = slice_info::<Ix1>(&xy)?;
|
||||||
arr_frame.slice(xys).to_owned().into_dyn()
|
arr_frame.slice(xys).to_owned().into_dyn()
|
||||||
@@ -643,15 +854,21 @@ impl<D: Dimension> View<D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut out = Some(array);
|
let mut out = Some(array);
|
||||||
let ax_out: HashMap<Axis, usize> = ax_out
|
let mut ax_out: HashMap<Axis, usize> = ax_out
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, a)| (a, i))
|
.map(|(i, a)| (a, i))
|
||||||
.collect();
|
.collect();
|
||||||
for (ax, op) in self.operations.iter().skip(op_xy.len() + op_czt.len()) {
|
for (ax, op) in self.operations.iter().skip(op_xy.len() + op_czt.len()) {
|
||||||
if let Some(&idx) = ax_out.get(ax) {
|
if let Some(idx) = ax_out.remove(ax) {
|
||||||
|
for (_, i) in ax_out.iter_mut() {
|
||||||
|
if *i > idx {
|
||||||
|
*i -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
let arr = out.take().unwrap();
|
let arr = out.take().unwrap();
|
||||||
let _ = out.insert(unsafe { transmute_copy(&op.operate(arr, idx)?) });
|
let a = op.operate(arr, idx)?;
|
||||||
|
let _ = out.insert(a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut n = 1;
|
let mut n = 1;
|
||||||
@@ -671,23 +888,59 @@ impl<D: Dimension> View<D> {
|
|||||||
Ok(array)
|
Ok(array)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// retrieve a single frame at czt, sliced accordingly
|
/// turn the view into a 1d array
|
||||||
pub fn get_frame<T>(&self, c: isize, z: isize, t: isize) -> Result<Array2<T>>
|
pub fn flatten<T>(&self) -> Result<Array1<T>>
|
||||||
where
|
where
|
||||||
T: Number,
|
T: Number,
|
||||||
ArrayD<T>: MinMax,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
Array1<T>: MinMax,
|
Array1<T>: MinMax<Output = Array0<T>>,
|
||||||
Array2<T>: MinMax,
|
Array2<T>: MinMax<Output = Array1<T>>,
|
||||||
{
|
{
|
||||||
|
Ok(Array1::from_iter(self.as_array()?.iter().cloned()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// turn the data into a byte vector
|
||||||
|
pub fn to_bytes<T>(&self) -> Result<Vec<u8>>
|
||||||
|
where
|
||||||
|
T: Number + ToBytesVec,
|
||||||
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
|
Array1<T>: MinMax<Output = Array0<T>>,
|
||||||
|
Array2<T>: MinMax<Output = Array1<T>>,
|
||||||
|
{
|
||||||
|
Ok(self
|
||||||
|
.as_array()?
|
||||||
|
.iter()
|
||||||
|
.flat_map(|i| i.to_bytes_vec())
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// retrieve a single frame at czt, sliced accordingly
|
||||||
|
pub fn get_frame<T, N>(&self, c: N, z: N, t: N) -> Result<Array2<T>>
|
||||||
|
where
|
||||||
|
T: Number,
|
||||||
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
|
Array1<T>: MinMax<Output = Array0<T>>,
|
||||||
|
Array2<T>: MinMax<Output = Array1<T>>,
|
||||||
|
N: Display + ToPrimitive,
|
||||||
|
{
|
||||||
|
let c = c
|
||||||
|
.to_isize()
|
||||||
|
.ok_or_else(|| anyhow!("cannot convert {} into isize", c))?;
|
||||||
|
let z = z
|
||||||
|
.to_isize()
|
||||||
|
.ok_or_else(|| anyhow!("cannot convert {} into isize", z))?;
|
||||||
|
let t = t
|
||||||
|
.to_isize()
|
||||||
|
.ok_or_else(|| anyhow!("cannot convert {} into isize", t))?;
|
||||||
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>
|
||||||
where
|
where
|
||||||
T: Number + Sum,
|
T: Number + Sum,
|
||||||
ArrayD<T>: MinMax,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
Array1<T>: MinMax,
|
Array1<T>: MinMax<Output = Array0<T>>,
|
||||||
Array2<T>: MinMax,
|
Array2<T>: MinMax<Output = Array1<T>>,
|
||||||
{
|
{
|
||||||
let arr: ArrayD<T> = self.as_array_dyn()?;
|
let arr: ArrayD<T> = self.as_array_dyn()?;
|
||||||
Ok(match operation {
|
Ok(match operation {
|
||||||
@@ -715,9 +968,9 @@ impl<D: Dimension> View<D> {
|
|||||||
pub fn max<T>(&self) -> Result<T>
|
pub fn max<T>(&self) -> Result<T>
|
||||||
where
|
where
|
||||||
T: Number + Sum,
|
T: Number + Sum,
|
||||||
ArrayD<T>: MinMax,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
Array1<T>: MinMax,
|
Array1<T>: MinMax<Output = Array0<T>>,
|
||||||
Array2<T>: MinMax,
|
Array2<T>: MinMax<Output = Array1<T>>,
|
||||||
{
|
{
|
||||||
self.get_stat(Operation::Max)
|
self.get_stat(Operation::Max)
|
||||||
}
|
}
|
||||||
@@ -726,9 +979,9 @@ impl<D: Dimension> View<D> {
|
|||||||
pub fn min<T>(&self) -> Result<T>
|
pub fn min<T>(&self) -> Result<T>
|
||||||
where
|
where
|
||||||
T: Number + Sum,
|
T: Number + Sum,
|
||||||
ArrayD<T>: MinMax,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
Array1<T>: MinMax,
|
Array1<T>: MinMax<Output = Array0<T>>,
|
||||||
Array2<T>: MinMax,
|
Array2<T>: MinMax<Output = Array1<T>>,
|
||||||
{
|
{
|
||||||
self.get_stat(Operation::Min)
|
self.get_stat(Operation::Min)
|
||||||
}
|
}
|
||||||
@@ -737,9 +990,9 @@ impl<D: Dimension> View<D> {
|
|||||||
pub fn sum<T>(&self) -> Result<T>
|
pub fn sum<T>(&self) -> Result<T>
|
||||||
where
|
where
|
||||||
T: Number + Sum,
|
T: Number + Sum,
|
||||||
ArrayD<T>: MinMax,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
Array1<T>: MinMax,
|
Array1<T>: MinMax<Output = Array0<T>>,
|
||||||
Array2<T>: MinMax,
|
Array2<T>: MinMax<Output = Array1<T>>,
|
||||||
{
|
{
|
||||||
self.get_stat(Operation::Sum)
|
self.get_stat(Operation::Sum)
|
||||||
}
|
}
|
||||||
@@ -748,12 +1001,35 @@ impl<D: Dimension> View<D> {
|
|||||||
pub fn mean<T>(&self) -> Result<T>
|
pub fn mean<T>(&self) -> Result<T>
|
||||||
where
|
where
|
||||||
T: Number + Sum,
|
T: Number + Sum,
|
||||||
ArrayD<T>: MinMax,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
Array1<T>: MinMax,
|
Array1<T>: MinMax<Output = Array0<T>>,
|
||||||
Array2<T>: MinMax,
|
Array2<T>: MinMax<Output = Array1<T>>,
|
||||||
{
|
{
|
||||||
self.get_stat(Operation::Mean)
|
self.get_stat(Operation::Mean)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// gives a helpful summary of the recorded experiment
|
||||||
|
pub fn summary(&self) -> Result<String> {
|
||||||
|
let mut s = "".to_string();
|
||||||
|
s.push_str(&format!("path/filename: {}\n", self.path.display()));
|
||||||
|
s.push_str(&format!("series/pos: {}\n", self.series));
|
||||||
|
s.push_str(&format!("dtype: {:?}\n", self.pixel_type));
|
||||||
|
let axes = self
|
||||||
|
.axes()
|
||||||
|
.into_iter()
|
||||||
|
.map(|ax| format!("{}", ax))
|
||||||
|
.join("")
|
||||||
|
.to_lowercase();
|
||||||
|
let shape = self
|
||||||
|
.shape()
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| format!("{}", s))
|
||||||
|
.join(" x ");
|
||||||
|
let space = " ".repeat(6usize.saturating_sub(axes.len()));
|
||||||
|
s.push_str(&format!("shape ({}):{}{}\n", axes, space, shape));
|
||||||
|
s.push_str(&self.get_ome()?.summary()?);
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: Dimension> Deref for View<D> {
|
impl<D: Dimension> Deref for View<D> {
|
||||||
@@ -768,9 +1044,9 @@ impl<T, D> TryFrom<View<D>> for Array<T, D>
|
|||||||
where
|
where
|
||||||
T: Number,
|
T: Number,
|
||||||
D: Dimension,
|
D: Dimension,
|
||||||
ArrayD<T>: MinMax,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
Array1<T>: MinMax,
|
Array1<T>: MinMax<Output = Array0<T>>,
|
||||||
Array2<T>: MinMax,
|
Array2<T>: MinMax<Output = Array1<T>>,
|
||||||
{
|
{
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
@@ -783,9 +1059,9 @@ impl<T, D> TryFrom<&View<D>> for Array<T, D>
|
|||||||
where
|
where
|
||||||
T: Number,
|
T: Number,
|
||||||
D: Dimension,
|
D: Dimension,
|
||||||
ArrayD<T>: MinMax,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
Array1<T>: MinMax,
|
Array1<T>: MinMax<Output = Array0<T>>,
|
||||||
Array2<T>: MinMax,
|
Array2<T>: MinMax<Output = Array1<T>>,
|
||||||
{
|
{
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
@@ -799,18 +1075,37 @@ pub trait Item {
|
|||||||
fn item<T>(&self) -> Result<T>
|
fn item<T>(&self) -> Result<T>
|
||||||
where
|
where
|
||||||
T: Number,
|
T: Number,
|
||||||
ArrayD<T>: MinMax,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
Array1<T>: MinMax,
|
Array1<T>: MinMax<Output = Array0<T>>,
|
||||||
Array2<T>: MinMax;
|
Array2<T>: MinMax<Output = Array1<T>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View<Ix5> {
|
||||||
|
pub fn from_path<P>(path: P, series: usize) -> Result<Self>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let mut path = path.as_ref().to_path_buf();
|
||||||
|
if path.is_dir() {
|
||||||
|
for file in path.read_dir()?.flatten() {
|
||||||
|
let p = file.path();
|
||||||
|
if file.path().is_file() && (p.extension() == Some("tif".as_ref())) {
|
||||||
|
path = p;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Reader::new(path, series)?.view())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Item for View<Ix0> {
|
impl Item for View<Ix0> {
|
||||||
fn item<T>(&self) -> Result<T>
|
fn item<T>(&self) -> Result<T>
|
||||||
where
|
where
|
||||||
T: Number,
|
T: Number,
|
||||||
ArrayD<T>: MinMax,
|
ArrayD<T>: MinMax<Output = ArrayD<T>>,
|
||||||
Array1<T>: MinMax,
|
Array1<T>: MinMax<Output = Array0<T>>,
|
||||||
Array2<T>: MinMax,
|
Array2<T>: MinMax<Output = Array1<T>>,
|
||||||
{
|
{
|
||||||
Ok(self
|
Ok(self
|
||||||
.as_array()?
|
.as_array()?
|
||||||
@@ -819,3 +1114,36 @@ impl Item for View<Ix0> {
|
|||||||
.clone())
|
.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<D: Dimension> Display for View<D> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
if let Ok(summary) = self.summary() {
|
||||||
|
write!(f, "{}", summary)
|
||||||
|
} else {
|
||||||
|
write!(f, "{}", self.path.display())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// trait to convert numbers to bytes
|
||||||
|
pub trait ToBytesVec {
|
||||||
|
fn to_bytes_vec(&self) -> Vec<u8>;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! to_bytes_vec_impl {
|
||||||
|
($($t:ty $(,)?)*) => {
|
||||||
|
$(
|
||||||
|
impl ToBytesVec for $t {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn to_bytes_vec(&self) -> Vec<u8> {
|
||||||
|
self.to_ne_bytes().to_vec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
to_bytes_vec_impl!(
|
||||||
|
u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64
|
||||||
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user