nifti/
util.rs

1//! Private utility module
2use super::error::NiftiError;
3use super::typedef::NiftiType;
4use crate::error::Result;
5use crate::NiftiHeader;
6use byteordered::Endian;
7use either::Either;
8use flate2::bufread::GzDecoder;
9use std::borrow::Cow;
10use std::fs::File;
11use std::io::{BufReader, Result as IoResult};
12use std::mem;
13use std::path::{Path, PathBuf};
14
15pub fn convert_bytes_to<T, E>(mut a: Vec<u8>, e: E) -> Vec<T>
16where
17    T: bytemuck::Pod,
18    E: Endian,
19{
20    adapt_bytes_inline::<T, _>(&mut a, e);
21    match bytemuck::allocation::try_cast_vec(a) {
22        Ok(v) => v,
23        Err((_, v)) => bytemuck::allocation::pod_collect_to_vec(&v),
24    }
25}
26
27/// Adapt a sequence of bytes for reading contiguous values of type `T`,
28/// by swapping bytes if the given endianness is not native.
29pub fn adapt_bytes_inline<T, E>(a: &mut [u8], e: E)
30where
31    E: Endian,
32{
33    let nb_bytes = mem::size_of::<T>();
34    if !e.is_native() && nb_bytes > 1 {
35        // Swap endianness by block of nb_bytes
36        let split_at = nb_bytes / 2;
37        for c in a.chunks_mut(nb_bytes) {
38            let (a, b) = c.split_at_mut(split_at);
39            for (l, r) in a.iter_mut().zip(b.iter_mut().rev()) {
40                mem::swap(l, r);
41            }
42        }
43    }
44}
45
46/// Adapt a sequence of bytes for reading contiguous values of type `T`,
47/// by swapping bytes if the given endianness is not native. If no
48/// swapping is needed, the same byte slice is returned.
49#[cfg_attr(not(feature = "ndarray_volumes"), allow(dead_code))]
50pub fn adapt_bytes<T, E>(bytes: &[u8], e: E) -> Cow<[u8]>
51where
52    E: Endian,
53{
54    let nb_bytes = mem::size_of::<T>();
55    if !e.is_native() && nb_bytes > 1 {
56        // Swap endianness by block of nb_bytes
57        let mut a = bytes.to_vec();
58        adapt_bytes_inline::<T, E>(&mut a, e);
59        a.into()
60    } else {
61        bytes.into()
62    }
63}
64
65/// Validate a raw volume dimensions array, returning a slice of the concrete
66/// dimensions.
67///
68/// # Error
69///
70/// Errors if `dim[0]` is outside the accepted rank boundaries or
71/// one of the used dimensions is not positive.
72pub fn validate_dim(raw_dim: &[u16; 8]) -> Result<&[u16]> {
73    let ndim = validate_dimensionality(raw_dim)?;
74    let o = &raw_dim[1..=ndim];
75    if let Some(i) = o.iter().position(|&x| x == 0) {
76        return Err(NiftiError::InconsistentDim(i as u8, raw_dim[i]));
77    }
78    Ok(o)
79}
80
81/// Validate a raw N-dimensional index or shape, returning its rank.
82///
83/// # Error
84///
85/// Errors if `raw_dim[0]` is outside the accepted rank boundaries: 0 or
86/// larger than 7.
87pub fn validate_dimensionality(raw_dim: &[u16; 8]) -> Result<usize> {
88    if raw_dim[0] == 0 || raw_dim[0] > 7 {
89        return Err(NiftiError::InconsistentDim(0, raw_dim[0]));
90    }
91    Ok(usize::from(raw_dim[0]))
92}
93
94pub fn nb_bytes_for_data(header: &NiftiHeader) -> Result<usize> {
95    let resolution = nb_values_for_dims(header.dim()?);
96    resolution
97        .and_then(|r| r.checked_mul(header.bitpix as usize / 8))
98        .ok_or(NiftiError::BadVolumeSize)
99}
100
101pub fn nb_values_for_dims(dim: &[u16]) -> Option<usize> {
102    dim.iter()
103        .cloned()
104        .try_fold(1usize, |acc, v| acc.checked_mul(v as usize))
105}
106
107pub fn nb_bytes_for_dim_datatype(dim: &[u16], datatype: NiftiType) -> Option<usize> {
108    let resolution = nb_values_for_dims(dim);
109    resolution.and_then(|r| r.checked_mul(datatype.size_of()))
110}
111
112#[cfg(feature = "ndarray_volumes")]
113pub fn is_hdr_file<P>(path: P) -> bool
114where
115    P: AsRef<Path>,
116{
117    path.as_ref()
118        .file_name()
119        .map(|a| {
120            let s = a.to_string_lossy();
121            s.ends_with(".hdr") || s.ends_with(".hdr.gz")
122        })
123        .unwrap_or(false)
124}
125
126pub fn is_gz_file<P>(path: P) -> bool
127where
128    P: AsRef<Path>,
129{
130    path.as_ref()
131        .file_name()
132        .map(|a| a.to_string_lossy().ends_with(".gz"))
133        .unwrap_or(false)
134}
135
136/// Convert a file path to a header file (.hdr or .hdr.gz) to
137/// the respective volume file with GZip compression (.img.gz).
138///
139/// # Panics
140/// Can panic if the given file path is not a valid path to a header file.
141/// If it doesn't panic in this case, the result might still not be correct.
142pub fn into_img_file_gz(mut path: PathBuf) -> PathBuf {
143    if is_gz_file(&path) {
144        // Leave only the first extension (.hdr)
145        let _ = path.set_extension("");
146    }
147    path.with_extension("img.gz")
148}
149
150/// A reader for a GZip encoded file.
151pub type GzDecodedFile = GzDecoder<BufReader<File>>;
152
153/// A byte reader which might be GZip encoded based on some run-time condition.
154pub type MaybeGzDecoded<T> = Either<T, GzDecoder<T>>;
155
156/// A reader for a file which might be GZip encoded based on some run-time
157/// condition.
158pub type MaybeGzDecodedFile = MaybeGzDecoded<BufReader<File>>;
159
160/// Open a file for reading, which might be Gzip compressed based on whether
161/// the extension ends with ".gz".
162pub fn open_file_maybe_gz<P>(path: P) -> IoResult<MaybeGzDecodedFile>
163where
164    P: AsRef<Path>,
165{
166    let path = path.as_ref();
167    let file = BufReader::new(File::open(path)?);
168    if is_gz_file(path) {
169        Ok(Either::Right(GzDecoder::new(file)))
170    } else {
171        Ok(Either::Left(file))
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    #[cfg(feature = "ndarray_volumes")]
178    use super::is_hdr_file;
179    use super::{into_img_file_gz, is_gz_file, nb_bytes_for_dim_datatype};
180    use crate::typedef::NiftiType;
181    use std::path::PathBuf;
182
183    #[test]
184    fn test_nbytes() {
185        assert_eq!(
186            nb_bytes_for_dim_datatype(&[2, 3, 2], NiftiType::Uint8),
187            Some(12),
188        );
189        assert_eq!(
190            nb_bytes_for_dim_datatype(&[2, 3], NiftiType::Uint8),
191            Some(6),
192        );
193        assert_eq!(
194            nb_bytes_for_dim_datatype(&[2, 3], NiftiType::Uint16),
195            Some(12),
196        );
197        assert_eq!(
198            nb_bytes_for_dim_datatype(&[0x4000, 0x4000, 0x4000, 0x4000, 0x4000], NiftiType::Uint32),
199            None,
200        );
201    }
202
203    #[test]
204    fn filenames() {
205        assert!(!is_gz_file("/path/to/something.nii"));
206        assert!(is_gz_file("/path/to/something.nii.gz"));
207        assert!(!is_gz_file("volume.não"));
208        assert!(is_gz_file("1.2.3.nii.gz"));
209        assert!(!is_gz_file("não_é_gz.hdr"));
210
211        let path = "/path/to/image.hdr";
212        #[cfg(feature = "ndarray_volumes")]
213        assert!(is_hdr_file(path));
214        assert!(!is_gz_file(path));
215        assert_eq!(
216            into_img_file_gz(PathBuf::from(path)),
217            PathBuf::from("/path/to/image.img.gz")
218        );
219
220        let path = "/path/to/image.hdr.gz";
221        #[cfg(feature = "ndarray_volumes")]
222        assert!(is_hdr_file(path));
223        assert!(is_gz_file(path));
224        assert_eq!(
225            into_img_file_gz(PathBuf::from(path)),
226            PathBuf::from("/path/to/image.img.gz")
227        );
228
229        let path = "my_ct_scan.1.hdr.gz";
230        #[cfg(feature = "ndarray_volumes")]
231        assert!(is_hdr_file(path));
232        assert!(is_gz_file(path));
233        assert_eq!(
234            into_img_file_gz(PathBuf::from(path)),
235            PathBuf::from("my_ct_scan.1.img.gz")
236        );
237
238        assert_eq!(
239            into_img_file_gz(PathBuf::from("../you.cant.fool.me.hdr.gz")),
240            PathBuf::from("../you.cant.fool.me.img.gz")
241        );
242    }
243}
244
245#[cfg(feature = "ndarray_volumes")]
246#[cfg(test)]
247mod test_nd_array {
248    use super::convert_bytes_to;
249    use byteordered::Endianness;
250
251    #[test]
252    fn test_convert_vec_i8() {
253        assert_eq!(
254            convert_bytes_to::<i8, _>(vec![0x01, 0x11, 0xff], Endianness::Big),
255            vec![1, 17, -1]
256        );
257        assert_eq!(
258            convert_bytes_to::<i8, _>(vec![0x01, 0x11, 0xfe], Endianness::Little),
259            vec![1, 17, -2]
260        );
261    }
262
263    #[test]
264    fn test_convert_vec_u8() {
265        assert_eq!(
266            convert_bytes_to::<u8, _>(vec![0x01, 0x11, 0xff], Endianness::Big),
267            vec![1, 17, 255]
268        );
269        assert_eq!(
270            convert_bytes_to::<u8, _>(vec![0x01, 0x11, 0xfe], Endianness::Little),
271            vec![1, 17, 254]
272        );
273    }
274
275    #[test]
276    fn test_convert_vec_i16() {
277        assert_eq!(
278            convert_bytes_to::<i16, _>(vec![0x00, 0x01, 0x01, 0x00, 0xff, 0xfe], Endianness::Big),
279            vec![1, 256, -2]
280        );
281        assert_eq!(
282            convert_bytes_to::<i16, _>(
283                vec![0x01, 0x00, 0x00, 0x01, 0xfe, 0xff],
284                Endianness::Little
285            ),
286            vec![1, 256, -2]
287        );
288    }
289
290    #[test]
291    fn test_convert_vec_u16() {
292        assert_eq!(
293            convert_bytes_to::<u16, _>(vec![0x00, 0x01, 0x01, 0x00, 0xff, 0xfe], Endianness::Big),
294            vec![1, 256, 65534]
295        );
296        assert_eq!(
297            convert_bytes_to::<u16, _>(
298                vec![0x01, 0x00, 0x00, 0x01, 0xfe, 0xff],
299                Endianness::Little
300            ),
301            vec![1, 256, 65534]
302        );
303    }
304
305    #[test]
306    fn test_convert_vec_i32() {
307        assert_eq!(
308            convert_bytes_to::<i32, _>(
309                vec![0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0xf0, 0xf1, 0xf2, 0xf3],
310                Endianness::Big
311            ),
312            vec![1, 16777216, -252_579_085]
313        );
314        assert_eq!(
315            convert_bytes_to::<i32, _>(
316                vec![0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf3, 0xf2, 0xf1, 0xf0],
317                Endianness::Little
318            ),
319            vec![1, 16777216, -252_579_085]
320        );
321    }
322
323    #[test]
324    fn test_convert_vec_u32() {
325        assert_eq!(
326            convert_bytes_to::<u32, _>(
327                vec![0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0xf0, 0xf1, 0xf2, 0xf3],
328                Endianness::Big
329            ),
330            vec![1, 0x01000000, 4_042_388_211]
331        );
332        assert_eq!(
333            convert_bytes_to::<u32, _>(
334                vec![0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf3, 0xf2, 0xf1, 0xf0],
335                Endianness::Little
336            ),
337            vec![1, 0x01000000, 4_042_388_211]
338        );
339    }
340
341    #[test]
342    fn test_convert_vec_i64() {
343        assert_eq!(
344            convert_bytes_to::<i64, _>(
345                vec![
346                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
347                    0x00, 0x00, 0x00, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
348                ],
349                Endianness::Big
350            ),
351            vec![1, 0x0100000000000000, -1_084_818_905_618_843_913]
352        );
353        assert_eq!(
354            convert_bytes_to::<i64, _>(
355                vec![
356                    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
357                    0x00, 0x00, 0x01, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0,
358                ],
359                Endianness::Little
360            ),
361            vec![1, 0x0100000000000000, -1_084_818_905_618_843_913]
362        );
363    }
364
365    #[test]
366    fn test_convert_vec_u64() {
367        assert_eq!(
368            convert_bytes_to::<u64, _>(
369                vec![
370                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
371                    0x00, 0x00, 0x00, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
372                ],
373                Endianness::Big
374            ),
375            vec![1, 0x100000000000000, 0xf0f1f2f3f4f5f6f7]
376        );
377        assert_eq!(
378            convert_bytes_to::<u64, _>(
379                vec![
380                    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
381                    0x00, 0x00, 0x01, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0,
382                ],
383                Endianness::Little
384            ),
385            vec![1, 0x100000000000000, 0xf0f1f2f3f4f5f6f7]
386        );
387    }
388
389    #[test]
390    fn test_convert_vec_f32() {
391        let v: Vec<f32> = convert_bytes_to(
392            vec![0x42, 0x28, 0x00, 0x00, 0x42, 0x2A, 0x00, 0x00],
393            Endianness::Big,
394        );
395        assert_eq!(v, vec![42., 42.5]);
396
397        let v: Vec<f32> = convert_bytes_to(
398            vec![0x00, 0x00, 0x28, 0x42, 0x00, 0x00, 0x2A, 0x42],
399            Endianness::Little,
400        );
401        assert_eq!(v, vec![42., 42.5]);
402    }
403
404    #[test]
405    fn test_convert_vec_f64() {
406        let v: Vec<f64> = convert_bytes_to(
407            vec![
408                0x40, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x45, 0x40, 0x00, 0x00, 0x00,
409                0x00, 0x00,
410            ],
411            Endianness::Big,
412        );
413        assert_eq!(v, vec![42.0, 42.5]);
414
415        let v: Vec<f64> = convert_bytes_to(
416            vec![
417                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
418                0x45, 0x40,
419            ],
420            Endianness::Little,
421        );
422        assert_eq!(v, vec![42.0, 42.5]);
423    }
424}