Skip to content

Commit

Permalink
Merge pull request #59 from nilgoyette/ndarray13
Browse files Browse the repository at this point in the history
ndarray 0.13 + build_dim_array
  • Loading branch information
Enet4 authored Oct 1, 2019
2 parents 5b3d44d + 3e1dee0 commit 39f2e49
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 52 deletions.
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ branch = "master"
repository = "Enet4/nifti-rs"

[dependencies]
approx = "0.3"
byteordered = "0.4.0"
flate2 = "1.0.1"
num = "0.2.0"
Expand All @@ -41,10 +42,10 @@ version = "0.18"

[dependencies.ndarray]
optional = true
version = ">=0.10.12,<0.13.0"
version = "0.13"
features = ["approx"]

[dev-dependencies]
approx = "0.3.0"
pretty_assertions = "0.6.1"
tempfile = "3.0"

Expand Down
23 changes: 13 additions & 10 deletions src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@

#[cfg(feature = "nalgebra_affine")]
use crate::affine::*;
#[cfg(feature = "nalgebra_affine")]
use alga::general::SubsetOf;
use byteordered::{ByteOrdered, Endian, Endianness};
use crate::error::{NiftiError, Result};
use crate::typedef::*;
use crate::util::{is_gz_file, validate_dim, validate_dimensionality};
#[cfg(feature = "nalgebra_affine")]
use alga::general::SubsetOf;
use byteordered::{ByteOrdered, Endian, Endianness};
use flate2::bufread::GzDecoder;
#[cfg(feature = "nalgebra_affine")]
use nalgebra::{Matrix3, Matrix4, Quaternion, RealField, Vector3};
Expand Down Expand Up @@ -196,9 +196,9 @@ impl Default for NiftiHeader {
quatern_y: 0.,
quatern_z: 0.,

srow_x: [1., 0., 0., 0.,],
srow_y: [0., 1., 0., 0.,],
srow_z: [0., 0., 1., 0.,],
srow_x: [1., 0., 0., 0.],
srow_y: [0., 1., 0., 0.],
srow_z: [0., 0., 1., 0.],

intent_name: [0; 16],

Expand Down Expand Up @@ -243,9 +243,9 @@ impl NiftiHeader {
/// Retrieve and validate 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.
///
///
/// # Error
///
///
/// `NiftiError::InconsistentDim` if `dim[0]` does not represent a valid
/// dimensionality, or any of the real dimensions are zero.
pub fn dim(&self) -> Result<&[u16]> {
Expand All @@ -254,9 +254,9 @@ impl NiftiHeader {

/// Retrieve and validate the number of dimensions of the volume. This is
/// `dim[0]` after the necessary byte order conversions.
///
///
/// # Error
///
///
/// `NiftiError::` if `dim[0]` does not represent a valid dimensionality
/// (it must be positive and not higher than 7).
pub fn dimensionality(&self) -> Result<usize> {
Expand Down Expand Up @@ -386,6 +386,7 @@ impl NiftiHeader {
T: RealField,
f32: SubsetOf<T>,
{
#[rustfmt::skip]
let affine = Matrix4::new(
self.srow_x[0], self.srow_x[1], self.srow_x[2], self.srow_x[3],
self.srow_y[0], self.srow_y[1], self.srow_y[2], self.srow_y[3],
Expand Down Expand Up @@ -415,6 +416,7 @@ impl NiftiHeader {
self.pixdim[3] as f64 * self.pixdim[0] as f64,
));
let m = r * s;
#[rustfmt::skip]
let affine = Matrix4::new(
m[0], m[3], m[6], self.quatern_x as f64,
m[1], m[4], m[7], self.quatern_y as f64,
Expand Down Expand Up @@ -509,6 +511,7 @@ impl NiftiHeader {
(aff2[3] + aff2[4] + aff2[5]).sqrt(),
(aff2[6] + aff2[7] + aff2[8]).sqrt(),
);
#[rustfmt::skip]
let mut r = Matrix3::new(
affine[0] / spacing.0, affine[3] / spacing.1, affine[6] / spacing.2,
affine[1] / spacing.0, affine[4] / spacing.1, affine[7] / spacing.2,
Expand Down
2 changes: 1 addition & 1 deletion src/volume/ndarray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,6 @@ where
{
// TODO optimize this implementation (we don't need the whole volume)
let volume = self.volume.into_ndarray()?;
Ok(volume.into_subview(Axis(self.axis as Ix), self.index as usize))
Ok(volume.index_axis_move(Axis(self.axis as Ix), self.index as usize))
}
}
11 changes: 8 additions & 3 deletions src/volume/shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
//!
//! [`Dim`]: ./struct.Dim.html
//! [`Idx`]: ./struct.Idx.html
use num_traits::AsPrimitive;

use crate::error::{NiftiError, Result};
use crate::util::{validate_dim, validate_dimensionality};

Expand Down Expand Up @@ -137,14 +139,17 @@ impl Dim {
/// assert_eq!(dim.as_ref(), &[64, 32, 16]);
/// # Ok::<(), nifti::NiftiError>(())
/// ```
pub fn from_slice(dim: &[u16]) -> Result<Self> {
pub fn from_slice<T>(dim: &[T]) -> Result<Self>
where
T: 'static + Copy + AsPrimitive<u16>,
{
if dim.len() == 0 || dim.len() > 7 {
return Err(NiftiError::InconsistentDim(0, dim.len() as u16));
}
let mut raw = [0; 8];
let mut raw = [1; 8];
raw[0] = dim.len() as u16;
for (i, d) in dim.iter().enumerate() {
raw[i + 1] = *d;
raw[i + 1] = d.as_();
}
let _ = validate_dim(&raw)?;
Ok(Dim(Idx(raw)))
Expand Down
12 changes: 3 additions & 9 deletions src/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use safe_transmute::{transmute_to_bytes, TriviallyTransmutable};
use crate::{
header::{MAGIC_CODE_NI1, MAGIC_CODE_NIP1},
util::{adapt_bytes, is_gz_file, is_hdr_file},
volume::element::DataElement,
NiftiHeader, NiftiType, Result,
volume::shape::Dim,
DataElement, NiftiHeader, NiftiType, Result,
};

/// Write a nifti file (.nii or .nii.gz).
Expand Down Expand Up @@ -165,12 +165,6 @@ where
T: Data,
D: Dimension,
{
let mut dim = [1; 8];
dim[0] = data.ndim() as u16;
for (i, s) in data.shape().iter().enumerate() {
dim[i + 1] = *s as u16;
}

// If no reference header is given, use the default.
let reference = match reference {
Some(r) => r.clone(),
Expand All @@ -182,7 +176,7 @@ where
};

let mut header = NiftiHeader {
dim,
dim: *Dim::from_slice(data.shape())?.raw(),
sizeof_hdr: 348,
datatype: datatype as i16,
bitpix: (datatype.size_of() * 8) as i16,
Expand Down
55 changes: 28 additions & 27 deletions tests/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ mod tests {
path::{Path, PathBuf},
};

use ndarray::{s, Array, Array2, Axis, Dimension, IxDyn, ShapeBuilder};
use approx::assert_abs_diff_eq;
use ndarray::{s, Array, Array1, Array2, Axis, Dimension, Ix2, IxDyn, ShapeBuilder};
use num_traits::AsPrimitive;
use tempfile::tempdir;

use nifti::{
header::{MAGIC_CODE_NI1, MAGIC_CODE_NIP1},
object::NiftiObject,
volume::shape::Dim,
writer::{write_nifti, write_rgb_nifti},
DataElement, InMemNiftiObject, IntoNdArray, NiftiHeader, NiftiType,
};
Expand Down Expand Up @@ -81,18 +83,15 @@ mod tests {
(header, dyn_data.into_dimensionality::<D>().unwrap())
}

fn test_write_read(arr: &Array<f32, IxDyn>, path: &str) {
fn test_write_read(arr: Array<f32, IxDyn>, path: &str) {
let path = get_temporary_path(path);
let mut dim = [1; 8];
dim[0] = arr.ndim() as u16;
for (i, s) in arr.shape().iter().enumerate() {
dim[i + 1] = *s as u16;
}
let dim = *Dim::from_slice(arr.shape()).unwrap().raw();
let header = generate_nifti_header(dim, 1.0, 0.0, NiftiType::Float32);
write_nifti(&path, &arr, Some(&header)).unwrap();

let gt = arr.into_dimensionality::<Ix2>().unwrap();
let read_nifti: Array2<f32> = read_as_ndarray(path).1;
assert!(read_nifti.all_close(&arr, 1e-10));
assert_abs_diff_eq!(read_nifti, gt, epsilon = 1e-10);
}

fn f_order_array() -> Array<f32, IxDyn> {
Expand All @@ -111,36 +110,36 @@ mod tests {
fn fortran_writing() {
// Test .nii
let arr = f_order_array();
test_write_read(&arr, "test.nii");
test_write_read(arr, "test.nii");
let mut arr = f_order_array();
arr.invert_axis(Axis(1));
test_write_read(&arr, "test_non_contiguous.nii");
test_write_read(arr, "test_non_contiguous.nii");

// Test .nii.gz
let arr = f_order_array();
test_write_read(&arr, "test.nii.gz");
test_write_read(arr, "test.nii.gz");
let mut arr = f_order_array();
arr.invert_axis(Axis(1));
test_write_read(&arr, "test_non_contiguous.nii.gz");
test_write_read(arr, "test_non_contiguous.nii.gz");
}

#[test]
fn c_writing() {
// Test .nii
let arr = c_order_array();
test_write_read(&arr, "test.nii");
test_write_read(arr, "test.nii");

let mut arr = c_order_array();
arr.invert_axis(Axis(1));
test_write_read(&arr, "test_non_contiguous.nii");
test_write_read(arr, "test_non_contiguous.nii");

// Test .nii.gz
let arr = c_order_array();
test_write_read(&arr, "test.nii.gz");
test_write_read(arr, "test.nii.gz");

let mut arr = c_order_array();
arr.invert_axis(Axis(1));
test_write_read(&arr, "test_non_contiguous.nii.gz");
test_write_read(arr, "test_non_contiguous.nii.gz");
}

#[test]
Expand All @@ -150,22 +149,22 @@ mod tests {
let inter = 101.1;

let path = get_temporary_path("test_slope_inter.nii");
let mut dim = [1; 8];
dim[0] = arr.ndim() as u16;
for (i, s) in arr.shape().iter().enumerate() {
dim[i + 1] = *s as u16;
}
let dim = *Dim::from_slice(arr.shape()).unwrap().raw();
let header = generate_nifti_header(dim, slope, inter, NiftiType::Float32);
let transformed_data = arr.mul(slope).add(inter);
write_nifti(&path, &transformed_data, Some(&header)).unwrap();

let gt = transformed_data.into_dimensionality::<Ix2>().unwrap();
let read_nifti: Array2<f32> = read_as_ndarray(path).1;
assert!(read_nifti.all_close(&transformed_data, 1e-10));
assert_abs_diff_eq!(read_nifti, gt, epsilon = 1e-10);
}

#[test]
fn half_slope() {
let data = Array::from_iter(0..216).into_shape((6, 6, 6)).unwrap();
let data = (0..216)
.collect::<Array1<_>>()
.into_shape((6, 6, 6))
.unwrap();
let dim = [3, 6, 6, 6, 1, 1, 1, 1];
let slope = 0.4;
let inter = 100.1;
Expand Down Expand Up @@ -200,11 +199,13 @@ mod tests {

let path = get_temporary_path("non_contiguous_0.nii.gz");
write_nifti(&path, &data.slice(s![.., .., ..;2]), None).unwrap();
assert_eq!(read_as_ndarray(path).1, Array::from_elem((3, 4, 6), 42.0));
let loaded_data = read_as_ndarray::<_, f32, _>(path).1;
assert_eq!(loaded_data, Array::from_elem((3, 4, 6), 42.0));

let path = get_temporary_path("non_contiguous_1.nii.gz");
write_nifti(&path, &data.slice(s![.., .., 1..;2]), None).unwrap();
assert_eq!(read_as_ndarray(path).1, Array::from_elem((3, 4, 5), 1.5));
let loaded_data = read_as_ndarray::<_, f32, _>(path).1;
assert_eq!(loaded_data, Array::from_elem((3, 4, 5), 1.5));
}

#[test]
Expand All @@ -227,14 +228,14 @@ mod tests {
// set_description
header.set_description("ひらがな".as_bytes()).unwrap();
write_nifti(&path, &data, Some(&header)).unwrap();
let (new_header, new_data) = read_as_ndarray(&path);
let (new_header, new_data) = read_as_ndarray::<_, f32, _>(&path);
assert_eq!(new_header, header);
assert_eq!(new_data, data);

// set_description_str
header.set_description_str("русский").unwrap();
write_nifti(&path, &data, Some(&header)).unwrap();
let (new_header, new_data) = read_as_ndarray(&path);
let (new_header, new_data) = read_as_ndarray::<_, f32, _>(&path);
assert_eq!(new_header, header);
assert_eq!(new_data, data);
}
Expand Down

0 comments on commit 39f2e49

Please sign in to comment.