1use half::f16;
2use std::path::Path;
3
4use crate::dtype::DType;
5use crate::error::{Result, TrxError};
6use crate::header::Header;
7use crate::io::filename::TrxFilename;
8use crate::trx_file::{DataArrayInfo, TrxFile};
9
10pub enum PositionsRef<'a> {
12 F16(&'a [[f16; 3]]),
13 F32(&'a [[f32; 3]]),
14 F64(&'a [[f64; 3]]),
15}
16
17pub enum AnyTrxFile {
22 F16(TrxFile<f16>),
23 F32(TrxFile<f32>),
24 F64(TrxFile<f64>),
25}
26
27impl AnyTrxFile {
28 pub fn load(path: &Path) -> Result<Self> {
30 let dtype = detect_positions_dtype(path)?;
31 match dtype {
32 DType::Float16 => Ok(AnyTrxFile::F16(TrxFile::<f16>::load(path)?)),
33 DType::Float32 => Ok(AnyTrxFile::F32(TrxFile::<f32>::load(path)?)),
34 DType::Float64 => Ok(AnyTrxFile::F64(TrxFile::<f64>::load(path)?)),
35 other => Err(TrxError::DType(format!(
36 "positions dtype {other} is not a float type"
37 ))),
38 }
39 }
40
41 pub fn positions_ref(&self) -> PositionsRef<'_> {
43 match self {
44 AnyTrxFile::F16(f) => PositionsRef::F16(f.positions()),
45 AnyTrxFile::F32(f) => PositionsRef::F32(f.positions()),
46 AnyTrxFile::F64(f) => PositionsRef::F64(f.positions()),
47 }
48 }
49
50 pub fn dtype(&self) -> DType {
52 match self {
53 AnyTrxFile::F16(_) => DType::Float16,
54 AnyTrxFile::F32(_) => DType::Float32,
55 AnyTrxFile::F64(_) => DType::Float64,
56 }
57 }
58
59 pub fn header(&self) -> &Header {
61 match self {
62 AnyTrxFile::F16(f) => f.header(),
63 AnyTrxFile::F32(f) => f.header(),
64 AnyTrxFile::F64(f) => f.header(),
65 }
66 }
67
68 pub fn nb_streamlines(&self) -> usize {
70 match self {
71 AnyTrxFile::F16(f) => f.nb_streamlines(),
72 AnyTrxFile::F32(f) => f.nb_streamlines(),
73 AnyTrxFile::F64(f) => f.nb_streamlines(),
74 }
75 }
76
77 pub fn nb_vertices(&self) -> usize {
79 match self {
80 AnyTrxFile::F16(f) => f.nb_vertices(),
81 AnyTrxFile::F32(f) => f.nb_vertices(),
82 AnyTrxFile::F64(f) => f.nb_vertices(),
83 }
84 }
85
86 pub fn with_typed<R>(
88 &self,
89 on_f16: impl FnOnce(&TrxFile<f16>) -> R,
90 on_f32: impl FnOnce(&TrxFile<f32>) -> R,
91 on_f64: impl FnOnce(&TrxFile<f64>) -> R,
92 ) -> R {
93 match self {
94 AnyTrxFile::F16(f) => on_f16(f),
95 AnyTrxFile::F32(f) => on_f32(f),
96 AnyTrxFile::F64(f) => on_f64(f),
97 }
98 }
99
100 pub fn positions_f32(&self) -> Vec<[f32; 3]> {
101 match self.positions_ref() {
102 PositionsRef::F16(data) => data
103 .iter()
104 .map(|point| [point[0].to_f32(), point[1].to_f32(), point[2].to_f32()])
105 .collect(),
106 PositionsRef::F32(data) => data.to_vec(),
107 PositionsRef::F64(data) => data
108 .iter()
109 .map(|point| [point[0] as f32, point[1] as f32, point[2] as f32])
110 .collect(),
111 }
112 }
113
114 pub fn offsets_vec(&self) -> Vec<u32> {
115 self.with_typed(
116 TrxFile::<f16>::offsets_vec,
117 TrxFile::<f32>::offsets_vec,
118 TrxFile::<f64>::offsets_vec,
119 )
120 }
121
122 pub fn dpv_entries(&self) -> Vec<(String, DataArrayInfo)> {
123 self.with_typed(
124 |trx| {
125 trx.iter_dpv()
126 .map(|(name, info)| (name.to_string(), info))
127 .collect()
128 },
129 |trx| {
130 trx.iter_dpv()
131 .map(|(name, info)| (name.to_string(), info))
132 .collect()
133 },
134 |trx| {
135 trx.iter_dpv()
136 .map(|(name, info)| (name.to_string(), info))
137 .collect()
138 },
139 )
140 }
141
142 pub fn dps_entries(&self) -> Vec<(String, DataArrayInfo)> {
143 self.with_typed(
144 |trx| {
145 trx.iter_dps()
146 .map(|(name, info)| (name.to_string(), info))
147 .collect()
148 },
149 |trx| {
150 trx.iter_dps()
151 .map(|(name, info)| (name.to_string(), info))
152 .collect()
153 },
154 |trx| {
155 trx.iter_dps()
156 .map(|(name, info)| (name.to_string(), info))
157 .collect()
158 },
159 )
160 }
161
162 pub fn groups_owned(&self) -> Vec<(String, Vec<u32>)> {
163 self.with_typed(
164 TrxFile::<f16>::group_entries_owned,
165 TrxFile::<f32>::group_entries_owned,
166 TrxFile::<f64>::group_entries_owned,
167 )
168 }
169
170 pub fn dpg_group_entries(&self) -> Vec<(String, Vec<(String, DataArrayInfo)>)> {
171 self.with_typed(
172 collect_dpg_group_entries,
173 collect_dpg_group_entries,
174 collect_dpg_group_entries,
175 )
176 }
177
178 pub fn scalar_dpv_f32(&self, name: &str) -> Result<Vec<f32>> {
179 self.with_typed(
180 |trx| trx.scalar_dpv_f32(name),
181 |trx| trx.scalar_dpv_f32(name),
182 |trx| trx.scalar_dpv_f32(name),
183 )
184 }
185
186 pub fn scalar_dps_f32(&self, name: &str) -> Result<Vec<f32>> {
187 self.with_typed(
188 |trx| trx.scalar_dps_f32(name),
189 |trx| trx.scalar_dps_f32(name),
190 |trx| trx.scalar_dps_f32(name),
191 )
192 }
193
194 pub fn with_updated_header(self, header: Header) -> Self {
196 match self {
197 AnyTrxFile::F16(f) => AnyTrxFile::F16(f.with_updated_header(header)),
198 AnyTrxFile::F32(f) => AnyTrxFile::F32(f.with_updated_header(header)),
199 AnyTrxFile::F64(f) => AnyTrxFile::F64(f.with_updated_header(header)),
200 }
201 }
202
203 pub fn save(&self, path: &Path) -> Result<()> {
204 self.with_typed(
205 |trx| trx.save(path),
206 |trx| trx.save(path),
207 |trx| trx.save(path),
208 )
209 }
210
211 pub fn convert_positions_dtype(&self, dtype: DType) -> Result<Self> {
212 match dtype {
213 DType::Float16 => self.with_typed(
214 |trx| Ok(Self::F16(trx.clone_with_positions_dtype::<f16>())),
215 |trx| Ok(Self::F16(trx.clone_with_positions_dtype::<f16>())),
216 |trx| Ok(Self::F16(trx.clone_with_positions_dtype::<f16>())),
217 ),
218 DType::Float32 => self.with_typed(
219 |trx| Ok(Self::F32(trx.clone_with_positions_dtype::<f32>())),
220 |trx| Ok(Self::F32(trx.clone_with_positions_dtype::<f32>())),
221 |trx| Ok(Self::F32(trx.clone_with_positions_dtype::<f32>())),
222 ),
223 DType::Float64 => self.with_typed(
224 |trx| Ok(Self::F64(trx.clone_with_positions_dtype::<f64>())),
225 |trx| Ok(Self::F64(trx.clone_with_positions_dtype::<f64>())),
226 |trx| Ok(Self::F64(trx.clone_with_positions_dtype::<f64>())),
227 ),
228 other => Err(TrxError::DType(format!(
229 "TRX positions must be float16, float32, or float64, got {other}"
230 ))),
231 }
232 }
233}
234
235impl std::fmt::Debug for AnyTrxFile {
236 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237 match self {
238 AnyTrxFile::F16(t) => t.fmt(f),
239 AnyTrxFile::F32(t) => t.fmt(f),
240 AnyTrxFile::F64(t) => t.fmt(f),
241 }
242 }
243}
244
245fn collect_dpg_group_entries<P: crate::dtype::TrxScalar>(
246 trx: &TrxFile<P>,
247) -> Vec<(String, Vec<(String, DataArrayInfo)>)> {
248 trx.dpg_group_names()
249 .into_iter()
250 .map(|group| {
251 let entries = trx
252 .dpg_entries(group)
253 .expect("group name came from dpg_group_names()");
254 (group.to_string(), entries)
255 })
256 .collect()
257}
258
259pub fn detect_positions_dtype(path: &Path) -> Result<DType> {
261 if path.is_dir() {
262 detect_positions_dtype_dir(path)
263 } else if path.is_file() {
264 detect_positions_dtype_zip(path)
265 } else {
266 Err(TrxError::FileNotFound(path.to_path_buf()))
267 }
268}
269
270fn detect_positions_dtype_dir(dir: &Path) -> Result<DType> {
271 for entry in std::fs::read_dir(dir)? {
272 let entry = entry?;
273 let name = entry.file_name();
274 let name_str = name.to_string_lossy();
275 if name_str.starts_with("positions.") {
276 let parsed = TrxFilename::parse(&name_str)?;
277 return Ok(parsed.dtype);
278 }
279 }
280 Err(TrxError::Format(
281 "no positions file found in directory".into(),
282 ))
283}
284
285fn detect_positions_dtype_zip(path: &Path) -> Result<DType> {
286 let file = std::fs::File::open(path)?;
287 let archive = zip::ZipArchive::new(file)?;
288
289 for i in 0..archive.len() {
290 let name = archive.name_for_index(i).unwrap_or("");
291 let basename = name.rsplit('/').next().unwrap_or(name);
292 if basename.starts_with("positions.") {
293 let parsed = TrxFilename::parse(basename)?;
294 return Ok(parsed.dtype);
295 }
296 }
297 Err(TrxError::Format(
298 "no positions file found in zip archive".into(),
299 ))
300}