Skip to content

Commit

Permalink
Fix stuff!
Browse files Browse the repository at this point in the history
- make NIFTI header builder work with struct-level default;
- delegate file name gz-checking and conversion from header to volume file to util functions, add separate tests for them;
- expose nifti::Result;
- fix reading objects;
- document all the public things;
- also more doctests.
  • Loading branch information
Enet4 committed May 25, 2017
1 parent d5f7fb5 commit 1cf9e92
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 58 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ quick-error = "1.2.0"

[dependencies.derive_builder]
version = "0.4.7"
features = ["struct_default"]

[dependencies.ndarray]
optional = true
Expand Down
96 changes: 87 additions & 9 deletions src/extension.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
//! This module contains definitions for the extension and related types.
//! Extensions are optional data frames sitting before the voxel data.
//! When present, an extender frame of 4 bytes is also present at the
//! end of the NIFTI-1 header, with the first byte set to something
//! other than 0.
use std::io::{Read, ErrorKind as IoErrorKind};
use error::{NiftiError, Result};
use byteorder::{ByteOrder, ReadBytesExt};

/// Data type for the extender code.
#[derive(Debug, Default, PartialEq, Clone, Copy)]
pub struct Extender {
extension: [u8; 4],
}
pub struct Extender([u8; 4]);

impl Extender {

/// Fetch the extender code from the given source, while expecting it to exist.
pub fn from_stream<S: Read>(mut source: S) -> Result<Self> {
let mut extension = [0u8; 4];
source.read_exact(&mut extension)?;
Ok(Extender { extension })
Ok(extension.into())
}

/// Fetch the extender code from the given source, while
Expand All @@ -23,28 +28,85 @@ impl Extender {
pub fn from_stream_optional<S: Read>(mut source: S) -> Result<Option<Self>> {
let mut extension = [0u8; 4];
match source.read_exact(&mut extension) {
Ok(_) => Ok(Some(Extender { extension })),
Ok(()) => Ok(Some(extension.into())),
Err(ref e) if e.kind() == IoErrorKind::UnexpectedEof => {
println!("[Extender]: No data!");
Ok(None)
}
Err(e) => Err(NiftiError::from(e))
}
}

/// Whether extensions should exist after this extender code.
/// Whether extensions should exist upon this extender code.
pub fn has_extensions(&self) -> bool {
self.extension[0] != 0
self.0[0] != 0
}

/// Get the extender's bytes
pub fn as_bytes(&self) -> &[u8; 4] {
&self.0
}
}

impl From<[u8; 4]> for Extender {
fn from(extender: [u8; 4]) -> Self {
Extender(extender)
}
}

/// Data type for the raw contents of an extension.
/// Users of this type have to reinterpret the data
/// to suit their needs.
#[derive(Debug, PartialEq, Clone)]
pub struct Extension {
esize: i32,
ecode: i32,
edata: Vec<u8>,
}

impl Extension {

/// Create an extension out of its main components.
///
/// # Panics
/// If `esize` does not correspond to the full size
/// of the extension in bytes: `8 + edata.len()`
pub fn new(esize: i32, ecode: i32, edata: Vec<u8>) -> Self {
if esize as usize != 8 + edata.len() {
panic!("Illegal extension size: esize is {}, but full size is {}",
esize, edata.len());
}

Extension {
esize,
ecode,
edata
}
}

/// Obtain the claimed extension raw size (`esize` field).
pub fn size(&self) -> i32 {
self.esize
}

/// Obtain the extension's code (`ecode` field).
pub fn code(&self) -> i32 {
self.ecode
}

/// Obtain the extension's data (`edata` field).
pub fn data(&self) -> &Vec<u8> {
&self.edata
}

/// Take the extension's raw data, discarding the rest.
pub fn into_data(self) -> Vec<u8> {
self.edata
}
}

/// Data type for aggregating the extender code and
/// all extensions.
#[derive(Debug, PartialEq, Clone)]
pub struct ExtensionSequence {
extender: Extender,
Expand Down Expand Up @@ -82,11 +144,11 @@ impl ExtensionSequence {
let data_size = esize as usize - 8;
let mut edata = vec![0u8; data_size];
source.read_exact(&mut edata)?;
extensions.push(Extension {
extensions.push(Extension::new(
esize,
ecode,
edata
});
));
offset += esize as usize;
}
}
Expand All @@ -97,8 +159,24 @@ impl ExtensionSequence {
})
}

/// Obtain an iterator to the extensions.
pub fn iter(&self) -> ::std::slice::Iter<Extension> {
self.extensions.iter()
}

/// Whether the sequence of extensions is empty.
pub fn is_empty(&self) -> bool {
self.extensions.is_empty()
}

/// Obtain the number of extensions available.
pub fn len(&self) -> usize {
self.extensions.len()
}

/// Get the extender code from this extension sequence.
pub fn extender(&self) -> Extender {
self.extender
}
}

45 changes: 45 additions & 0 deletions src/header.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//! This module defines the `NiftiHeader` struct, which is used
//! to provide important information about NIFTI-1 volumes.
use error::{NiftiError, Result};
use util::{Endianness, OppositeNativeEndian, is_gz_file};
use std::fs::File;
Expand All @@ -6,14 +9,56 @@ use std::path::Path;
use byteorder::{ByteOrder, ReadBytesExt, NativeEndian};
use flate2::bufread::GzDecoder;

/// Magic code for NIFTI-1 header files (extention ".hdr[.gz]").
pub const MAGIC_CODE_NI1 : &'static [u8; 4] = b"ni1\0";
/// Magic code for full NIFTI-1 files (extention ".nii[.gz]").
pub const MAGIC_CODE_NIP1 : &'static [u8; 4] = b"n+1\0";

/// The NIFTI-1 header data type.
/// All fields are public and named after the specification's header file.
/// The type of each field was adjusted according to their use and
/// array limitations. A builder is also available.
///
/// # Examples
///
/// ```no_run
/// use nifti::{NiftiHeader, Endianness};
/// # use nifti::Result;
///
/// # fn run() -> Result<()> {
/// let (hdr1, endianness): (NiftiHeader, Endianness) =
/// NiftiHeader::from_file("0000.hdr")?;
///
/// let hdr2: NiftiHeader = NiftiHeader::from_file("0001.hdr.gz")?.0;
/// let (hdr3, end3) = NiftiHeader::from_file("4321.nii.gz")?;
/// # Ok(())
/// # }
/// ```
///
/// Or to build one yourself:
///
/// ```
/// use nifti::NiftiHeaderBuilder;
/// # use std::error::Error;
///
/// # fn run() -> Result<(), Box<Error>> {
/// let hdr = NiftiHeaderBuilder::default()
/// .cal_min(0.)
/// .cal_max(128.)
/// .build()?;
/// assert_eq!(hdr.cal_min, 0.);
/// assert_eq!(hdr.cal_max, 128.);
/// # Ok(())
/// # }
/// # run().unwrap();
/// ```
#[derive(Debug, Clone, PartialEq, Builder)]
#[builder(derive(Debug))]
#[builder(field(public))]
#[builder(default)]
pub struct NiftiHeader {
/// Header size, must be 348
#[builder(default="348")]
pub sizeof_hdr: i32,
/// Unused in NIFTI-1
pub data_type: [u8; 10],
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,10 @@ pub mod error;
pub mod typedef;
mod util;

pub use error::{NiftiError, Result};
pub use object::{NiftiObject, InMemNiftiObject};
pub use extension::{Extender, Extension, ExtensionSequence};
pub use header::NiftiHeader;
pub use header::{NiftiHeader, NiftiHeaderBuilder};
pub use volume::{NiftiVolume, InMemNiftiVolume};
pub use typedef::{NiftiType, Unit, Intent, XForm, SliceOrder};
pub use util::Endianness;
68 changes: 46 additions & 22 deletions src/object.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Module for handling and retrieving complete NIFTI-1 objects.
use std::fs::File;
use std::path::Path;
use std::io::{BufReader, Read};
Expand All @@ -13,7 +15,8 @@ use byteorder::{BigEndian, LittleEndian};
use flate2::bufread::GzDecoder;

/// Trait type for all possible implementations of
/// owning NIFTI-1 objects.
/// owning NIFTI-1 objects. Objects contain a NIFTI header,
/// a volume, and a possibly empty extension sequence.
pub trait NiftiObject {

/// The concrete type of the volume.
Expand Down Expand Up @@ -68,37 +71,23 @@ impl InMemNiftiObject {

let file = BufReader::new(File::open(&path)?);
if gz {
Self::from_file_2(GzDecoder::new(file)?, file)
Self::from_file_2(path, GzDecoder::new(file)?)
} else {
Self::from_file_2(path, file)
}
}

/// Retrieve a NIFTI object as separate header and volume files.
/// This method is useful when file names are not conventional for a
/// NIFTI file pair.
pub fn from_file_pair<P, Q>(hdr_path: P, vol_path: Q) -> Result<InMemNiftiObject>
where P: AsRef<Path>,
Q: AsRef<Path>
{
let mut hdr_stream = BufReader::new(File::open(hdr_path)?);
let (header, endianness) = NiftiHeader::from_stream(&mut hdr_stream)?;
let extender = Extender::from_stream_optional(hdr_stream)?.unwrap_or_default();
let (volume, extensions) = InMemNiftiVolume::from_file_with_extensions(vol_path, &header, endianness, extender)?;

Ok(InMemNiftiObject {
header,
extensions,
volume,
})
}

fn from_file_2<P: AsRef<Path>, S>(path: P, mut stream: S) -> Result<InMemNiftiObject>
where S: Read
{
let (header, endianness) = NiftiHeader::from_stream(&mut stream)?;
let extender = Extender::from_stream_optional(&mut stream)?.unwrap_or_default();
let (volume, ext) = if &header.magic == MAGIC_CODE_NI1 {
// extensions and volume are in another file

// extender is optional
let extender = Extender::from_stream_optional(&mut stream)?
.unwrap_or_default();

// look for corresponding img file
let mut img_path = path.as_ref().to_path_buf();

Expand All @@ -111,6 +100,8 @@ impl InMemNiftiObject {
e
})?
} else {
// extensions and volume are in the same source

let extender = Extender::from_stream(&mut stream)?;
let len = header.vox_offset as usize;
let len = if len < 352 {
Expand All @@ -136,6 +127,39 @@ impl InMemNiftiObject {
})
}

/// Retrieve a NIFTI object as separate header and volume files.
/// This method is useful when file names are not conventional for a
/// NIFTI file pair.
pub fn from_file_pair<P, Q>(hdr_path: P, vol_path: Q) -> Result<InMemNiftiObject>
where P: AsRef<Path>,
Q: AsRef<Path>
{
let gz = is_gz_file(&hdr_path);

let file = BufReader::new(File::open(&hdr_path)?);
if gz {
Self::from_file_pair_2(GzDecoder::new(file)?, vol_path)
} else {
Self::from_file_pair_2(file, vol_path)
}
}

fn from_file_pair_2<S, Q>(mut hdr_stream: S, vol_path: Q) -> Result<InMemNiftiObject>
where S: Read,
Q: AsRef<Path>,
{
let (header, endianness) = NiftiHeader::from_stream(&mut hdr_stream)?;
let extender = Extender::from_stream_optional(hdr_stream)?.unwrap_or_default();
let (volume, extensions) = InMemNiftiVolume::from_file_with_extensions(vol_path, &header, endianness, extender)?;

Ok(InMemNiftiObject {
header,
extensions,
volume,
})
}


/// Retrieve a NIFTI object from a stream of data.
///
/// # Errors
Expand Down
9 changes: 9 additions & 0 deletions src/typedef.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
//! This module contains multiple types defined by the standard.
//! At the moment, not all of them are used internally (`NiftiType`
//! makes the exception, which also provides a safe means of
//! reading voxel values). However, primitive integer values can be
//! converted to these types and vice-versa.
use byteorder::ReadBytesExt;
use error::{Result, NiftiError};
use std::io::Read;
Expand Down Expand Up @@ -92,10 +98,12 @@ impl NiftiType {
Ok(raw_to_value(raw as f32, slope, inter))
},
NiftiType::Uint64 => {
// TODO find a way to not lose precision
let raw = endianness.read_u64(source)?;
Ok(raw_to_value(raw as f32, slope, inter))
},
NiftiType::Int64 => {
// TODO find a way to not lose precision
let raw = endianness.read_i64(source)?;
Ok(raw_to_value(raw as f32, slope, inter))
},
Expand All @@ -104,6 +112,7 @@ impl NiftiType {
Ok(raw_to_value(raw, slope, inter))
},
NiftiType::Float64 => {
// TODO find a way to not lose precision
let raw = endianness.read_f64(source)?;
Ok(raw_to_value(raw as f32, slope, inter))
},
Expand Down
Loading

0 comments on commit 1cf9e92

Please sign in to comment.