Skip to content

Commit

Permalink
Refactor volume stuff + add Sliceable trait
Browse files Browse the repository at this point in the history
  • Loading branch information
Enet4 committed Aug 15, 2017
1 parent 917bb2a commit de7d065
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 150 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
target
Cargo.lock
/nifti1.h
/nifti1.h
/.vscode
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
3 changes: 2 additions & 1 deletion src/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
150 changes: 3 additions & 147 deletions src/volume.rs → src/volume/inmem.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<f64>;

/// 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<f32> {
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<Self::Slice>;
}


/// 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
Expand Down Expand Up @@ -286,66 +205,3 @@ impl NiftiVolume for InMemNiftiVolume {
}
}
}

fn coords_to_index(coords: &[u16], dim: &[u16]) -> Result<usize> {
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());
}
}
77 changes: 77 additions & 0 deletions src/volume/mod.rs
Original file line number Diff line number Diff line change
@@ -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<f64>;

/// 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<f32> {
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<Self::Slice>;
}
65 changes: 65 additions & 0 deletions src/volume/util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! Miscellaneous volume-related functions
use error::{Result, NiftiError};

pub fn coords_to_index(coords: &[u16], dim: &[u16]) -> Result<usize> {
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());
}
}

0 comments on commit de7d065

Please sign in to comment.