trk_io/
reader.rs

1use std::{fs::File, io::BufReader, path::Path};
2
3use anyhow::{Context, Result};
4use byteorder::{BigEndian, ByteOrder, LittleEndian, ReadBytesExt};
5use nalgebra::Vector3;
6
7use crate::{
8    cheader::Endianness,
9    tractogram::{Point, Points, Streamlines, Tractogram, TractogramItem},
10    Affine, ArraySequence, Header, Spacing, Translation, Writer,
11};
12
13pub struct Reader {
14    reader: BufReader<File>,
15    endianness: Endianness,
16    pub header: Header,
17
18    raw: bool,
19    voxel_space: Option<Spacing>,
20
21    floats_per_point: usize,
22    buffer: Vec<f32>,
23}
24
25impl Reader {
26    /// Create an object to read all points of a TrackVis file in world space.
27    ///
28    /// Will also read the scalars and properties, if requested.
29    pub fn new<P: AsRef<Path>>(path: P) -> Result<Reader> {
30        let f = File::open(path.as_ref())
31            .with_context(|| format!("Failed to load {:?}", path.as_ref()))?;
32        let mut reader = BufReader::new(f);
33        let (header, endianness) = Header::read(&mut reader)?;
34        let floats_per_point = 3 + header.scalars_name.len();
35        let buffer = Vec::with_capacity(300);
36
37        let raw = false;
38        let voxel_space = None;
39
40        Ok(Reader { reader, endianness, header, raw, voxel_space, floats_per_point, buffer })
41    }
42
43    /// Modify the affine in order to read all streamlines in voxel space.
44    ///
45    /// Once this function is called, it's not possible to revert to reading in world space.
46    ///
47    /// Panics if `raw` has been called.
48    pub fn to_voxel_space(mut self, spacing: Spacing) -> Self {
49        if self.raw {
50            panic!("Can't use raw + voxel space reading");
51        }
52
53        self.voxel_space = Some(spacing);
54        self.header.affine_to_rasmm =
55            Affine::from_diagonal(&Vector3::new(1.0 / spacing.x, 1.0 / spacing.y, 1.0 / spacing.z));
56        self.header.translation = Translation::zeros();
57        self
58    }
59
60    /// Read the points as they are written on disk, without any transformation.
61    ///
62    /// Panics if `to_voxel_space` has been called.
63    pub fn raw(mut self) -> Self {
64        if self.voxel_space.is_some() {
65            panic!("Can't use voxel space + raw reading");
66        }
67
68        self.raw = true;
69        self
70    }
71
72    /// Build a compatible `Writer` from the collected information in `self`.
73    pub fn build_writer<P: AsRef<Path>>(&self, path: P) -> Result<Writer> {
74        let mut w = Writer::new(path, Some(&self.header))?;
75        if let Some(spacing) = self.voxel_space {
76            w = w.from_voxel_space(spacing);
77        } else if self.raw {
78            w = w.raw();
79        }
80        Ok(w)
81    }
82
83    /// Iterate only on streamlines (`Vec<Point>`), ignoring scalars and properties.
84    pub fn into_streamlines_iter(self) -> StreamlinesIter {
85        StreamlinesIter { reader: self }
86    }
87
88    /// Read the complete tractogram, that is, all points, scalars and properties, if any.
89    pub fn tractogram(&mut self) -> Tractogram {
90        match self.endianness {
91            Endianness::Little => self.read_tractogram_::<LittleEndian>(),
92            Endianness::Big => self.read_tractogram_::<BigEndian>(),
93        }
94    }
95
96    /// Read all points, ignoring the scalars and properties.
97    pub fn streamlines(&mut self) -> Streamlines {
98        match self.endianness {
99            Endianness::Little => self.read_points_::<LittleEndian>(),
100            Endianness::Big => self.read_points_::<BigEndian>(),
101        }
102    }
103
104    fn read_tractogram_<E: ByteOrder>(&mut self) -> Tractogram {
105        // TODO Anything we can do to reserve?
106        let mut lengths = Vec::new();
107        let mut v = Vec::with_capacity(300);
108        let mut scalars = ArraySequence::with_capacity(300);
109        let mut properties = ArraySequence::with_capacity(300);
110        while let Ok(nb_points) = self.reader.read_i32::<E>() {
111            lengths.push(nb_points as usize);
112            self.read_streamline::<E>(&mut v, &mut scalars, nb_points as usize);
113            self.read_properties_to_arr::<E>(&mut properties);
114        }
115
116        self.buffer = vec![];
117        Tractogram::new(Streamlines::new(lengths, v), scalars, properties)
118    }
119
120    fn read_points_<E: ByteOrder>(&mut self) -> Streamlines {
121        // TODO Anything we can do to reserve?
122        let mut lengths = Vec::new();
123        let mut v = Vec::with_capacity(300);
124        while let Ok(nb_points) = self.reader.read_i32::<E>() {
125            lengths.push(nb_points as usize);
126            self.read_streamline_fast::<E>(&mut v, nb_points as usize);
127        }
128
129        self.buffer = vec![];
130        Streamlines::new(lengths, v)
131    }
132
133    fn read_streamline<E: ByteOrder>(
134        &mut self,
135        points: &mut Points,
136        scalars: &mut ArraySequence<f32>,
137        nb_points: usize,
138    ) {
139        self.read_floats::<E>(nb_points);
140        for floats in self.buffer.chunks(self.floats_per_point) {
141            self.add_points(points, floats);
142            for f in &floats[3..] {
143                scalars.push(*f);
144            }
145        }
146        scalars.end_push();
147    }
148
149    /// Ignore the scalars and properties.
150    fn read_streamline_fast<E: ByteOrder>(&mut self, points: &mut Points, nb_points: usize) {
151        self.read_floats::<E>(nb_points);
152        for floats in self.buffer.chunks(self.floats_per_point) {
153            self.add_points(points, floats);
154            // Scalars have been read in `floats`, but we do not save them
155        }
156
157        // Properties must be read to advance the cursor, but we do not save them
158        for _ in 0..self.header.properties_name.len() {
159            self.reader.read_f32::<E>().unwrap();
160        }
161    }
162
163    /// Read all points and scalars for the current streamline.
164    ///
165    /// Simply chunk the result by `nb_floats_per_point` to get the 3D point and the scalars.
166    fn read_floats<E: ByteOrder>(&mut self, nb_points: usize) {
167        // Vec::resize never decreases capacity, it can only increase it so there won't be any
168        // useless allocation.
169        let nb_floats = nb_points * self.floats_per_point;
170        self.buffer.resize(nb_floats, 0.0);
171        self.reader.read_f32_into::<E>(self.buffer.as_mut_slice()).unwrap();
172    }
173
174    #[inline(always)]
175    fn add_points(&self, points: &mut Points, floats: &[f32]) {
176        let mut p = Point::new(floats[0], floats[1], floats[2]);
177        if !self.raw {
178            p = (self.header.affine_to_rasmm * p) + self.header.translation;
179        }
180        points.push(p);
181    }
182
183    fn read_properties_to_arr<E: ByteOrder>(&mut self, properties: &mut ArraySequence<f32>) {
184        for _ in 0..self.header.properties_name.len() {
185            properties.push(self.reader.read_f32::<E>().unwrap());
186        }
187        properties.end_push();
188    }
189
190    fn read_properties_to_vec<E: ByteOrder>(&mut self, properties: &mut Vec<f32>) {
191        for _ in 0..self.header.properties_name.len() {
192            properties.push(self.reader.read_f32::<E>().unwrap());
193        }
194    }
195
196    fn read_nb_points(&mut self) -> Option<usize> {
197        match self.endianness {
198            Endianness::Little => self.reader.read_i32::<LittleEndian>(),
199            Endianness::Big => self.reader.read_i32::<BigEndian>(),
200        }
201        .ok()
202        .map(|nb_points| nb_points as usize)
203    }
204}
205
206impl Iterator for Reader {
207    type Item = TractogramItem;
208
209    fn next(&mut self) -> Option<TractogramItem> {
210        let nb_points = self.read_nb_points()?;
211        let mut streamline = Vec::with_capacity(nb_points);
212        let mut scalars = ArraySequence::with_capacity(nb_points * self.header.scalars_name.len());
213        let mut properties = Vec::with_capacity(self.header.properties_name.len());
214        match self.endianness {
215            Endianness::Little => {
216                self.read_streamline::<LittleEndian>(&mut streamline, &mut scalars, nb_points);
217                self.read_properties_to_vec::<LittleEndian>(&mut properties);
218            }
219            Endianness::Big => {
220                self.read_streamline::<BigEndian>(&mut streamline, &mut scalars, nb_points);
221                self.read_properties_to_vec::<BigEndian>(&mut properties);
222            }
223        };
224        Some((streamline, scalars, properties))
225    }
226}
227
228pub struct StreamlinesIter {
229    reader: Reader,
230}
231
232impl Iterator for StreamlinesIter {
233    type Item = Points;
234
235    fn next(&mut self) -> Option<Points> {
236        let nb_points = self.reader.read_nb_points()?;
237        let mut streamline = Vec::with_capacity(nb_points);
238        match self.reader.endianness {
239            Endianness::Little => {
240                self.reader.read_streamline_fast::<LittleEndian>(&mut streamline, nb_points);
241            }
242            Endianness::Big => {
243                self.reader.read_streamline_fast::<BigEndian>(&mut streamline, nb_points);
244            }
245        };
246        Some(streamline)
247    }
248}