Skip to main content

trx_rs/
header.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::path::Path;
4
5use crate::error::Result;
6
7/// TRX file header (stored as JSON in `header.json`).
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9pub struct Header {
10    /// 4x4 affine matrix mapping voxel coordinates to RAS+mm space.
11    /// Stored row-major as `[[f64; 4]; 4]`.
12    #[serde(rename = "VOXEL_TO_RASMM")]
13    pub voxel_to_rasmm: [[f64; 4]; 4],
14
15    /// Volume dimensions `[x, y, z]`.
16    #[serde(rename = "DIMENSIONS")]
17    pub dimensions: [u64; 3],
18
19    /// Total number of streamlines.
20    #[serde(rename = "NB_STREAMLINES")]
21    pub nb_streamlines: u64,
22
23    /// Total number of vertices (points) across all streamlines.
24    #[serde(rename = "NB_VERTICES")]
25    pub nb_vertices: u64,
26
27    /// Any extra fields not covered above.
28    #[serde(flatten)]
29    pub extra: HashMap<String, serde_json::Value>,
30}
31
32impl Header {
33    /// Read header from a `header.json` file.
34    pub fn from_file(path: &Path) -> Result<Self> {
35        let data = std::fs::read_to_string(path)?;
36        let header: Header = serde_json::from_str(&data)?;
37        Ok(header)
38    }
39
40    /// Serialize header to JSON string.
41    pub fn to_json(&self) -> Result<String> {
42        Ok(serde_json::to_string_pretty(self)?)
43    }
44
45    /// Write header to a file.
46    pub fn write_to(&self, path: &Path) -> Result<()> {
47        let json = self.to_json()?;
48        std::fs::write(path, json)?;
49        Ok(())
50    }
51
52    /// Identity affine (no transform).
53    pub fn identity_affine() -> [[f64; 4]; 4] {
54        [
55            [1.0, 0.0, 0.0, 0.0],
56            [0.0, 1.0, 0.0, 0.0],
57            [0.0, 0.0, 1.0, 0.0],
58            [0.0, 0.0, 0.0, 1.0],
59        ]
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn header_serde_round_trip() {
69        let header = Header {
70            voxel_to_rasmm: Header::identity_affine(),
71            dimensions: [256, 256, 256],
72            nb_streamlines: 100,
73            nb_vertices: 5000,
74            extra: HashMap::new(),
75        };
76
77        let json = header.to_json().unwrap();
78        let parsed: Header = serde_json::from_str(&json).unwrap();
79
80        assert_eq!(parsed.nb_streamlines, 100);
81        assert_eq!(parsed.nb_vertices, 5000);
82        assert_eq!(parsed.dimensions, [256, 256, 256]);
83        assert_eq!(parsed.voxel_to_rasmm[0][0], 1.0);
84        assert_eq!(parsed.voxel_to_rasmm[3][3], 1.0);
85    }
86
87    #[test]
88    fn header_with_extra_fields() {
89        let json = r#"{
90            "VOXEL_TO_RASMM": [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]],
91            "DIMENSIONS": [100, 100, 100],
92            "NB_STREAMLINES": 42,
93            "NB_VERTICES": 420,
94            "CUSTOM_FIELD": "hello"
95        }"#;
96
97        let header: Header = serde_json::from_str(json).unwrap();
98        assert_eq!(header.nb_streamlines, 42);
99        assert_eq!(
100            header.extra.get("CUSTOM_FIELD").unwrap(),
101            &serde_json::Value::String("hello".into())
102        );
103    }
104}