1use bytemuck::{cast_slice, Pod};
2use std::collections::HashMap;
3use std::path::Path;
4
5use crate::dtype::{DType, TrxScalar};
6use crate::error::{Result, TrxError};
7use crate::header::Header;
8use crate::mmap_backing::{vec_to_bytes, MmapBacking};
9use crate::typed_view::TypedView2D;
10
11#[derive(Debug)]
13pub struct DataArray {
14 backing: MmapBacking,
15 ncols: usize,
16 dtype: DType,
17}
18
19impl DataArray {
20 pub fn owned_bytes(backing: Vec<u8>, ncols: usize, dtype: DType) -> Self {
21 Self {
22 backing: MmapBacking::Owned(backing),
23 ncols,
24 dtype,
25 }
26 }
27
28 pub(crate) fn from_backing(backing: MmapBacking, ncols: usize, dtype: DType) -> Self {
29 Self {
30 backing,
31 ncols,
32 dtype,
33 }
34 }
35
36 pub fn clone_owned(&self) -> Self {
37 Self::owned_bytes(self.backing.as_bytes().to_vec(), self.ncols, self.dtype)
38 }
39
40 pub fn ncols(&self) -> usize {
41 self.ncols
42 }
43
44 pub fn dtype(&self) -> DType {
45 self.dtype
46 }
47
48 pub fn len_bytes(&self) -> usize {
49 self.backing.len()
50 }
51
52 pub fn nrows(&self) -> usize {
53 let row_bytes = self.ncols * self.dtype.size_of();
54 self.len_bytes().checked_div(row_bytes).unwrap_or(0)
55 }
56
57 pub fn as_bytes(&self) -> &[u8] {
58 self.backing.as_bytes()
59 }
60
61 pub(crate) fn as_bytes_mut(&mut self) -> Result<&mut [u8]> {
62 self.backing.as_bytes_mut()
63 }
64
65 pub fn cast_slice<T: Pod>(&self) -> &[T] {
66 self.backing.cast_slice()
67 }
68
69 pub(crate) fn cast_slice_mut<T: Pod>(&mut self) -> Result<&mut [T]> {
70 self.backing.cast_slice_mut()
71 }
72
73 pub fn typed_view<T: Pod>(&self) -> TypedView2D<'_, T> {
74 let data: &[T] = cast_slice(self.as_bytes());
75 TypedView2D::new(data, self.ncols)
76 }
77
78 pub fn to_u32_vec(&self) -> Vec<u32> {
80 match self.dtype {
81 DType::UInt32 => bytemuck::pod_collect_to_vec(self.as_bytes()),
82 DType::Int32 => bytemuck::pod_collect_to_vec::<u8, i32>(self.as_bytes())
83 .into_iter()
84 .map(|x| x as u32)
85 .collect(),
86 DType::UInt64 => bytemuck::pod_collect_to_vec::<u8, u64>(self.as_bytes())
87 .into_iter()
88 .map(|x| x as u32)
89 .collect(),
90 DType::Int64 => bytemuck::pod_collect_to_vec::<u8, i64>(self.as_bytes())
91 .into_iter()
92 .map(|x| x as u32)
93 .collect(),
94 _ => bytemuck::pod_collect_to_vec(self.as_bytes()),
95 }
96 }
97}
98
99pub type DataPerGroup = HashMap<String, HashMap<String, DataArray>>;
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub struct DataArrayInfo {
103 pub ncols: usize,
104 pub nrows: usize,
105 pub dtype: DType,
106}
107
108pub(crate) struct TrxParts {
109 pub header: Header,
110 pub positions_backing: MmapBacking,
111 pub offsets_backing: MmapBacking,
112 pub dps: HashMap<String, DataArray>,
113 pub dpv: HashMap<String, DataArray>,
114 pub groups: HashMap<String, DataArray>,
115 pub dpg: DataPerGroup,
116}
117
118pub struct TrxFile<P: TrxScalar> {
123 header: Header,
124
125 positions_backing: MmapBacking,
127
128 offsets_backing: MmapBacking,
131
132 dps: HashMap<String, DataArray>,
134
135 dpv: HashMap<String, DataArray>,
137
138 groups: HashMap<String, DataArray>,
140
141 dpg: DataPerGroup,
143
144 _phantom: std::marker::PhantomData<P>,
145}
146
147impl<P: TrxScalar> TrxFile<P> {
148 pub fn empty(header: Header) -> Self {
150 Self {
151 header,
152 positions_backing: MmapBacking::Owned(Vec::new()),
153 offsets_backing: MmapBacking::Owned(Vec::new()),
154 dps: HashMap::new(),
155 dpv: HashMap::new(),
156 groups: HashMap::new(),
157 dpg: HashMap::new(),
158 _phantom: std::marker::PhantomData,
159 }
160 }
161
162 pub(crate) fn from_parts(parts: TrxParts) -> Self {
164 Self {
165 header: parts.header,
166 positions_backing: parts.positions_backing,
167 offsets_backing: parts.offsets_backing,
168 dps: parts.dps,
169 dpv: parts.dpv,
170 groups: parts.groups,
171 dpg: parts.dpg,
172 _phantom: std::marker::PhantomData,
173 }
174 }
175
176 pub fn header(&self) -> &Header {
177 &self.header
178 }
179
180 pub fn with_updated_header(self, header: Header) -> Self {
183 Self { header, ..self }
184 }
185
186 pub fn positions(&self) -> &[[P; 3]] {
190 let bytes = self.positions_backing.as_bytes();
191 if bytes.is_empty() {
192 &[]
193 } else {
194 cast_slice(bytes)
195 }
196 }
197
198 pub fn positions_2d(&self) -> TypedView2D<'_, P> {
200 let flat: &[P] = cast_slice(self.positions_backing.as_bytes());
201 TypedView2D::new(flat, 3)
202 }
203
204 pub fn positions_bytes(&self) -> &[u8] {
206 self.positions_backing.as_bytes()
207 }
208
209 pub fn nb_vertices(&self) -> usize {
211 self.positions().len()
212 }
213
214 pub fn offsets(&self) -> &[u32] {
219 self.offsets_backing.cast_slice()
220 }
221
222 pub fn offsets_vec(&self) -> Vec<u32> {
223 self.offsets().to_vec()
224 }
225
226 pub fn nb_streamlines(&self) -> usize {
228 let offsets = self.offsets();
229 if offsets.is_empty() {
230 0
231 } else {
232 offsets.len() - 1
233 }
234 }
235
236 pub fn streamline(&self, i: usize) -> &[[P; 3]] {
240 let offsets = self.offsets();
241 let start = offsets[i] as usize;
242 let end = offsets[i + 1] as usize;
243 &self.positions()[start..end]
244 }
245
246 pub fn streamlines(&self) -> StreamlineIter<'_, P> {
248 StreamlineIter {
249 positions: self.positions(),
250 offsets: self.offsets(),
251 index: 0,
252 }
253 }
254
255 pub fn streamline_lengths(&self) -> Vec<usize> {
257 let offsets = self.offsets();
258 offsets.windows(2).map(|w| (w[1] - w[0]) as usize).collect()
259 }
260
261 pub fn dps<T: Pod>(&self, name: &str) -> Result<TypedView2D<'_, T>> {
265 let arr = self.lookup_dps(name)?;
266 Ok(arr.typed_view())
267 }
268
269 pub fn dpv<T: Pod>(&self, name: &str) -> Result<TypedView2D<'_, T>> {
271 let arr = self.lookup_dpv(name)?;
272 Ok(arr.typed_view())
273 }
274
275 pub fn group(&self, name: &str) -> Result<&[u32]> {
277 let arr = self.lookup_group(name)?;
278 Ok(arr.cast_slice())
279 }
280
281 pub fn dpg<T: Pod>(&self, group: &str, name: &str) -> Result<TypedView2D<'_, T>> {
283 let arr = self.lookup_dpg(group, name)?;
284 Ok(arr.typed_view())
285 }
286
287 pub fn dps_names(&self) -> Vec<&str> {
289 self.dps.keys().map(|s| s.as_str()).collect()
290 }
291
292 pub fn dpv_names(&self) -> Vec<&str> {
294 self.dpv.keys().map(|s| s.as_str()).collect()
295 }
296
297 pub fn group_names(&self) -> Vec<&str> {
299 self.groups.keys().map(|s| s.as_str()).collect()
300 }
301
302 pub fn dpg_group_names(&self) -> Vec<&str> {
304 self.dpg.keys().map(|s| s.as_str()).collect()
305 }
306
307 pub fn iter_dps(&self) -> impl Iterator<Item = (&str, DataArrayInfo)> + '_ {
308 self.dps
309 .iter()
310 .map(|(name, arr)| (name.as_str(), arr.info()))
311 }
312
313 pub fn iter_dpv(&self) -> impl Iterator<Item = (&str, DataArrayInfo)> + '_ {
314 self.dpv
315 .iter()
316 .map(|(name, arr)| (name.as_str(), arr.info()))
317 }
318
319 pub fn iter_groups(&self) -> impl Iterator<Item = (&str, &[u32])> + '_ {
327 self.groups
328 .iter()
329 .map(|(name, arr)| (name.as_str(), arr.cast_slice::<u32>()))
330 }
331
332 pub fn dpg_entries(&self, group: &str) -> Result<Vec<(String, DataArrayInfo)>> {
333 let entries = self
334 .dpg
335 .get(group)
336 .ok_or_else(|| TrxError::Argument(format!("no DPG group named '{group}'")))?;
337 Ok(entries
338 .iter()
339 .map(|(name, arr)| (name.clone(), arr.info()))
340 .collect())
341 }
342
343 pub fn dps_info(&self, name: &str) -> Result<DataArrayInfo> {
344 Ok(self.lookup_dps(name)?.info())
345 }
346
347 pub fn dps_array(&self, name: &str) -> Result<&DataArray> {
348 self.lookup_dps(name)
349 }
350
351 pub fn dpv_info(&self, name: &str) -> Result<DataArrayInfo> {
352 Ok(self.lookup_dpv(name)?.info())
353 }
354
355 pub fn dpv_array(&self, name: &str) -> Result<&DataArray> {
356 self.lookup_dpv(name)
357 }
358
359 pub fn group_info(&self, name: &str) -> Result<DataArrayInfo> {
360 Ok(self.lookup_group(name)?.info())
361 }
362
363 pub fn group_array(&self, name: &str) -> Result<&DataArray> {
364 self.lookup_group(name)
365 }
366
367 pub fn dpg_info(&self, group: &str, name: &str) -> Result<DataArrayInfo> {
368 Ok(self.lookup_dpg(group, name)?.info())
369 }
370
371 pub fn dpg_array(&self, group: &str, name: &str) -> Result<&DataArray> {
372 self.lookup_dpg(group, name)
373 }
374
375 pub fn scalar_dps_f32(&self, name: &str) -> Result<Vec<f32>> {
376 read_scalar_array_as_f32(self.lookup_dps(name)?, "DPS", name)
377 }
378
379 pub fn scalar_dpv_f32(&self, name: &str) -> Result<Vec<f32>> {
380 read_scalar_array_as_f32(self.lookup_dpv(name)?, "DPV", name)
381 }
382
383 pub fn group_entries_owned(&self) -> Vec<(String, Vec<u32>)> {
384 self.groups
385 .iter()
386 .map(|(name, arr)| (name.clone(), arr.to_u32_vec()))
387 .collect()
388 }
389
390 pub fn load(path: &Path) -> Result<Self> {
394 crate::io::load::<P>(path)
395 }
396
397 pub fn save_to_directory(&self, path: &Path) -> Result<()> {
402 crate::io::directory::save_to_directory(self, path)
403 }
404
405 pub fn save_to_zip(&self, path: &Path) -> Result<()> {
409 crate::io::zip::save_to_zip(self, path)
410 }
411
412 pub fn save_to_zip_deflate_groups(&self, path: &Path) -> Result<()> {
415 crate::io::zip::save_to_zip_with(self, path, zip::CompressionMethod::Deflated)
416 }
417
418 pub fn save_to_zip_stored(&self, path: &Path) -> Result<()> {
420 crate::io::zip::save_to_zip(self, path)
421 }
422
423 pub fn save(&self, path: &Path) -> Result<()> {
425 if path.extension().and_then(|e| e.to_str()) == Some("trx") {
426 self.save_to_zip(path)
427 } else {
428 self.save_to_directory(path)
429 }
430 }
431
432 pub fn is_file_backed(&self) -> bool {
433 self.positions_backing.is_mapped()
434 }
435
436 pub(crate) fn dps_arrays(&self) -> &HashMap<String, DataArray> {
437 &self.dps
438 }
439
440 pub(crate) fn dpv_arrays(&self) -> &HashMap<String, DataArray> {
441 &self.dpv
442 }
443
444 pub(crate) fn group_arrays(&self) -> &HashMap<String, DataArray> {
445 &self.groups
446 }
447
448 pub(crate) fn dpg_arrays(&self) -> &DataPerGroup {
449 &self.dpg
450 }
451
452 pub(crate) fn dps_arrays_mut(&mut self) -> &mut HashMap<String, DataArray> {
453 &mut self.dps
454 }
455
456 pub(crate) fn dpv_arrays_mut(&mut self) -> &mut HashMap<String, DataArray> {
457 &mut self.dpv
458 }
459
460 pub(crate) fn group_arrays_mut(&mut self) -> &mut HashMap<String, DataArray> {
461 &mut self.groups
462 }
463
464 pub(crate) fn dpg_arrays_mut(&mut self) -> &mut DataPerGroup {
465 &mut self.dpg
466 }
467
468 pub(crate) fn clone_with_positions_dtype<Q>(&self) -> TrxFile<Q>
469 where
470 Q: TrxScalar + FromF32,
471 {
472 let positions: Vec<[Q; 3]> = self
473 .positions()
474 .iter()
475 .map(|point| {
476 [
477 Q::from_f32(point[0].to_f32()),
478 Q::from_f32(point[1].to_f32()),
479 Q::from_f32(point[2].to_f32()),
480 ]
481 })
482 .collect();
483
484 TrxFile::from_parts(TrxParts {
485 header: self.header.clone(),
486 positions_backing: MmapBacking::Owned(vec_to_bytes(positions)),
487 offsets_backing: MmapBacking::Owned(vec_to_bytes(self.offsets_vec())),
488 dps: clone_data_map(&self.dps),
489 dpv: clone_data_map(&self.dpv),
490 groups: clone_data_map(&self.groups),
491 dpg: clone_dpg_map(&self.dpg),
492 })
493 }
494
495 fn lookup_dps(&self, name: &str) -> Result<&DataArray> {
496 self.dps
497 .get(name)
498 .ok_or_else(|| TrxError::Argument(format!("no DPS named '{name}'")))
499 }
500
501 fn lookup_dpv(&self, name: &str) -> Result<&DataArray> {
502 self.dpv
503 .get(name)
504 .ok_or_else(|| TrxError::Argument(format!("no DPV named '{name}'")))
505 }
506
507 fn lookup_group(&self, name: &str) -> Result<&DataArray> {
508 self.groups
509 .get(name)
510 .ok_or_else(|| TrxError::Argument(format!("no group named '{name}'")))
511 }
512
513 fn lookup_dpg(&self, group: &str, name: &str) -> Result<&DataArray> {
514 let group_map = self
515 .dpg
516 .get(group)
517 .ok_or_else(|| TrxError::Argument(format!("no DPG group named '{group}'")))?;
518 group_map
519 .get(name)
520 .ok_or_else(|| TrxError::Argument(format!("no DPG named '{name}' in group '{group}'")))
521 }
522}
523
524impl<P: TrxScalar> std::fmt::Debug for TrxFile<P> {
525 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
526 f.debug_struct("TrxFile")
527 .field("dtype", &P::DTYPE)
528 .field("nb_streamlines", &self.nb_streamlines())
529 .field("nb_vertices", &self.nb_vertices())
530 .field("dps", &self.dps_names())
531 .field("dpv", &self.dpv_names())
532 .field("groups", &self.group_names())
533 .field("dpg_group_names", &self.dpg_group_names())
534 .finish()
535 }
536}
537
538pub struct StreamlineIter<'a, P: TrxScalar> {
540 positions: &'a [[P; 3]],
541 offsets: &'a [u32],
542 index: usize,
543}
544
545impl<'a, P: TrxScalar> Iterator for StreamlineIter<'a, P> {
546 type Item = &'a [[P; 3]];
547
548 fn next(&mut self) -> Option<Self::Item> {
549 if self.index + 1 >= self.offsets.len() {
550 return None;
551 }
552 let start = self.offsets[self.index] as usize;
553 let end = self.offsets[self.index + 1] as usize;
554 self.index += 1;
555 Some(&self.positions[start..end])
556 }
557
558 fn size_hint(&self) -> (usize, Option<usize>) {
559 let remaining = if self.offsets.is_empty() {
560 0
561 } else {
562 self.offsets.len() - 1 - self.index
563 };
564 (remaining, Some(remaining))
565 }
566}
567
568impl<'a, P: TrxScalar> ExactSizeIterator for StreamlineIter<'a, P> {}
569
570impl DataArray {
571 pub fn info(&self) -> DataArrayInfo {
572 DataArrayInfo {
573 ncols: self.ncols,
574 nrows: self.nrows(),
575 dtype: self.dtype,
576 }
577 }
578}
579
580fn read_scalar_array_as_f32(arr: &DataArray, kind: &str, name: &str) -> Result<Vec<f32>> {
581 if arr.ncols() != 1 {
582 return Err(TrxError::Argument(format!(
583 "{kind} '{name}' has {} columns; expected a scalar field",
584 arr.ncols()
585 )));
586 }
587
588 let values = match arr.dtype() {
589 DType::Float16 => arr
590 .cast_slice::<half::f16>()
591 .iter()
592 .map(|value| value.to_f32())
593 .collect(),
594 DType::Float32 => arr.cast_slice::<f32>().to_vec(),
595 DType::Float64 => arr
596 .cast_slice::<f64>()
597 .iter()
598 .map(|&value| value as f32)
599 .collect(),
600 DType::Int8 => arr
601 .cast_slice::<i8>()
602 .iter()
603 .map(|&value| value as f32)
604 .collect(),
605 DType::Int16 => arr
606 .cast_slice::<i16>()
607 .iter()
608 .map(|&value| value as f32)
609 .collect(),
610 DType::Int32 => arr
611 .cast_slice::<i32>()
612 .iter()
613 .map(|&value| value as f32)
614 .collect(),
615 DType::UInt8 => arr
616 .cast_slice::<u8>()
617 .iter()
618 .map(|&value| value as f32)
619 .collect(),
620 DType::UInt16 => arr
621 .cast_slice::<u16>()
622 .iter()
623 .map(|&value| value as f32)
624 .collect(),
625 DType::UInt32 => arr
626 .cast_slice::<u32>()
627 .iter()
628 .map(|&value| value as f32)
629 .collect(),
630 other => {
631 return Err(TrxError::DType(format!(
632 "{kind} '{name}' uses unsupported scalar dtype {other}"
633 )))
634 }
635 };
636
637 Ok(values)
638}
639
640fn clone_data_map(map: &HashMap<String, DataArray>) -> HashMap<String, DataArray> {
641 map.iter()
642 .map(|(name, arr)| (name.clone(), arr.clone_owned()))
643 .collect()
644}
645
646fn clone_dpg_map(map: &DataPerGroup) -> DataPerGroup {
647 map.iter()
648 .map(|(group, entries)| (group.clone(), clone_data_map(entries)))
649 .collect()
650}
651
652pub(crate) trait FromF32 {
653 fn from_f32(value: f32) -> Self;
654}
655
656impl FromF32 for half::f16 {
657 fn from_f32(value: f32) -> Self {
658 half::f16::from_f32(value)
659 }
660}
661
662impl FromF32 for f32 {
663 fn from_f32(value: f32) -> Self {
664 value
665 }
666}
667
668impl FromF32 for f64 {
669 fn from_f32(value: f32) -> Self {
670 value as f64
671 }
672}