Skip to content

Commit

Permalink
some magic right there
Browse files Browse the repository at this point in the history
  • Loading branch information
twitzelbos committed Jun 17, 2023
1 parent ad8cb39 commit c1cac1f
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 16 deletions.
153 changes: 142 additions & 11 deletions src/volume/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use safe_transmute::transmute_vec;
use std::io::Read;
use std::mem::align_of;
use std::ops::{Add, Mul};
use safe_transmute::TriviallyTransmutable;

/// Interface for linear (affine) transformations to values. Multiple
/// implementations are needed because the original type `T` may not have
Expand Down Expand Up @@ -176,15 +177,15 @@ impl NiftiDataRescaler<Complex64> for Complex64 {
}

// Nifti 1.1 specifies that RGB data must NOT be rescaled
impl NiftiDataRescaler<RGB8> for RGB8 {
fn nifti_rescale(value: RGB8, slope: f32, intercept: f32) -> RGB8 {
impl NiftiDataRescaler<NiftiRGB> for NiftiRGB {
fn nifti_rescale(value: NiftiRGB, _slope: f32, _intercept: f32) -> NiftiRGB {
return value;
}
}

// Nifti 1.1 specifies that RGB data must NOT be rescaled
impl NiftiDataRescaler<RGBA8> for RGBA8 {
fn nifti_rescale(value: RGBA8, slope: f32, intercept: f32) -> RGBA8 {
// Nifti 1.1 specifies that RGB(A) data must NOT be rescaled
impl NiftiDataRescaler<NiftiRGBA> for NiftiRGBA {
fn nifti_rescale(value: NiftiRGBA, _slope: f32, _intercept: f32) -> NiftiRGBA {
return value;
}
}
Expand Down Expand Up @@ -950,7 +951,35 @@ impl DataElement for Complex64 {
fn_from_real_scalar!(f64);
}

impl DataElement for RGB8 {
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct NiftiRGB {
r: u8,
g: u8,
b: u8,
}

impl NiftiRGB {
pub fn new(r: u8, g: u8, b: u8) -> Self {
NiftiRGB { r, g, b }
}
}

impl Into<RGB8> for NiftiRGB {
fn into(self) -> RGB8 {
RGB8::new(self.r, self.g, self.b)
}
}

impl From<RGB8> for NiftiRGB {
fn from(rgb: RGB8) -> Self {
NiftiRGB::new(rgb.r, rgb.g, rgb.b)
}
}

unsafe impl TriviallyTransmutable for NiftiRGB {}

impl DataElement for NiftiRGB {
const DATA_TYPE: NiftiType = NiftiType::Rgb24;
type Transform = NoTransform;

Expand All @@ -960,7 +989,7 @@ impl DataElement for RGB8 {
{
Ok(convert_bytes_to::<[u8; 3], _>(vec, e)
.into_iter()
.map(|x| RGB8::new(x[0], x[1], x[2]))
.map(|x| NiftiRGB::new(x[0], x[1], x[2]))
.collect())
}

Expand Down Expand Up @@ -988,11 +1017,73 @@ impl DataElement for RGB8 {
let g = ByteOrdered::native(&mut src).read_u8()?;
let b = ByteOrdered::native(&mut src).read_u8()?;

Ok(RGB8::new(r, g, b))
Ok(NiftiRGB::new(r, g, b))
}
}

impl DataElement for [u8; 3] {
const DATA_TYPE: NiftiType = NiftiType::Rgb24;
type Transform = NoTransform;

fn from_raw_vec<E>(vec: Vec<u8>, e: E) -> Result<Vec<Self>>
where
E: Endian,
{
Ok(convert_bytes_to::<[u8; 3], _>(vec, e))
}

fn from_raw_vec_validated<E>(
vec: Vec<u8>,
endianness: E,
datatype: NiftiType,
) -> Result<Vec<Self>>
where
E: Endian,
{
if datatype == NiftiType::Rgb24 {
Self::from_raw_vec(vec, endianness)
} else {
Err(NiftiError::InvalidTypeConversion(datatype, "[u8; 3]"))
}
}

fn from_raw<R, E>(mut src: R, _: E) -> Result<Self>
where
R: Read,
E: Endian,
{
let r = ByteOrdered::native(&mut src).read_u8()?;
let g = ByteOrdered::native(&mut src).read_u8()?;
let b = ByteOrdered::native(&mut src).read_u8()?;

Ok([r, g, b])
}
}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
struct NiftiRGBA {
r: u8,
g: u8,
b: u8,
a: u8,
}

impl NiftiRGBA {
fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
NiftiRGBA { r, g, b, a }
}
}

impl Into<RGBA8> for NiftiRGBA {
fn into(self) -> RGBA8 {
RGBA8::new(self.r, self.g, self.b, self.a)
}
}

impl DataElement for RGBA8 {
unsafe impl TriviallyTransmutable for NiftiRGBA {}

impl DataElement for NiftiRGBA {
const DATA_TYPE: NiftiType = NiftiType::Rgba32;
type Transform = NoTransform;

Expand All @@ -1002,7 +1093,7 @@ impl DataElement for RGBA8 {
{
Ok(convert_bytes_to::<[u8; 4], _>(vec, e)
.into_iter()
.map(|x| RGBA8::new(x[0], x[1], x[2], x[3]))
.map(|x| NiftiRGBA::new(x[0], x[1], x[2], x[3]))
.collect())
}

Expand Down Expand Up @@ -1031,6 +1122,46 @@ impl DataElement for RGBA8 {
let b = ByteOrdered::native(&mut src).read_u8()?;
let a = ByteOrdered::native(&mut src).read_u8()?;

Ok(RGBA8::new(r, g, b, a))
Ok(NiftiRGBA::new(r, g, b, a))
}
}

impl DataElement for [u8; 4] {
const DATA_TYPE: NiftiType = NiftiType::Rgba32;
type Transform = NoTransform;

fn from_raw_vec<E>(vec: Vec<u8>, e: E) -> Result<Vec<Self>>
where
E: Endian,
{
Ok(convert_bytes_to::<[u8; 4], _>(vec, e))
}

fn from_raw_vec_validated<E>(
vec: Vec<u8>,
endianness: E,
datatype: NiftiType,
) -> Result<Vec<Self>>
where
E: Endian,
{
if datatype == NiftiType::Rgba32 {
Self::from_raw_vec(vec, endianness)
} else {
Err(NiftiError::InvalidTypeConversion(datatype, "[u8; 4]"))
}
}

fn from_raw<R, E>(mut src: R, _: E) -> Result<Self>
where
R: Read,
E: Endian,
{
let r = ByteOrdered::native(&mut src).read_u8()?;
let g = ByteOrdered::native(&mut src).read_u8()?;
let b = ByteOrdered::native(&mut src).read_u8()?;
let a = ByteOrdered::native(&mut src).read_u8()?;

Ok([r, g, b, a])
}
}
25 changes: 21 additions & 4 deletions src/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::{Path, PathBuf};
use std::mem::size_of;

use byteordered::{ByteOrdered, Endian};
use flate2::write::GzEncoder;
Expand Down Expand Up @@ -137,15 +138,20 @@ impl<'a> WriterOptions<'a> {
self
}

/// Write a nifti file (.nii or .nii.gz).
pub fn write_nifti<A, S, D>(&self, data: &ArrayBase<S, D>) -> Result<()>
/// Write a nifti file (.nii or .nii.gz) from an NdArray of any TriviallyTransmutable type
pub fn write_nifti_tt<A, S, D>(&self, data: &ArrayBase<S, D>, datatype: NiftiType) -> Result<()>
where
S: Data<Elem = A>,
A: DataElement,
A: TriviallyTransmutable,
D: Dimension + RemoveAxis,
{
let header = self.prepare_header(data, A::DATA_TYPE)?;

// do a basic size check
if size_of::<A>() != datatype.size_of() {
return Err(crate::error::NiftiError::UnsupportedDataType(datatype));
}

let header = self.prepare_header(data, datatype)?;
let (header_path, data_path) = self.output_paths();

// Need the transpose for fortran ordering used in nifti file format.
Expand Down Expand Up @@ -200,6 +206,17 @@ impl<'a> WriterOptions<'a> {
Ok(())
}

/// Write a nifti file (.nii or .nii.gz) from an NdArray of DataElements
pub fn write_nifti<A, S, D>(&self, data: &ArrayBase<S, D>) -> Result<()>
where
S: Data<Elem = A>,
A: DataElement,
A: TriviallyTransmutable,
D: Dimension + RemoveAxis,
{
self.write_nifti_tt(data, A::DATA_TYPE)
}

/// Write a RGB nifti file (.nii or .nii.gz).
pub fn write_rgb_nifti<S, D>(&self, data: &ArrayBase<S, D>) -> Result<()>
where
Expand Down
53 changes: 52 additions & 1 deletion tests/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ mod tests {
object::NiftiObject,
volume::shape::Dim,
writer::WriterOptions,
DataElement, IntoNdArray, NiftiHeader, NiftiType, ReaderOptions,
DataElement, IntoNdArray, NiftiHeader, NiftiType, ReaderOptions, volume::element::NiftiRGB,
};

use super::util::rgb_header_gt;
Expand Down Expand Up @@ -408,6 +408,57 @@ mod tests {
);
}

#[test]
fn write_4d_rgb_direct() {
let mut data = Array::from_elem((3, 3, 3, 2), [0u8, 0u8, 0u8]);
data[(0, 0, 0, 0)] = [55, 55, 0];
data[(0, 0, 1, 0)] = [55, 0, 55];
data[(0, 1, 0, 0)] = [0, 55, 55];
data[(0, 0, 0, 1)] = [55, 55, 0];
data[(0, 1, 0, 1)] = [55, 0, 55];
data[(1, 0, 0, 1)] = [0, 55, 55];

let path = get_temporary_path("rgb.nii");
let header = rgb_header_gt();
WriterOptions::new(&path)
.reference_header(&header)
.write_nifti_tt(&data, NiftiType::Rgb24)
.unwrap();

// Until we are able to read RGB images, we simply compare the bytes of the newly created
// image to the bytes of the prepared 4D RGB image in ressources/rgb/.
assert_eq!(
fs::read(path).unwrap(),
fs::read("resources/rgb/4D.nii").unwrap()
);
}

#[test]
fn write_4d_rgb2() {
let mut data = Array::from_elem((3, 3, 3, 2), NiftiRGB::new(0u8, 0u8, 0u8));

data[(0, 0, 0, 0)] = NiftiRGB::new(55, 55, 0);
data[(0, 0, 1, 0)] = NiftiRGB::new(55, 0, 55);
data[(0, 1, 0, 0)] = NiftiRGB::new(0, 55, 55);
data[(0, 0, 0, 1)] = NiftiRGB::new(55, 55, 0);
data[(0, 1, 0, 1)] = NiftiRGB::new(55, 0, 55);
data[(1, 0, 0, 1)] = NiftiRGB::new(0, 55, 55);

let path = get_temporary_path("rgb.nii");
let header = rgb_header_gt();
WriterOptions::new(&path)
.reference_header(&header)
.write_nifti(&data)
.unwrap();

// Until we are able to read RGB images, we simply compare the bytes of the newly created
// image to the bytes of the prepared 4D RGB image in ressources/rgb/.
assert_eq!(
fs::read(path).unwrap(),
fs::read("resources/rgb/4D.nii").unwrap()
);
}

#[test]
fn write_extended_header() {
let data: Array2<f64> = Array2::zeros((8, 8));
Expand Down

0 comments on commit c1cac1f

Please sign in to comment.