666 lines
23 KiB
Rust
666 lines
23 KiB
Rust
use crate::error::Error;
|
|
use ndarray::Array2;
|
|
use ome_metadata::Ome;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::fmt::Debug;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use crate::readers::{ArrayT, DynReader, Frame, PixelType, Reader, Shape};
|
|
use itertools::Itertools;
|
|
use j4rs::{Instance, InvocationArg, JavaOpt, Jvm, JvmBuilder};
|
|
use std::cell::OnceCell;
|
|
use std::collections::HashSet;
|
|
use std::hash::{Hash, Hasher};
|
|
use std::ops::Deref;
|
|
use std::rc::Rc;
|
|
use std::sync::Mutex;
|
|
use thread_local::ThreadLocal;
|
|
|
|
include!(concat!(env!("OUT_DIR"), "/constants.rs"));
|
|
|
|
thread_local! {
|
|
static JVM: OnceCell<Rc<Jvm>> = const { OnceCell::new() }
|
|
}
|
|
|
|
static DOWNLOAD_LOCK: Mutex<()> = Mutex::new(());
|
|
static JVM_BUILT: Mutex<bool> = Mutex::new(false);
|
|
|
|
/// Ensure 1 jvm per thread
|
|
fn jvm() -> Rc<Jvm> {
|
|
JVM.with(|cell| {
|
|
cell.get_or_init(move || {
|
|
#[cfg(feature = "python")]
|
|
let path = crate::py::ndbioimage_file();
|
|
|
|
#[cfg(not(feature = "python"))]
|
|
let path = std::env::current_exe()
|
|
.unwrap()
|
|
.parent()
|
|
.unwrap()
|
|
.to_path_buf();
|
|
|
|
let class_path = if path.join("jassets").exists() {
|
|
path.as_path()
|
|
} else {
|
|
path.parent().unwrap()
|
|
};
|
|
|
|
// download jars if needed, but make sure only one thread will do this
|
|
{
|
|
let _guard = DOWNLOAD_LOCK.lock().unwrap();
|
|
let jassets = class_path.join("jassets");
|
|
if !jassets.exists() {
|
|
std::fs::create_dir_all(&jassets).unwrap();
|
|
}
|
|
|
|
if !jassets.join(format!("j4rs-{}-jar-with-dependencies.jar", J4RS_VERSION)).exists() {
|
|
println!("downloading j4rs-{}-jar-with-dependencies.jar into {}", J4RS_VERSION, jassets.display());
|
|
let download = downloader::Download::new(&format!(
|
|
"https://github.com/astonbitecode/j4rs/raw/v{}/rust/jassets/j4rs-{}-jar-with-dependencies.jar",
|
|
J4RS_VERSION, J4RS_VERSION
|
|
));
|
|
let mut downloader = downloader::Downloader::builder()
|
|
.download_folder(&jassets)
|
|
.build().unwrap();
|
|
downloader
|
|
.download(&[download]).unwrap()
|
|
.into_iter()
|
|
.collect::<Result<Vec<_>, _>>().unwrap();
|
|
}
|
|
|
|
if !jassets.join(format!("bioformats_package-{}.jar", BIOFORMATS_VERSION)).exists() {
|
|
println!("downloading bioformats_package-{}.jar into {}", BIOFORMATS_VERSION, jassets.display());
|
|
let download = downloader::Download::new(&format!(
|
|
"https://artifacts.openmicroscopy.org/artifactory/ome.releases/ome/bioformats_package/{}/bioformats_package-{}.jar",
|
|
BIOFORMATS_VERSION, BIOFORMATS_VERSION
|
|
));
|
|
let mut downloader = downloader::Downloader::builder()
|
|
.download_folder(&jassets)
|
|
.build().unwrap();
|
|
downloader
|
|
.download(&[download]).unwrap()
|
|
.into_iter()
|
|
.collect::<Result<Vec<_>, _>>().unwrap();
|
|
}
|
|
|
|
#[cfg(feature = "gpl-formats")]
|
|
if !jassets.join(format!("formats-gpl-{}.jar", BIOFORMATS_VERSION)).exists() {
|
|
println!("downloading formats-gpl-{}.jar into {}", BIOFORMATS_VERSION, jassets.display());
|
|
let download = downloader::Download::new(&format!(
|
|
"https://artifacts.openmicroscopy.org/artifactory/ome.releases/ome/formats-gpl/{}/formats-gpl-{}.jar",
|
|
BIOFORMATS_VERSION, BIOFORMATS_VERSION
|
|
));
|
|
let mut downloader = downloader::Downloader::builder()
|
|
.download_folder(&jassets)
|
|
.build().unwrap();
|
|
downloader
|
|
.download(&[download]).unwrap()
|
|
.into_iter()
|
|
.collect::<Result<Vec<_>, _>>().unwrap();
|
|
}
|
|
}
|
|
{
|
|
let mut jvm_built = JVM_BUILT.lock().unwrap();
|
|
Rc::new(if *jvm_built {
|
|
Jvm::attach_thread().expect("Failed to attach to JVM")
|
|
} else {
|
|
*jvm_built = true;
|
|
let j = JvmBuilder::new()
|
|
.skip_setting_native_lib()
|
|
.with_base_path(class_path.to_str().unwrap())
|
|
.java_opt(JavaOpt::new("-XX:+IgnoreUnrecognizedVMOptions"))
|
|
.java_opt(JavaOpt::new("--enable-native-access=ALL-UNNAMED"))
|
|
.java_opt(JavaOpt::new("--sun-misc-unsafe-memory-access=allow"))
|
|
.build()
|
|
.expect("Failed to build JVM");
|
|
if let Ok(e) = InvocationArg::try_from("ERROR") {
|
|
let _ = j.invoke_static(
|
|
"loci.common.DebugTools",
|
|
"setRootLevel",
|
|
&[e],
|
|
);
|
|
}
|
|
j
|
|
})
|
|
}
|
|
})
|
|
.clone()
|
|
})
|
|
}
|
|
|
|
macro_rules! method_return {
|
|
($R:ty$(|c)?) => { Result<$R, Error> };
|
|
() => { Result<(), Error> };
|
|
}
|
|
|
|
macro_rules! method_arg {
|
|
($n:tt: $t:ty|p) => {
|
|
InvocationArg::try_from($n)?.into_primitive()?
|
|
};
|
|
($n:tt: $t:ty) => {
|
|
InvocationArg::try_from($n)?
|
|
};
|
|
}
|
|
|
|
macro_rules! method {
|
|
($name:ident, $method:expr $(,[$($n:tt: $t:ty$(|$p:tt)?),*])? $(=> $tt:ty$(|$c:tt)?)?) => {
|
|
#[allow(dead_code)]
|
|
pub(crate) fn $name(&self, $($($n: $t),*)?) -> method_return!($($tt)?) {
|
|
let args: Vec<InvocationArg> = vec![$($( method_arg!($n:$t$(|$p)?) ),*)?];
|
|
let _result = jvm().invoke(&self.0, $method, &args)?;
|
|
|
|
macro_rules! method_result {
|
|
($R:ty|c) => {
|
|
Ok(jvm().to_rust(_result)?)
|
|
};
|
|
($R:ty|d) => {
|
|
Ok(jvm().to_rust_deserialized(_result)?)
|
|
};
|
|
($R:ty) => {
|
|
Ok(_result)
|
|
};
|
|
() => {
|
|
Ok(())
|
|
};
|
|
}
|
|
|
|
method_result!($($tt$(|$c)?)?)
|
|
}
|
|
};
|
|
}
|
|
|
|
fn transmute_vec<T, U>(vec: Vec<T>) -> Vec<U> {
|
|
unsafe {
|
|
// Ensure the original vector is not dropped.
|
|
let mut v_clone = std::mem::ManuallyDrop::new(vec);
|
|
Vec::from_raw_parts(
|
|
v_clone.as_mut_ptr() as *mut U,
|
|
v_clone.len(),
|
|
v_clone.capacity(),
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Wrapper around bioformats java class loci.common.DebugTools
|
|
pub struct DebugTools;
|
|
|
|
impl DebugTools {
|
|
/// set debug root level: ERROR, DEBUG, TRACE, INFO, OFF
|
|
pub fn set_root_level(level: &str) -> Result<(), Error> {
|
|
jvm().invoke_static(
|
|
"loci.common.DebugTools",
|
|
"setRootLevel",
|
|
&[InvocationArg::try_from(level)?],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Wrapper around bioformats java class loci.formats.ChannelSeparator
|
|
pub(crate) struct ChannelSeparator(Instance);
|
|
|
|
impl ChannelSeparator {
|
|
pub(crate) fn new(image_reader: &ImageReader) -> Result<Self, Error> {
|
|
let jvm = jvm();
|
|
let channel_separator = jvm.create_instance(
|
|
"loci.formats.ChannelSeparator",
|
|
&[InvocationArg::from(jvm.clone_instance(&image_reader.0)?)],
|
|
)?;
|
|
Ok(ChannelSeparator(channel_separator))
|
|
}
|
|
|
|
pub(crate) fn open_bytes(&self, index: i32) -> Result<Vec<u8>, Error> {
|
|
Ok(transmute_vec(self.open_bi8(index)?))
|
|
}
|
|
|
|
method!(open_bi8, "openBytes", [index: i32|p] => Vec<i8>|c);
|
|
method!(get_index, "getIndex", [z: i32|p, c: i32|p, t: i32|p] => i32|c);
|
|
}
|
|
|
|
/// Wrapper around bioformats java class loci.formats.ImageReader
|
|
pub struct ImageReader(Instance);
|
|
|
|
impl Drop for ImageReader {
|
|
fn drop(&mut self) {
|
|
self.close().unwrap()
|
|
}
|
|
}
|
|
|
|
impl ImageReader {
|
|
pub(crate) fn new() -> Result<Self, Error> {
|
|
let reader = jvm().create_instance("loci.formats.ImageReader", InvocationArg::empty())?;
|
|
Ok(ImageReader(reader))
|
|
}
|
|
|
|
pub(crate) fn open_bytes(&self, index: i32) -> Result<Vec<u8>, Error> {
|
|
Ok(transmute_vec(self.open_bi8(index)?))
|
|
}
|
|
|
|
pub(crate) fn ome_xml(&self) -> Result<String, Error> {
|
|
let mds = self.get_metadata_store()?;
|
|
Ok(jvm()
|
|
.chain(&mds)?
|
|
.cast("loci.formats.ome.OMEPyramidStore")?
|
|
.invoke("dumpXML", InvocationArg::empty())?
|
|
.to_rust()?)
|
|
}
|
|
|
|
method!(close, "close");
|
|
method!(is_indexed, "isIndexed" => bool|c);
|
|
method!(is_interleaved, "isInterleaved" => bool|c);
|
|
method!(is_little_endian, "isLittleEndian" => bool|c);
|
|
method!(is_rgb, "isRGB" => bool|c);
|
|
method!(get_8bit_lookup_table, "get8BitLookupTable" => Instance);
|
|
method!(get_16bit_lookup_table, "get16BitLookupTable" => Instance);
|
|
method!(get_dimension_order, "getDimensionOrder" => String|c);
|
|
method!(set_id, "setId", [id: &str]);
|
|
method!(get_index, "getIndex", [z: i32|p, c: i32|p, t: i32|p] => i32|c);
|
|
method!(set_metadata_store, "setMetadataStore", [ome_data: Instance]);
|
|
method!(get_metadata_store, "getMetadataStore" => Instance);
|
|
method!(get_pixel_type, "getPixelType" => i32|c);
|
|
method!(get_rgb_channel_count, "getRGBChannelCount" => i32|c);
|
|
method!(get_series, "getSeries" => i32|c);
|
|
method!(set_series, "setSeries", [series: i32|p]);
|
|
method!(get_series_count, "getSeriesCount" => i32|c);
|
|
method!(get_size_x, "getSizeX" => i32|c);
|
|
method!(get_size_y, "getSizeY" => i32|c);
|
|
method!(get_size_c, "getSizeC" => i32|c);
|
|
method!(get_size_t, "getSizeT" => i32|c);
|
|
method!(get_size_z, "getSizeZ" => i32|c);
|
|
method!(open_bi8, "openBytes", [index: i32|p] => Vec<i8>|c);
|
|
}
|
|
|
|
/// Wrapper around bioformats java class loci.formats.MetadataTools
|
|
pub(crate) struct MetadataTools(Instance);
|
|
|
|
impl MetadataTools {
|
|
pub(crate) fn new() -> Result<Self, Error> {
|
|
let meta_data_tools =
|
|
jvm().create_instance("loci.formats.MetadataTools", InvocationArg::empty())?;
|
|
Ok(MetadataTools(meta_data_tools))
|
|
}
|
|
|
|
method!(create_ome_xml_metadata, "createOMEXMLMetadata" => Instance);
|
|
}
|
|
|
|
/// Reader interface to file. Use get_frame to get data.
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct BioFormatsJavaReader {
|
|
#[serde(skip)]
|
|
reader: ThreadLocal<ImageReader>,
|
|
/// path to file
|
|
path: PathBuf,
|
|
/// which (if more than 1) of the series in the file to open
|
|
series: usize,
|
|
shape: Shape,
|
|
pixel_type: PixelType,
|
|
little_endian: bool,
|
|
}
|
|
|
|
impl From<BioFormatsJavaReader> for DynReader {
|
|
fn from(value: BioFormatsJavaReader) -> Self {
|
|
DynReader::BioFormatsJava(value)
|
|
}
|
|
}
|
|
|
|
impl Hash for BioFormatsJavaReader {
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
self.path.hash(state);
|
|
self.series.hash(state);
|
|
}
|
|
}
|
|
|
|
impl PartialEq for BioFormatsJavaReader {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.path == other.path
|
|
&& self.series == other.series
|
|
&& self.shape == other.shape
|
|
&& self.pixel_type == other.pixel_type
|
|
&& self.little_endian == other.little_endian
|
|
}
|
|
}
|
|
|
|
impl Eq for BioFormatsJavaReader {}
|
|
|
|
impl Deref for BioFormatsJavaReader {
|
|
type Target = ImageReader;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
self.get_reader().unwrap()
|
|
}
|
|
}
|
|
|
|
impl Clone for BioFormatsJavaReader {
|
|
fn clone(&self) -> Self {
|
|
// BioFormatsReader::new(&self.path, self.series, 0).unwrap()
|
|
Self {
|
|
reader: ThreadLocal::default(),
|
|
path: self.path.clone(),
|
|
series: self.series,
|
|
shape: self.shape.clone(),
|
|
pixel_type: self.pixel_type,
|
|
little_endian: self.little_endian,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Debug for BioFormatsJavaReader {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("BioFormatsJavaReader")
|
|
.field("path", &self.path)
|
|
.field("series", &self.series)
|
|
.field("shape", &self.shape)
|
|
.field("pixel_type", &self.pixel_type)
|
|
.field("little_endian", &self.little_endian)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl BioFormatsJavaReader {
|
|
fn get_reader(&self) -> Result<&ImageReader, Error> {
|
|
self.reader.get_or_try(|| {
|
|
let reader = ImageReader::new()?;
|
|
let meta_data_tools = MetadataTools::new()?;
|
|
let ome_meta = meta_data_tools.create_ome_xml_metadata()?;
|
|
reader.set_metadata_store(ome_meta)?;
|
|
reader.set_id(self.path.to_str().ok_or(Error::InvalidFileName)?)?;
|
|
reader.set_series(self.series as i32)?;
|
|
Ok(reader)
|
|
})
|
|
}
|
|
|
|
// pub fn set_reader(&self) -> Result<(), Error> {
|
|
// self.get_reader().map(|_| ())
|
|
// }
|
|
|
|
/// Get ome metadata as ome structure
|
|
pub fn get_ome(&self) -> Result<Ome, Error> {
|
|
let mut ome = Ome::from_xml(self.ome_xml()?)?;
|
|
if ome.image.len() > 1 {
|
|
ome.image = vec![ome.image[self.series].clone()];
|
|
}
|
|
Ok(ome)
|
|
}
|
|
|
|
/// Get ome metadata as xml string
|
|
pub fn get_ome_xml(&self) -> Result<String, Error> {
|
|
self.ome_xml()
|
|
}
|
|
|
|
fn deinterleave(&self, bytes: Vec<u8>, channel: usize) -> Result<Vec<u8>, Error> {
|
|
let chunk_size = match self.pixel_type {
|
|
PixelType::Bool => 1,
|
|
PixelType::I8 => 1,
|
|
PixelType::U8 => 1,
|
|
PixelType::I16 => 2,
|
|
PixelType::U16 => 2,
|
|
PixelType::I32 => 4,
|
|
PixelType::U32 => 4,
|
|
PixelType::F32 => 4,
|
|
PixelType::F64 => 8,
|
|
PixelType::I64 => 8,
|
|
PixelType::U64 => 8,
|
|
PixelType::I128 => 16,
|
|
PixelType::U128 => 16,
|
|
PixelType::F128 => 8,
|
|
};
|
|
Ok(bytes
|
|
.chunks(chunk_size)
|
|
.skip(channel)
|
|
.step_by(self.shape.c)
|
|
.flat_map(|a| a.to_vec())
|
|
.collect())
|
|
}
|
|
|
|
fn bytes_to_frame(&self, bytes: Vec<u8>) -> Result<Frame, Error> {
|
|
macro_rules! get_frame {
|
|
(bool, <$n:expr) => {
|
|
Ok(ArrayT::from(Array2::from_shape_vec(
|
|
(self.shape.y, self.shape.x),
|
|
bytes
|
|
.iter()
|
|
.map(|x| [x & 128, x & 64, x & 32, x & 16, x & 8, x & 4, x & 2, x & 1])
|
|
.flatten()
|
|
.collect(),
|
|
)?))
|
|
};
|
|
(bool, >$n:expr) => {
|
|
Ok(ArrayT::from(Array2::from_shape_vec(
|
|
(self.shape.y, self.shape.x),
|
|
bytes
|
|
.iter()
|
|
.map(|x| [x & 1, x & 2, x & 4, x & 8, x & 16, x & 32, x & 64, x & 128])
|
|
.flatten()
|
|
.collect(),
|
|
)?))
|
|
};
|
|
($t:tt, <$n:expr) => {
|
|
Ok(ArrayT::from(Array2::from_shape_vec(
|
|
(self.shape.y, self.shape.x),
|
|
bytes
|
|
.chunks($n)
|
|
.map(|x| $t::from_le_bytes(x.try_into().unwrap()))
|
|
.collect(),
|
|
)?))
|
|
};
|
|
($t:tt, >$n:expr) => {
|
|
Ok(ArrayT::from(Array2::from_shape_vec(
|
|
(self.shape.y, self.shape.x),
|
|
bytes
|
|
.chunks($n)
|
|
.map(|x| $t::from_be_bytes(x.try_into().unwrap()))
|
|
.collect(),
|
|
)?))
|
|
};
|
|
}
|
|
|
|
match (&self.pixel_type, self.little_endian) {
|
|
(PixelType::Bool, true) => get_frame!(bool, <1),
|
|
(PixelType::I8, true) => get_frame!(i8, <1),
|
|
(PixelType::U8, true) => get_frame!(u8, <1),
|
|
(PixelType::I16, true) => get_frame!(i16, <2),
|
|
(PixelType::U16, true) => get_frame!(u16, <2),
|
|
(PixelType::I32, true) => get_frame!(i32, <4),
|
|
(PixelType::U32, true) => get_frame!(u32, <4),
|
|
(PixelType::F32, true) => get_frame!(f32, <4),
|
|
(PixelType::F64, true) => get_frame!(f64, <8),
|
|
(PixelType::I64, true) => get_frame!(i64, <8),
|
|
(PixelType::U64, true) => get_frame!(u64, <8),
|
|
(PixelType::I128, true) => get_frame!(i128, <16),
|
|
(PixelType::U128, true) => get_frame!(u128, <16),
|
|
(PixelType::F128, true) => get_frame!(f64, <8),
|
|
(PixelType::Bool, false) => get_frame!(bool, >1),
|
|
(PixelType::I8, false) => get_frame!(i8, >1),
|
|
(PixelType::U8, false) => get_frame!(u8, >1),
|
|
(PixelType::I16, false) => get_frame!(i16, >2),
|
|
(PixelType::U16, false) => get_frame!(u16, >2),
|
|
(PixelType::I32, false) => get_frame!(i32, >4),
|
|
(PixelType::U32, false) => get_frame!(u32, >4),
|
|
(PixelType::F32, false) => get_frame!(f32, >4),
|
|
(PixelType::F64, false) => get_frame!(f64, >8),
|
|
(PixelType::I64, false) => get_frame!(i64, >8),
|
|
(PixelType::U64, false) => get_frame!(u64, >8),
|
|
(PixelType::I128, false) => get_frame!(i128, >16),
|
|
(PixelType::U128, false) => get_frame!(u128, >16),
|
|
(PixelType::F128, false) => get_frame!(f64, >8),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for BioFormatsJavaReader {
|
|
fn drop(&mut self) {
|
|
if let Ok(reader) = self.get_reader() {
|
|
reader.close().unwrap();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn find_tiff(path: &Path) -> Result<Option<PathBuf>, Error> {
|
|
if let Some(ext) = path.extension()
|
|
&& path.is_file()
|
|
&& (["tif", "tiff"].contains(&ext.to_string_lossy().to_lowercase().as_str()))
|
|
{
|
|
return Ok(Some(path.to_path_buf()));
|
|
} else if path.is_dir() {
|
|
for file in path.read_dir()?.flatten().sorted_by_key(|i| i.file_name()) {
|
|
if let Ok(Some(file)) = find_tiff(file.path().as_path()) {
|
|
return Ok(Some(file));
|
|
}
|
|
}
|
|
}
|
|
Ok(None)
|
|
}
|
|
|
|
impl Reader for BioFormatsJavaReader {
|
|
/// Create a new reader for the image file at a path, and open series #.
|
|
fn new<P>(path: P, series: usize, _position: usize) -> Result<Self, Error>
|
|
where
|
|
P: AsRef<Path>,
|
|
{
|
|
let mut path = path.as_ref().to_path_buf();
|
|
if path.is_dir() {
|
|
let orig = path.clone();
|
|
path = find_tiff(&path)?.ok_or_else(|| {
|
|
Error::FileDoesNotExist(orig.join("**").join("*.tif").display().to_string())
|
|
})?;
|
|
}
|
|
let mut new = Self {
|
|
reader: ThreadLocal::default(),
|
|
path,
|
|
series,
|
|
shape: Shape::default(),
|
|
pixel_type: PixelType::I8,
|
|
little_endian: false,
|
|
};
|
|
// new.set_reader()?;
|
|
new.shape.x = new.get_size_x()? as usize;
|
|
new.shape.y = new.get_size_y()? as usize;
|
|
new.shape.c = new.get_size_c()? as usize;
|
|
new.shape.z = new.get_size_z()? as usize;
|
|
new.shape.t = new.get_size_t()? as usize;
|
|
new.pixel_type = PixelType::try_from(new.get_pixel_type()?)?;
|
|
new.little_endian = new.is_little_endian()?;
|
|
Ok(new)
|
|
}
|
|
|
|
fn metadata(&self) -> Result<Ome, Error> {
|
|
self.get_ome()
|
|
}
|
|
|
|
/// Retrieve fame at channel c, slize z and time t.
|
|
fn get_frame(&self, c: usize, z: usize, t: usize) -> Result<Frame, Error> {
|
|
let bytes = if self.is_rgb()? && self.is_interleaved()? {
|
|
let index = self.get_index(z as i32, 0, t as i32)?;
|
|
self.deinterleave(self.open_bytes(index)?, c)?
|
|
} else if self.get_rgb_channel_count()? > 1 {
|
|
let channel_separator = ChannelSeparator::new(self)?;
|
|
let index = channel_separator.get_index(z as i32, c as i32, t as i32)?;
|
|
channel_separator.open_bytes(index)?
|
|
} else {
|
|
let index = self.get_index(z as i32, c as i32, t as i32)?;
|
|
self.open_bytes(index)?
|
|
};
|
|
self.bytes_to_frame(bytes)
|
|
}
|
|
|
|
fn path(&self) -> &Path {
|
|
&self.path
|
|
}
|
|
|
|
fn series(&self) -> usize {
|
|
self.series
|
|
}
|
|
|
|
fn position(&self) -> usize {
|
|
0
|
|
}
|
|
|
|
fn shape(&self) -> &Shape {
|
|
&self.shape
|
|
}
|
|
|
|
fn pixel_type(&self) -> &PixelType {
|
|
&self.pixel_type
|
|
}
|
|
|
|
fn get_available_positions<P>(_path: P, _series: usize) -> Result<HashSet<usize>, Error>
|
|
where
|
|
P: AsRef<Path>,
|
|
{
|
|
Ok(HashSet::from([0]))
|
|
}
|
|
|
|
fn get_available_series<P>(path: P) -> Result<HashSet<usize>, Error>
|
|
where
|
|
P: AsRef<Path>,
|
|
{
|
|
let mut path = path.as_ref().to_path_buf();
|
|
if path.is_dir() {
|
|
let orig = path.clone();
|
|
path = find_tiff(&path)?.ok_or_else(|| {
|
|
Error::FileDoesNotExist(orig.join("**").join("*.tif").display().to_string())
|
|
})?;
|
|
}
|
|
let new = BioFormatsJavaReader {
|
|
reader: ThreadLocal::default(),
|
|
path,
|
|
series: 0,
|
|
shape: Shape::default(),
|
|
pixel_type: PixelType::I8,
|
|
little_endian: false,
|
|
};
|
|
Ok(HashSet::from_iter(0..(new.get_series_count()? as usize)))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn open(file: &str) -> Result<BioFormatsJavaReader, Error> {
|
|
let path = std::env::current_dir()?
|
|
.join("tests")
|
|
.join("files")
|
|
.join(file);
|
|
BioFormatsJavaReader::new(&path, 0, 0)
|
|
}
|
|
|
|
macro_rules! test_metadata {
|
|
($($name:ident: $file:expr $(,)?)*) => {
|
|
$(
|
|
#[test]
|
|
fn $name() -> Result<(), Error> {
|
|
let bf = open($file)?;
|
|
println!("{}", bf.view().squeeze()?.summary()?);
|
|
Ok(())
|
|
}
|
|
)*
|
|
};
|
|
}
|
|
|
|
test_metadata! {
|
|
metadata_a: "czi/1xp53-01-AP1.czi",
|
|
metadata_b: "czi/beads_2023_05_04__19_00_22.czi",
|
|
metadata_c: "czi/Experiment-2029.czi",
|
|
metadata_d: "czi/MK022_cE9_1-01-Airyscan Processing-01-Scene-2-P1.czi",
|
|
metadata_e: "czi/YTL1849A131_2023_05_04__13_36_36.czi",
|
|
metadata_f: "czi/EU_UV_t=1-01.czi",
|
|
metadata_g: "tiffseq/4-Pos_001_002/img_000000000_Cy3-Cy3_filter_000.tif",
|
|
metadata_h: "tiffseq/20-Pos_005_005/img_000000000_Cy3-Cy3_filter_000.tif",
|
|
metadata_i: "tiffseq/YTL1841B2-2-1_1hr_DMSO_galinduction_1",
|
|
}
|
|
|
|
#[test]
|
|
fn ome_xml() -> Result<(), Error> {
|
|
let file = "czi/Experiment-2029.czi";
|
|
let path = std::env::current_dir()?
|
|
.join("tests")
|
|
.join("files")
|
|
.join(file);
|
|
let reader = BioFormatsJavaReader::new(&path, 0, 0)?;
|
|
let xml = reader.get_ome_xml()?;
|
|
println!("{}", xml);
|
|
Ok(())
|
|
}
|
|
}
|