trk_io/
writer.rs

1use std::{fs::File, io::BufWriter, path::Path};
2
3use anyhow::Result;
4use byteorder::WriteBytesExt;
5use nalgebra::Vector4;
6
7use crate::{
8    affine::get_affine_and_translation,
9    tractogram::{Point, RefTractogramItem, Tractogram, TractogramItem},
10    Affine, Affine4, CHeader, Header, Spacing, Translation, TrkEndianness,
11};
12
13macro_rules! write_streamline {
14    ($writer:ident, $streamline:expr, $scalars:expr, $properties:expr) => {
15        if $writer.nb_scalars == 0 {
16            $streamline.write($writer);
17        } else {
18            $writer.writer.write_i32::<TrkEndianness>($streamline.len() as i32).unwrap();
19            $writer.real_n_count += 1;
20
21            let scalars = $scalars.chunks($writer.nb_scalars);
22            for (p, scalars) in $streamline.into_iter().zip(scalars) {
23                $writer.write_point(&p);
24                $writer.write_f32s(scalars);
25            }
26        }
27
28        $writer.write_f32s($properties);
29    };
30    // Fast method, without scalars and properties
31    ($writer:ident, $streamline:expr, $nb_points:expr) => {
32        $writer.writer.write_i32::<TrkEndianness>($nb_points as i32).unwrap();
33        for p in $streamline {
34            $writer.write_point(&p);
35        }
36        $writer.real_n_count += 1;
37    };
38}
39
40pub struct Writer {
41    writer: BufWriter<File>,
42    pub affine4: Affine4,
43    affine: Affine,
44    translation: Translation,
45    nb_scalars: usize,
46
47    real_n_count: i32,
48    raw: bool,
49    voxel_space: bool,
50}
51
52pub trait Writable {
53    fn write(self, w: &mut Writer);
54}
55
56impl Writable for Tractogram {
57    fn write(self, w: &mut Writer) {
58        for item in &self {
59            item.write(w);
60        }
61    }
62}
63
64impl Writable for TractogramItem {
65    fn write(self, writer: &mut Writer) {
66        let (streamline, scalars, properties) = self;
67        write_streamline!(writer, streamline, scalars.data.as_slice(), &properties);
68    }
69}
70
71impl<'data> Writable for RefTractogramItem<'data> {
72    fn write(self, writer: &mut Writer) {
73        let (streamline, scalars, properties) = self;
74        write_streamline!(writer, streamline, scalars, properties);
75    }
76}
77
78impl<'data> Writable for &'data [Point] {
79    fn write(self, writer: &mut Writer) {
80        write_streamline!(writer, self, self.len());
81    }
82}
83
84impl Writer {
85    pub fn new<P: AsRef<Path>>(path: P, reference: Option<&Header>) -> Result<Writer> {
86        let f = File::create(path).expect("Can't create new trk file.");
87        let mut writer = BufWriter::new(f);
88
89        let (affine4, nb_scalars) = match reference {
90            Some(header) => {
91                header.write(&mut writer)?;
92                let affine4 = header
93                    .affine4_to_rasmm
94                    .try_inverse()
95                    .expect("Unable to inverse 4x4 affine matrix");
96                (affine4, header.scalars_name.len())
97            }
98            None => {
99                Header::default().write(&mut writer)?;
100                (Affine4::identity(), 0)
101            }
102        };
103        let (affine, translation) = get_affine_and_translation(&affine4);
104
105        Ok(Writer {
106            writer,
107            affine4,
108            affine,
109            translation,
110            real_n_count: 0,
111            nb_scalars,
112            raw: false,
113            voxel_space: false,
114        })
115    }
116
117    /// Modifies the affine in order to write all streamlines from voxel space to the right
118    /// coordinate space on disk.
119    ///
120    /// The resulting file will only valid if the streamlines were read using `to_voxel_space`.
121    ///
122    /// Once this function is called, it's not possible to revert to writing from world space.
123    ///
124    /// Panics if `raw` has been called.
125    pub fn from_voxel_space(mut self, spacing: Spacing) -> Self {
126        if self.raw {
127            panic!("Can't use raw + voxel space reading");
128        }
129
130        self.voxel_space = true;
131        self.affine = Affine::from_diagonal(&spacing);
132        self.affine4 = Affine4::from_diagonal(&Vector4::new(spacing.x, spacing.y, spacing.z, 1.0));
133        self.translation = Translation::zeros();
134        self
135    }
136
137    /// Write the points as they are read on disk, without any transformation.
138    ///
139    /// The resulting file will only valid if the streamlines were read using `raw`.
140    ///
141    /// Calling `reset_affine` or `apply_affine` is not forbidden, but it will have no effect.
142    ///
143    /// Panics if `from_voxel_space` has been called.
144    pub fn raw(mut self) -> Self {
145        if self.voxel_space {
146            panic!("Can't use voxel space + raw reading");
147        }
148
149        self.raw = true;
150        self
151    }
152
153    /// Resets the affine so that no transformation is applied to the points.
154    ///
155    /// The TrackVis header (on disk) will **not** be modified.
156    pub fn reset_affine(&mut self) {
157        self.affine4 = Affine4::identity();
158        self.affine = Affine::identity();
159        self.translation = Translation::zeros();
160    }
161
162    /// Applies a new affine over the current affine.
163    ///
164    /// The TrackVis header (on disk) will **not** be modified.
165    pub fn apply_affine(&mut self, affine: &Affine4) {
166        self.affine4 = self.affine4 * affine;
167        let (affine, translation) = get_affine_and_translation(&self.affine4);
168        self.affine = affine;
169        self.translation = translation;
170    }
171
172    pub fn write<T: Writable>(&mut self, data: T) {
173        data.write(self);
174    }
175
176    pub fn write_from_iter<I>(&mut self, streamline: I, len: usize)
177    where
178        I: IntoIterator<Item = Point>,
179    {
180        write_streamline!(self, streamline, len);
181    }
182
183    fn write_point(&mut self, p: &Point) {
184        let p = if self.raw { *p } else { self.affine * p + self.translation };
185        self.writer.write_f32::<TrkEndianness>(p.x).unwrap();
186        self.writer.write_f32::<TrkEndianness>(p.y).unwrap();
187        self.writer.write_f32::<TrkEndianness>(p.z).unwrap();
188    }
189
190    fn write_f32s(&mut self, data: &[f32]) {
191        for &d in data {
192            self.writer.write_f32::<TrkEndianness>(d).unwrap();
193        }
194    }
195}
196
197// Finally write `n_count`
198impl Drop for Writer {
199    fn drop(&mut self) {
200        CHeader::seek_n_count_field(&mut self.writer)
201            .expect("Unable to seek to 'n_count' field before closing trk file.");
202        self.writer
203            .write_i32::<TrkEndianness>(self.real_n_count)
204            .expect("Unable to write 'n_count' field before closing trk file.");
205    }
206}