1pub mod tck;
2pub mod trk;
3pub mod tt;
4pub mod vtk;
5
6use std::path::Path;
7
8use crate::any_trx_file::AnyTrxFile;
9use crate::dtype::DType;
10use crate::error::{Result, TrxError};
11use crate::header::Header;
12use crate::tractogram::Tractogram;
13pub use vtk::{
14 inspect_vtk_declared_space, vtk_import_warnings, VtkCoordinateMode, VtkCoordinateSpace,
15};
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub enum Format {
20 Trx,
22 Trk,
24 Tck,
26 Vtk,
28 TinyTrack,
30}
31
32#[derive(Clone, Debug)]
34pub struct ConversionOptions {
35 pub header: Option<Header>,
37 pub trx_positions_dtype: DType,
39 pub vtk_coordinate_mode: VtkCoordinateMode,
41}
42
43impl Default for ConversionOptions {
44 fn default() -> Self {
45 Self {
46 header: None,
47 trx_positions_dtype: DType::Float32,
48 vtk_coordinate_mode: VtkCoordinateMode::AssumeRas,
49 }
50 }
51}
52
53pub fn detect_format(path: &Path) -> Result<Format> {
57 let file_name = path
58 .file_name()
59 .and_then(|name| name.to_str())
60 .ok_or_else(|| {
61 TrxError::Argument(format!("cannot determine format for {}", path.display()))
62 })?;
63
64 if file_name.ends_with(".trx") || path.is_dir() {
65 return Ok(Format::Trx);
66 }
67 if file_name.ends_with(".trk") || file_name.ends_with(".trk.gz") {
68 return Ok(Format::Trk);
69 }
70 if file_name.ends_with(".tck") || file_name.ends_with(".tck.gz") {
71 return Ok(Format::Tck);
72 }
73 if file_name.ends_with(".vtk") {
74 return Ok(Format::Vtk);
75 }
76 if file_name.ends_with(".tt") || file_name.ends_with(".tt.gz") {
77 return Ok(Format::TinyTrack);
78 }
79
80 Err(TrxError::Format(format!(
81 "unsupported tractogram format for {}",
82 path.display()
83 )))
84}
85
86pub fn read_tractogram(path: &Path, options: &ConversionOptions) -> Result<Tractogram> {
90 match detect_format(path)? {
91 Format::Trx => Ok(Tractogram::from(&AnyTrxFile::load(path)?)),
92 Format::Trk => trk::read_trk(path, options.header.clone()),
93 Format::Tck => tck::read_tck(path, options.header.clone()),
94 Format::Vtk => vtk::read_vtk(path, options.header.clone(), options.vtk_coordinate_mode),
95 Format::TinyTrack => tt::read_tt(path),
96 }
97}
98
99pub fn write_tractogram(
103 path: &Path,
104 tractogram: &Tractogram,
105 options: &ConversionOptions,
106) -> Result<()> {
107 match detect_format(path)? {
108 Format::Trx => {
109 let mut tractogram = tractogram.clone();
110 if let Some(header) = &options.header {
111 tractogram.set_spatial_metadata(header.voxel_to_rasmm, header.dimensions);
112 }
113 match tractogram.to_trx(options.trx_positions_dtype)? {
114 AnyTrxFile::F16(file) => file.save(path),
115 AnyTrxFile::F32(file) => file.save(path),
116 AnyTrxFile::F64(file) => file.save(path),
117 }
118 }
119 Format::Trk => {
120 let mut tractogram = tractogram.clone();
121 if let Some(header) = &options.header {
122 tractogram.set_spatial_metadata(header.voxel_to_rasmm, header.dimensions);
123 }
124 crate::legacy_io::write_trk(path, &tractogram, None).map_err(|err| {
125 TrxError::Format(format!(
126 "failed to write TrackVis file {}: {err}",
127 path.display()
128 ))
129 })
130 }
131 Format::Tck => tck::write_tck(path, tractogram),
132 Format::Vtk => vtk::write_vtk(path, tractogram),
133 Format::TinyTrack => Err(TrxError::Format(
134 "Tiny Track (.tt/.tt.gz) conversion is not implemented yet".into(),
135 )),
136 }
137}
138
139pub fn convert(input: &Path, output: &Path, options: &ConversionOptions) -> Result<()> {
143 if detect_format(input)? == Format::Trk && detect_format(output)? == Format::Trx {
144 return trk::convert_trk_to_trx(input, output, options);
145 }
146 let tractogram = read_tractogram(input, options)?;
147 write_tractogram(output, &tractogram, options)
148}
149
150#[cfg(test)]
151mod tests {
152 use super::{ConversionOptions, VtkCoordinateMode};
153
154 #[test]
155 fn conversion_options_default_to_ras_for_vtk_parity() {
156 assert_eq!(
157 ConversionOptions::default().vtk_coordinate_mode,
158 VtkCoordinateMode::AssumeRas
159 );
160 }
161}