use super::shape::Dim;
use super::util::coords_to_index;
use crate::error::{NiftiError, Result};
use crate::header::NiftiHeader;
use crate::typedef::NiftiType;
use crate::util::{nb_bytes_for_data, nb_bytes_for_dim_datatype};
use crate::volume::element::{DataElement, NiftiDataRescaler};
use crate::volume::{FromSource, FromSourceOptions, NiftiVolume, RandomAccessNiftiVolume};
use byteordered::Endianness;
use flate2::bufread::GzDecoder;
use num_traits::Num;
use std::fs::File;
use std::io::{BufReader, Read};
use std::ops::{Add, Mul};
use std::path::Path;
#[cfg(feature = "ndarray_volumes")]
use super::ndarray::IntoNdArray;
#[cfg(feature = "ndarray_volumes")]
use ndarray::{Array, Ix, IxDyn, ShapeBuilder};
macro_rules! fn_convert_and_cast {
($fname: ident, $typ: ty, $converter: expr) => {
#[cfg(feature = "ndarray_volumes")]
fn $fname<O>(self) -> Result<Array<O, IxDyn>>
where
O: DataElement,
{
let dim: Vec<_> = self.dim().iter().map(|d| *d as Ix).collect();
let data: Vec<_> = <$typ as DataElement>::from_raw_vec(self.raw_data, self.endianness)?;
let mut data: Vec<O> = data.into_iter().map($converter).collect();
<O as DataElement>::DataRescaler::nifti_rescale_many_inline(
&mut data,
self.scl_slope,
self.scl_inter,
);
Ok(Array::from_shape_vec(IxDyn(&dim).f(), data).expect("Inconsistent raw data size"))
}
};
}
#[derive(Debug, PartialEq, Clone)]
pub struct InMemNiftiVolume {
dim: Dim,
datatype: NiftiType,
scl_slope: f32,
scl_inter: f32,
raw_data: Vec<u8>,
endianness: Endianness,
}
impl InMemNiftiVolume {
pub fn from_raw_data(header: &NiftiHeader, raw_data: Vec<u8>) -> Result<Self> {
let nbytes = nb_bytes_for_data(header)?;
if nbytes != raw_data.len() {
return Err(NiftiError::IncompatibleLength(raw_data.len(), nbytes));
}
let datatype = header.data_type()?;
Ok(InMemNiftiVolume {
dim: Dim::new(header.dim)?,
datatype,
scl_slope: header.scl_slope,
scl_inter: header.scl_inter,
raw_data,
endianness: header.endianness,
})
}
pub fn from_raw_fields(
raw_dim: [u16; 8],
datatype: NiftiType,
scl_slope: f32,
scl_inter: f32,
raw_data: Vec<u8>,
endianness: Endianness,
) -> Result<Self> {
let dim = Dim::new(raw_dim)?;
let nbytes = nb_bytes_for_dim_datatype(dim.as_ref(), datatype);
if nbytes != Some(raw_data.len()) {
return Err(NiftiError::IncompatibleLength(
raw_data.len(),
nbytes.unwrap_or(usize::max_value()),
));
}
Ok(InMemNiftiVolume {
dim,
datatype,
scl_slope,
scl_inter,
raw_data,
endianness,
})
}
pub fn from_reader<R: Read>(source: R, header: &NiftiHeader) -> Result<Self> {
let nb_bytes = nb_bytes_for_data(header)?;
let mut raw_data = Vec::new();
raw_data
.try_reserve_exact(nb_bytes)
.map_err(|e| NiftiError::ReserveVolume(nb_bytes, e))?;
let nb_bytes_written = source.take(nb_bytes as u64).read_to_end(&mut raw_data)?;
if nb_bytes_written != nb_bytes {
return Err(NiftiError::IncompatibleLength(nb_bytes_written, nb_bytes));
}
let datatype = header.data_type()?;
Ok(InMemNiftiVolume {
dim: Dim::new(header.dim)?,
datatype,
scl_slope: header.scl_slope,
scl_inter: header.scl_inter,
raw_data,
endianness: header.endianness,
})
}
pub fn from_file<P: AsRef<Path>>(path: P, header: &NiftiHeader) -> Result<Self> {
let gz = path
.as_ref()
.extension()
.map(|a| a.to_string_lossy() == "gz")
.unwrap_or(false);
let file = BufReader::new(File::open(path)?);
if gz {
InMemNiftiVolume::from_reader(GzDecoder::new(file), header)
} else {
InMemNiftiVolume::from_reader(file, header)
}
}
pub fn into_raw_data(self) -> Vec<u8> {
self.raw_data
}
pub fn raw_data(&self) -> &[u8] {
&self.raw_data
}
pub fn raw_data_mut(&mut self) -> &mut [u8] {
&mut self.raw_data
}
pub fn into_nifti_typed_data<T>(self) -> Result<Vec<T>>
where
T: DataElement,
{
T::from_raw_vec_validated(self.raw_data, self.endianness, self.datatype)
}
fn get_prim<T>(&self, coords: &[u16]) -> Result<T>
where
T: DataElement,
T: NiftiDataRescaler<T>,
T: Num,
T: Copy,
T: Mul<Output = T>,
T: Add<Output = T>,
{
let index = coords_to_index(coords, self.dim())?;
let range = &self.raw_data[index * self.datatype.size_of()..];
self.datatype
.read_primitive_value(range, self.endianness, self.scl_slope, self.scl_inter)
}
fn_convert_and_cast!(convert_and_cast_u8, u8, DataElement::from_u8);
fn_convert_and_cast!(convert_and_cast_i8, i8, DataElement::from_i8);
fn_convert_and_cast!(convert_and_cast_u16, u16, DataElement::from_u16);
fn_convert_and_cast!(convert_and_cast_i16, i16, DataElement::from_i16);
fn_convert_and_cast!(convert_and_cast_u32, u32, DataElement::from_u32);
fn_convert_and_cast!(convert_and_cast_i32, i32, DataElement::from_i32);
fn_convert_and_cast!(convert_and_cast_u64, u64, DataElement::from_u64);
fn_convert_and_cast!(convert_and_cast_i64, i64, DataElement::from_i64);
fn_convert_and_cast!(convert_and_cast_f32, f32, DataElement::from_f32);
fn_convert_and_cast!(convert_and_cast_f64, f64, DataElement::from_f64);
#[cfg(feature = "ndarray_volumes")]
fn no_cast_convert_to_ndarray<T>(self) -> Result<Array<T, IxDyn>>
where
T: DataElement,
{
let dim: Vec<_> = self.dim().iter().map(|d| *d as Ix).collect();
let mut data: Vec<_> = <T as DataElement>::from_raw_vec(self.raw_data, self.endianness)?;
if self.datatype != NiftiType::Rgb24
&& self.datatype != NiftiType::Rgba32
&& self.scl_slope != 0.0
{
<T as DataElement>::DataRescaler::nifti_rescale_many_inline(
&mut data,
self.scl_slope,
self.scl_inter,
);
}
Ok(Array::from_shape_vec(IxDyn(&dim).f(), data).expect("Inconsistent raw data size"))
}
}
impl FromSourceOptions for InMemNiftiVolume {
type Options = ();
}
impl<R> FromSource<R> for InMemNiftiVolume
where
R: Read,
{
fn from_reader(reader: R, header: &NiftiHeader, (): Self::Options) -> Result<Self> {
InMemNiftiVolume::from_reader(reader, header)
}
}
#[cfg(feature = "ndarray_volumes")]
impl IntoNdArray for InMemNiftiVolume {
fn into_ndarray<T>(self) -> Result<Array<T, IxDyn>>
where
T: DataElement,
{
match self.datatype {
NiftiType::Uint8 => self.convert_and_cast_u8::<T>(),
NiftiType::Int8 => self.convert_and_cast_i8::<T>(),
NiftiType::Uint16 => self.convert_and_cast_u16::<T>(),
NiftiType::Int16 => self.convert_and_cast_i16::<T>(),
NiftiType::Uint32 => self.convert_and_cast_u32::<T>(),
NiftiType::Int32 => self.convert_and_cast_i32::<T>(),
NiftiType::Uint64 => self.convert_and_cast_u64::<T>(),
NiftiType::Int64 => self.convert_and_cast_i64::<T>(),
NiftiType::Float32 => self.convert_and_cast_f32::<T>(),
NiftiType::Float64 => self.convert_and_cast_f64::<T>(),
NiftiType::Complex64 => self.no_cast_convert_to_ndarray::<T>(),
NiftiType::Complex128 => self.no_cast_convert_to_ndarray::<T>(),
NiftiType::Rgb24 => self.no_cast_convert_to_ndarray::<T>(),
NiftiType::Rgba32 => self.no_cast_convert_to_ndarray::<T>(),
_ => Err(NiftiError::UnsupportedDataType(self.datatype)),
}
}
}
#[cfg(feature = "ndarray_volumes")]
impl<'a> IntoNdArray for &'a InMemNiftiVolume {
fn into_ndarray<T>(self) -> Result<Array<T, IxDyn>>
where
T: DataElement,
{
self.clone().into_ndarray()
}
}
impl<'a> NiftiVolume for &'a InMemNiftiVolume {
fn dim(&self) -> &[u16] {
(**self).dim()
}
fn dimensionality(&self) -> usize {
(**self).dimensionality()
}
fn data_type(&self) -> NiftiType {
(**self).data_type()
}
}
impl NiftiVolume for InMemNiftiVolume {
fn dim(&self) -> &[u16] {
self.dim.as_ref()
}
fn dimensionality(&self) -> usize {
self.dim.rank()
}
fn data_type(&self) -> NiftiType {
self.datatype
}
}
impl RandomAccessNiftiVolume for InMemNiftiVolume {
fn get_f32(&self, coords: &[u16]) -> Result<f32> {
self.get_prim(coords)
}
fn get_f64(&self, coords: &[u16]) -> Result<f64> {
self.get_prim(coords)
}
fn get_u8(&self, coords: &[u16]) -> Result<u8> {
self.get_prim(coords)
}
fn get_i8(&self, coords: &[u16]) -> Result<i8> {
self.get_prim(coords)
}
fn get_u16(&self, coords: &[u16]) -> Result<u16> {
self.get_prim(coords)
}
fn get_i16(&self, coords: &[u16]) -> Result<i16> {
self.get_prim(coords)
}
fn get_u32(&self, coords: &[u16]) -> Result<u32> {
self.get_prim(coords)
}
fn get_i32(&self, coords: &[u16]) -> Result<i32> {
self.get_prim(coords)
}
fn get_u64(&self, coords: &[u16]) -> Result<u64> {
self.get_prim(coords)
}
fn get_i64(&self, coords: &[u16]) -> Result<i64> {
self.get_prim(coords)
}
}
impl<'a> RandomAccessNiftiVolume for &'a InMemNiftiVolume {
fn get_f32(&self, coords: &[u16]) -> Result<f32> {
(**self).get_f32(coords)
}
fn get_f64(&self, coords: &[u16]) -> Result<f64> {
(**self).get_f64(coords)
}
fn get_u8(&self, coords: &[u16]) -> Result<u8> {
(**self).get_u8(coords)
}
fn get_i8(&self, coords: &[u16]) -> Result<i8> {
(**self).get_i8(coords)
}
fn get_u16(&self, coords: &[u16]) -> Result<u16> {
(**self).get_u16(coords)
}
fn get_i16(&self, coords: &[u16]) -> Result<i16> {
(**self).get_i16(coords)
}
fn get_u32(&self, coords: &[u16]) -> Result<u32> {
(**self).get_u32(coords)
}
fn get_i32(&self, coords: &[u16]) -> Result<i32> {
(**self).get_i32(coords)
}
fn get_u64(&self, coords: &[u16]) -> Result<u64> {
(**self).get_u64(coords)
}
fn get_i64(&self, coords: &[u16]) -> Result<i64> {
(**self).get_i64(coords)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::typedef::NiftiType;
use crate::volume::shape::Dim;
use crate::volume::Sliceable;
use byteordered::Endianness;
#[test]
fn test_u8_inmem_volume() {
let data: Vec<u8> = (0..64).map(|x| x * 2).collect();
let vol = InMemNiftiVolume {
dim: Dim::new([3, 4, 4, 4, 0, 0, 0, 0]).unwrap(),
datatype: NiftiType::Uint8,
scl_slope: 1.,
scl_inter: -5.,
raw_data: data,
endianness: Endianness::Little,
};
let v = vol.get_f32(&[3, 1, 0]).unwrap();
assert_eq!(v, 9.);
let v = vol.get_f32(&[3, 3, 3]).unwrap();
assert_eq!(v, 121.);
let v = vol.get_f32(&[2, 1, 1]).unwrap();
assert_eq!(v, 39.);
assert!(vol.get_f32(&[4, 0, 0]).is_err());
}
#[test]
fn test_u8_inmem_volume_slice() {
let data: Vec<u8> = (0..64).map(|x| x * 2).collect();
let vol = InMemNiftiVolume {
dim: Dim::new([3, 4, 4, 4, 0, 0, 0, 0]).unwrap(),
datatype: NiftiType::Uint8,
scl_slope: 1.,
scl_inter: -5.,
raw_data: data,
endianness: Endianness::Little,
};
let slice = (&vol).get_slice(0, 3).unwrap();
assert_eq!(slice.dim(), &[4, 4]);
assert_eq!(slice.dimensionality(), 2);
let v = slice.get_f32(&[1, 0]).unwrap();
assert_eq!(v, 9.);
let v = slice.get_f32(&[3, 3]).unwrap();
assert_eq!(v, 121.);
let slice = (&vol).get_slice(1, 1).unwrap();
assert_eq!(slice.dim(), &[4, 4]);
assert_eq!(slice.dimensionality(), 2);
let v = slice.get_f32(&[2, 1]).unwrap();
assert_eq!(v, 39.);
}
#[test]
fn test_false_4d() {
let (w, h, d) = (5, 5, 5);
let mut header = NiftiHeader {
dim: [4, w, h, d, 1, 1, 1, 1],
datatype: 2,
bitpix: 8,
..Default::default()
};
let raw_data = vec![0; (w * h * d) as usize];
let mut volume = InMemNiftiVolume::from_raw_data(&header, raw_data).unwrap();
assert_eq!(header.dim[0], 4);
assert_eq!(volume.dimensionality(), 4);
if header.dim[header.dim[0] as usize] == 1 {
header.dim[0] -= 1;
volume = InMemNiftiVolume::from_raw_data(&header, volume.into_raw_data()).unwrap();
}
assert_eq!(volume.dimensionality(), 3);
#[cfg(feature = "ndarray_volumes")]
{
use ndarray::Ix3;
let dyn_data = volume.into_ndarray::<f32>().unwrap();
assert_eq!(dyn_data.ndim(), 3);
let data = dyn_data.into_dimensionality::<Ix3>().unwrap();
assert_eq!(data.ndim(), 3); }
}
}