neuroformats/
fs_label.rs

1//! Functions for reading FreeSurfer label files.
2//!
3//! A label groups a number of vertices (for surface label) or voxels (for volume labels) together. E.g., all
4//! vertices which are part of a certain brain region can be stored in a label. Note though that nothing requires that the
5//! vertices of a label form a spatially adjacent patch. Each vertex or voxel that is part of the label can be assigned a scalar value.
6
7
8use std::fs::File;
9use std::io::{BufRead, BufReader, Write, LineWriter};
10use std::path::{Path};
11use std::fmt;
12
13
14use crate::error::{NeuroformatsError, Result};
15use crate::util::vec32minmax;
16
17#[derive(Debug, Clone, PartialEq)]
18pub struct FsLabel {
19    pub vertexes: Vec<FsLabelVertex>,
20}
21
22impl FsLabel {
23
24    /// Determine whether this is a binary label. 
25    ///
26    /// A binary label assigns the same value (typically `0.0`) to all its vertices.
27    /// Such a label is typically used to define a region of some sort, e.g., a single brain region extracted from a brain
28    /// surface parcellation (see FsAnnot). Whether or not the label is intended as a binary inside/outside region definition
29    /// cannot be known, so treat the return value as an educated guess.
30    ///
31    /// # Panics
32    ///
33    /// * If the label is empty, i.e., contains no values.
34    pub fn is_binary(&self) -> bool {
35        let mut values_iter = self.vertexes.iter().map(|x| x.value);
36        let first_val = values_iter.next().expect("Empty label");
37        values_iter.all(|val| val == first_val)
38    }
39
40
41    /// Determine for each vertex whether it is part of this label.
42    ///
43    /// This is a simple convenience function. Note that you need to supply the total number of vertices of
44    /// the respective surface, as that number is not stored in the label.
45    ///
46    /// # Panics
47    ///
48    /// * If `num_surface_verts` is smaller than the max index stored in the label. If this happens, the label cannot belong to the respective surface.
49    pub fn is_surface_vertex_in_label(&self, num_surface_verts: usize) -> Vec<bool> {
50        if num_surface_verts < self.vertexes.len() {
51            // In this case, num_surface_verts is definitely wrong, but we do not check the max index here, which means stuff can still go wrong below.
52            panic!("Invalid vertex count 'num_surface_verts' for surface: label contains {} vertices, surface cannot contain only {}.", self.vertexes.len(), num_surface_verts);
53        }
54        let mut data_bin = vec![false; num_surface_verts];
55        for label_vert in self.vertexes.iter() {
56            data_bin[label_vert.index as usize] = true;
57        }
58        data_bin
59    }
60
61
62    /// Generate data for the whole surface from this label.
63    ///
64    /// This is a simple convenience function that creates a data vector with the specified length and fills it with the label
65    /// value for vertices which are part of this label and sets the rest to the `not_in_label_value` (typically `f32::NAN`).
66    ///
67    /// # Panics
68    ///
69    /// * If `num_surface_verts` is smaller than the max index stored in the label. If this happens, the label cannot belong to the respective surface.
70    pub fn as_surface_data(&self, num_surface_verts : usize, not_in_label_value : f32) -> Vec<f32> {
71        let mut surface_data : Vec<f32> = vec![not_in_label_value; num_surface_verts];
72        for surface_vert in self.vertexes.iter() {
73            surface_data[surface_vert.index as usize] = surface_vert.value;
74        }
75        surface_data
76    }
77}
78
79impl fmt::Display for FsLabel {    
80    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {        
81        let (min, max) = vec32minmax(self.vertexes.iter().map(|v| v.value), false);
82        write!(f, "Label for {} vertices/voxels, with label values in range {} to {}.", self.vertexes.len(), min, max)
83    }
84}
85
86#[derive(Debug, Clone, PartialEq)]
87pub struct FsLabelVertex {
88    pub index: i32,
89    pub coord1: f32,
90    pub coord2: f32,
91    pub coord3: f32,
92    pub value: f32,
93}
94
95// Read a FsLabelVertex from a line.
96impl std::str::FromStr for FsLabelVertex {
97    type Err = NeuroformatsError;
98    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
99        let mut iter = s.split_whitespace();
100        let index = iter.next().unwrap().parse::<i32>().expect("Expected vertex index of type i32.");
101        let coord1 = iter.next().unwrap().parse::<f32>().expect("Expected coord1 of type f32.");
102        let coord2 = iter.next().unwrap().parse::<f32>().expect("Expected coord2 of type f32.");
103        let coord3 = iter.next().unwrap().parse::<f32>().expect("Expected coord3 of type f32.");
104        let value = iter.next().unwrap().parse::<f32>().expect("Expected vertex value of type f32.");
105        Ok(FsLabelVertex{ index, coord1, coord2, coord3, value })
106    }
107}
108
109/// Read a surface label or volume label from a file in FreeSurfer label format.
110///
111/// A label groups a number of vertices (for surface label) or voxels (for volume labels) together. It can
112/// also assign a scalar value to each element.
113///
114/// # Examples
115///
116/// ```no_run
117/// let label = neuroformats::read_label("/path/to/subjects_dir/subject1/label/lh.entorhinal_exvivo.label").unwrap();
118/// let first = &label.vertexes[0];
119/// println!("Vertex #{} has coordinates {} {} {} and is assigned value {}.", first.index, first.coord1, first.coord2, first.coord3, first.value);
120/// ```
121pub fn read_label<P: AsRef<Path>>(path: P) -> Result<FsLabel> {
122
123    let reader = BufReader::new(File::open(path)?);
124
125    // Read the file line by line using the lines() iterator from std::io::BufRead.
126    let mut lines = reader.lines();
127    // We ignore the first line at index 0: it is a comment line.
128    let _comment_line = lines.next().transpose()?;
129    // The line 1 (after comment) is the header
130    let hdr_num_entries: i32 = lines.next().transpose()?.and_then(|header| header.parse::<i32>().ok()).expect("Could not parse label header line.");
131    let mut vertexes = Vec::with_capacity(hdr_num_entries as usize);
132    for line in lines {
133        let line = line?;
134        let vertex = line.parse()?;
135        vertexes.push(vertex);
136    }
137
138    if hdr_num_entries as usize != vertexes.len() {
139        Err(NeuroformatsError::InvalidFsLabelFormat)
140    } else {
141        Ok(FsLabel{ vertexes })
142    }
143}
144
145
146/// Write an FsLabel struct to a new file.
147pub fn write_label<P: AsRef<Path> + Copy>(path: P, label : &FsLabel) -> std::io::Result<()> {
148    let file = File::create(path)?;
149    let mut file = LineWriter::new(file);
150
151    let header_lines = format!("# FreeSurfer label.\n{}\n", label.vertexes.len());
152    let header_lines = header_lines.as_bytes();
153    file.write_all(header_lines)?;
154
155    for vertex in label.vertexes.iter() {
156        let vline = format!("{} {} {} {} {}\n", vertex.index, vertex.coord1, vertex.coord2, vertex.coord3, vertex.value);
157        let vline = vline.as_bytes();
158        file.write_all(vline)?;
159    }
160
161    file.flush()?;
162
163    Ok(())
164}
165
166
167#[cfg(test)]
168mod test { 
169    use super::*;
170    use tempfile::{tempdir};
171
172    #[test]
173    fn the_demo_surface_label_file_can_be_read() {
174        const LABEL_FILE: &str = "resources/subjects_dir/subject1/label/lh.entorhinal_exvivo.label";
175        let label = read_label(LABEL_FILE).unwrap();
176
177        let expected_vertex_count: usize = 1085;
178        assert_eq!(expected_vertex_count, label.vertexes.len());
179    }
180
181    #[test]
182    fn the_label_utility_functions_work() {
183        const LABEL_FILE: &str = "resources/subjects_dir/subject1/label/lh.entorhinal_exvivo.label";
184        let label = read_label(LABEL_FILE).unwrap();
185
186        let num_surface_verts: usize = 160_000;
187        let label_mask = label.is_surface_vertex_in_label(num_surface_verts);
188        assert_eq!(num_surface_verts, label_mask.len());
189
190        let surface_data = label.as_surface_data(num_surface_verts, f32::NAN);
191        assert_eq!(num_surface_verts, surface_data.len());
192
193        assert_eq!(false, label.is_binary());
194    }
195
196    #[test]
197    fn a_label_file_can_be_written_and_reread() {
198        const LABEL_FILE: &str = "resources/subjects_dir/subject1/label/lh.entorhinal_exvivo.label";
199        let label = read_label(LABEL_FILE).unwrap();
200
201        let dir = tempdir().unwrap();
202
203        let tfile_path = dir.path().join("temp-file.label");
204        let tfile_path = tfile_path.to_str().unwrap();
205        write_label(tfile_path, &label).unwrap();
206
207        let label_re = read_label(tfile_path).unwrap();
208        let expected_vertex_count: usize = 1085;
209        assert_eq!(expected_vertex_count, label_re.vertexes.len());
210    }
211
212}