diff --git a/.gitignore b/.gitignore index cf54bb8..a67b12c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ target Cargo.lock -/nifti1.h \ No newline at end of file +/nifti1.h +/.vscode \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 896b121..0133380 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,6 +67,6 @@ pub use error::{NiftiError, Result}; pub use object::{NiftiObject, InMemNiftiObject}; pub use extension::{Extender, Extension, ExtensionSequence}; pub use header::{NiftiHeader, NiftiHeaderBuilder}; -pub use volume::{NiftiVolume, InMemNiftiVolume}; +pub use volume::{NiftiVolume, InMemNiftiVolume, Sliceable}; pub use typedef::{NiftiType, Unit, Intent, XForm, SliceOrder}; pub use util::Endianness; diff --git a/src/object.rs b/src/object.rs index da971e3..2e8ccb8 100644 --- a/src/object.rs +++ b/src/object.rs @@ -8,7 +8,8 @@ use error::NiftiError; use extension::{ExtensionSequence, Extender}; use header::NiftiHeader; use header::MAGIC_CODE_NI1; -use volume::{NiftiVolume, InMemNiftiVolume}; +use volume::NiftiVolume; +use volume::inmem::InMemNiftiVolume; use util::{Endianness, is_gz_file, to_img_file}; use error::Result; use byteorder::{BigEndian, LittleEndian}; diff --git a/src/volume.rs b/src/volume/inmem.rs similarity index 60% rename from src/volume.rs rename to src/volume/inmem.rs index e8686eb..817fe8b 100644 --- a/src/volume.rs +++ b/src/volume/inmem.rs @@ -1,10 +1,7 @@ -//! This module defines the voxel volume API, as well as data -//! types for reading volumes from files. -//! An integration with `ndarray` allows for more elegant and -//! efficient approaches, and should be preferred when possible. -//! In order to do so, you must add the `ndarray_volumes` feature -//! to this crate. +//! Module holding an in-memory implementation of a NIfTI volume. +use super::NiftiVolume; +use super::util::coords_to_index; use std::io::{Read, BufReader}; use std::fs::File; use std::path::Path; @@ -21,84 +18,6 @@ use num::FromPrimitive; #[cfg(feature = "ndarray_volumes")] use std::ops::{Add, Mul}; #[cfg(feature = "ndarray_volumes")] use num::Num; -/// Public API for NIFTI volume data, exposed as a multi-dimensional -/// voxel array. -/// -/// This API is currently experimental and will likely be subjected to -/// various changes and additions in future versions. -pub trait NiftiVolume { - /// Get the dimensions of the volume. Unlike how NIFTI-1 - /// stores dimensions, the returned slice does not include - /// `dim[0]` and is clipped to the effective number of dimensions. - fn dim(&self) -> &[u16]; - - /// Get the volume's number of dimensions. In a fully compliant file, - /// this is equivalent to the corresponding header's `dim[0]` field - /// (with byte swapping already applied). - fn dimensionality(&self) -> usize { - self.dim().len() - } - - /// Fetch a single voxel's value in the given voxel index coordinates - /// as a double precision floating point value. - /// All necessary conversions and transformations are made - /// when reading the voxel, including scaling. Note that using this - /// function continuously to traverse the volume is inefficient. - /// Prefer using iterators or the `ndarray` API for volume traversal. - /// - /// # Errors - /// - /// - `NiftiError::OutOfBounds` if the given coordinates surpass this - /// volume's boundaries. - fn get_f64(&self, coords: &[u16]) -> Result; - - /// Get this volume's data type. - fn data_type(&self) -> NiftiType; - - /// Fetch a single voxel's value in the given voxel index coordinates - /// as a single precision floating point value. - /// All necessary conversions and transformations are made - /// when reading the voxel, including scaling. Note that using this - /// function continuously to traverse the volume is inefficient. - /// Prefer using iterators or the `ndarray` API for volume traversal. - /// - /// # Errors - /// - /// - `NiftiError::OutOfBounds` if the given coordinates surpass this - /// volume's boundaries. - fn get_f32(&self, coords: &[u16]) -> Result { - let v = self.get_f64(coords)?; - Ok(v as f32) - } -} - -/// Interface for a volume that can be sliced. -pub trait Sliceable { - /// The type of the resulting slice, which is also a volume. - type Slice: NiftiVolume; - - /// Obtain a slice of the volume over a certain axis, yielding a - /// volume of N-1 dimensions. - fn get_slice_f64(&self, axis: u16, index: u16) -> Result; -} - - -/// A data type for a NIFTI-1 volume contained in memory. -/// Objects of this type contain raw image data, which -/// is converted automatically when using reading methods -/// or converting it to an `ndarray` (with the -/// `ndarray_volumes` feature). -#[derive(Debug, PartialEq, Clone)] -pub struct InMemNiftiVolumeRef<'v> { - dim: [u16; 8], - datatype: NiftiType, - scl_slope: f32, - scl_inter: f32, - raw_data: &'v [u8], - endianness: Endianness, -} - - /// A data type for a NIFTI-1 volume contained in memory. /// Objects of this type contain raw image data, which /// is converted automatically when using reading methods @@ -286,66 +205,3 @@ impl NiftiVolume for InMemNiftiVolume { } } } - -fn coords_to_index(coords: &[u16], dim: &[u16]) -> Result { - if coords.len() != dim.len() || coords.is_empty() { - return Err(NiftiError::IncorrectVolumeDimensionality( - dim.len() as u16, - coords.len() as u16 - )) - } - - if !coords.iter().zip(dim).all(|(i, d)| { - *i < (*d) as u16 - }) { - return Err(NiftiError::OutOfBounds(Vec::from(coords))); - } - - let mut crds = coords.into_iter(); - let start = *crds.next_back().unwrap() as usize; - let index = crds.zip(dim).rev() - .fold(start, |a, b| { - a * *b.1 as usize + *b.0 as usize - }); - - Ok(index) -} - -#[cfg(test)] -mod tests { - use super::coords_to_index; - - #[test] - fn test_coords_to_index() { - assert!(coords_to_index(&[0, 0], &[10, 10, 5]).is_err()); - assert!(coords_to_index(&[0, 0, 0, 0], &[10, 10, 5]).is_err()); - assert_eq!( - coords_to_index(&[0, 0, 0], &[10, 10, 5]).unwrap(), - 0 - ); - - assert_eq!( - coords_to_index(&[1, 0, 0], &[16, 16, 3]).unwrap(), - 1 - ); - assert_eq!( - coords_to_index(&[0, 1, 0], &[16, 16, 3]).unwrap(), - 16 - ); - assert_eq!( - coords_to_index(&[0, 0, 1], &[16, 16, 3]).unwrap(), - 256 - ); - assert_eq!( - coords_to_index(&[1, 1, 1], &[16, 16, 3]).unwrap(), - 273 - ); - - assert_eq!( - coords_to_index(&[15, 15, 2], &[16, 16, 3]).unwrap(), - 16 * 16 * 3 - 1 - ); - - assert!(coords_to_index(&[16, 15, 2], &[16, 16, 3]).is_err()); - } -} diff --git a/src/volume/mod.rs b/src/volume/mod.rs new file mode 100644 index 0000000..6285a60 --- /dev/null +++ b/src/volume/mod.rs @@ -0,0 +1,77 @@ +//! This module defines the voxel volume API, as well as data +//! types for reading volumes from files. +//! An integration with `ndarray` allows for more elegant and +//! efficient approaches, and should be preferred when possible. +//! In order to do so, you must add the `ndarray_volumes` feature +//! to this crate. + +pub mod inmem; +pub use self::inmem::*; +mod util; +use error::Result; +use typedef::NiftiType; + +#[cfg(feature = "ndarray_volumes")] use ndarray::{Array, Ix, IxDyn, ShapeBuilder}; +#[cfg(feature = "ndarray_volumes")] use std::ops::{Add, Mul}; +#[cfg(feature = "ndarray_volumes")] use num::Num; + +/// Public API for NIFTI volume data, exposed as a multi-dimensional +/// voxel array. +/// +/// This API is currently experimental and will likely be subjected to +/// various changes and additions in future versions. +pub trait NiftiVolume { + /// Get the dimensions of the volume. Unlike how NIFTI-1 + /// stores dimensions, the returned slice does not include + /// `dim[0]` and is clipped to the effective number of dimensions. + fn dim(&self) -> &[u16]; + + /// Get the volume's number of dimensions. In a fully compliant file, + /// this is equivalent to the corresponding header's `dim[0]` field + /// (with byte swapping already applied). + fn dimensionality(&self) -> usize { + self.dim().len() + } + + /// Fetch a single voxel's value in the given voxel index coordinates + /// as a double precision floating point value. + /// All necessary conversions and transformations are made + /// when reading the voxel, including scaling. Note that using this + /// function continuously to traverse the volume is inefficient. + /// Prefer using iterators or the `ndarray` API for volume traversal. + /// + /// # Errors + /// + /// - `NiftiError::OutOfBounds` if the given coordinates surpass this + /// volume's boundaries. + fn get_f64(&self, coords: &[u16]) -> Result; + + /// Get this volume's data type. + fn data_type(&self) -> NiftiType; + + /// Fetch a single voxel's value in the given voxel index coordinates + /// as a single precision floating point value. + /// All necessary conversions and transformations are made + /// when reading the voxel, including scaling. Note that using this + /// function continuously to traverse the volume is inefficient. + /// Prefer using iterators or the `ndarray` API for volume traversal. + /// + /// # Errors + /// + /// - `NiftiError::OutOfBounds` if the given coordinates surpass this + /// volume's boundaries. + fn get_f32(&self, coords: &[u16]) -> Result { + let v = self.get_f64(coords)?; + Ok(v as f32) + } +} + +/// Interface for a volume that can be sliced. +pub trait Sliceable { + /// The type of the resulting slice, which is also a volume. + type Slice: NiftiVolume; + + /// Obtain a slice of the volume over a certain axis, yielding a + /// volume of N-1 dimensions. + fn get_slice(&self, axis: u16, index: u16) -> Result; +} diff --git a/src/volume/util.rs b/src/volume/util.rs new file mode 100644 index 0000000..77048fe --- /dev/null +++ b/src/volume/util.rs @@ -0,0 +1,65 @@ +//! Miscellaneous volume-related functions +use error::{Result, NiftiError}; + +pub fn coords_to_index(coords: &[u16], dim: &[u16]) -> Result { + if coords.len() != dim.len() || coords.is_empty() { + return Err(NiftiError::IncorrectVolumeDimensionality( + dim.len() as u16, + coords.len() as u16 + )) + } + + if !coords.iter().zip(dim).all(|(i, d)| { + *i < (*d) as u16 + }) { + return Err(NiftiError::OutOfBounds(Vec::from(coords))); + } + + let mut crds = coords.into_iter(); + let start = *crds.next_back().unwrap() as usize; + let index = crds.zip(dim).rev() + .fold(start, |a, b| { + a * *b.1 as usize + *b.0 as usize + }); + + Ok(index) +} + +#[cfg(test)] +mod tests { + use super::coords_to_index; + + #[test] + fn test_coords_to_index() { + assert!(coords_to_index(&[0, 0], &[10, 10, 5]).is_err()); + assert!(coords_to_index(&[0, 0, 0, 0], &[10, 10, 5]).is_err()); + assert_eq!( + coords_to_index(&[0, 0, 0], &[10, 10, 5]).unwrap(), + 0 + ); + + assert_eq!( + coords_to_index(&[1, 0, 0], &[16, 16, 3]).unwrap(), + 1 + ); + assert_eq!( + coords_to_index(&[0, 1, 0], &[16, 16, 3]).unwrap(), + 16 + ); + assert_eq!( + coords_to_index(&[0, 0, 1], &[16, 16, 3]).unwrap(), + 256 + ); + assert_eq!( + coords_to_index(&[1, 1, 1], &[16, 16, 3]).unwrap(), + 273 + ); + + assert_eq!( + coords_to_index(&[15, 15, 2], &[16, 16, 3]).unwrap(), + 16 * 16 * 3 - 1 + ); + + assert!(coords_to_index(&[16, 15, 2], &[16, 16, 3]).is_err()); + } +}