Skip to main content

czi_rs/
types.rs

1use std::collections::{BTreeMap, HashMap};
2
3use crate::error::{CziError, Result};
4
5#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
6pub enum Dimension {
7    Z,
8    C,
9    T,
10    R,
11    S,
12    I,
13    H,
14    V,
15    B,
16    X,
17    Y,
18    M,
19}
20
21impl Dimension {
22    pub const FRAME_ORDER: [Dimension; 9] = [
23        Dimension::S,
24        Dimension::T,
25        Dimension::C,
26        Dimension::Z,
27        Dimension::R,
28        Dimension::I,
29        Dimension::H,
30        Dimension::V,
31        Dimension::B,
32    ];
33
34    pub fn as_str(self) -> &'static str {
35        match self {
36            Self::Z => "Z",
37            Self::C => "C",
38            Self::T => "T",
39            Self::R => "R",
40            Self::S => "S",
41            Self::I => "I",
42            Self::H => "H",
43            Self::V => "V",
44            Self::B => "B",
45            Self::X => "X",
46            Self::Y => "Y",
47            Self::M => "M",
48        }
49    }
50
51    pub fn from_code(code: &str) -> Option<Self> {
52        match code.trim().to_ascii_uppercase().as_str() {
53            "Z" => Some(Self::Z),
54            "C" => Some(Self::C),
55            "T" => Some(Self::T),
56            "R" => Some(Self::R),
57            "S" => Some(Self::S),
58            "I" => Some(Self::I),
59            "H" => Some(Self::H),
60            "V" => Some(Self::V),
61            "B" => Some(Self::B),
62            "X" => Some(Self::X),
63            "Y" => Some(Self::Y),
64            "M" => Some(Self::M),
65            _ => None,
66        }
67    }
68
69    pub fn is_frame_dimension(self) -> bool {
70        matches!(
71            self,
72            Self::Z | Self::C | Self::T | Self::R | Self::S | Self::I | Self::H | Self::V | Self::B
73        )
74    }
75}
76
77impl std::fmt::Display for Dimension {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        f.write_str(self.as_str())
80    }
81}
82
83#[derive(Copy, Clone, Debug, PartialEq, Eq)]
84pub struct Interval {
85    pub start: i32,
86    pub size: usize,
87}
88
89impl Interval {
90    pub fn end_exclusive(self) -> i32 {
91        self.start + self.size as i32
92    }
93}
94
95#[derive(Clone, Debug, Default, PartialEq, Eq)]
96pub struct DimBounds {
97    intervals: BTreeMap<Dimension, Interval>,
98}
99
100impl DimBounds {
101    pub fn new() -> Self {
102        Self::default()
103    }
104
105    pub fn get(&self, dimension: Dimension) -> Option<Interval> {
106        self.intervals.get(&dimension).copied()
107    }
108
109    pub fn set(&mut self, dimension: Dimension, interval: Interval) {
110        self.intervals.insert(dimension, interval);
111    }
112
113    pub fn update_value(&mut self, dimension: Dimension, value: i32) {
114        match self.intervals.get_mut(&dimension) {
115            Some(existing) => {
116                let current_end = existing.end_exclusive();
117                if value < existing.start {
118                    existing.size = (current_end - value) as usize;
119                    existing.start = value;
120                } else if value >= current_end {
121                    existing.size = (value - existing.start + 1) as usize;
122                }
123            }
124            None => {
125                self.set(
126                    dimension,
127                    Interval {
128                        start: value,
129                        size: 1,
130                    },
131                );
132            }
133        }
134    }
135
136    pub fn iter(&self) -> impl Iterator<Item = (Dimension, Interval)> + '_ {
137        self.intervals
138            .iter()
139            .map(|(dimension, interval)| (*dimension, *interval))
140    }
141
142    pub fn to_size_map(&self) -> HashMap<String, usize> {
143        self.iter()
144            .map(|(dimension, interval)| (dimension.as_str().to_owned(), interval.size))
145            .collect()
146    }
147}
148
149#[derive(Clone, Debug, Default, PartialEq, Eq)]
150pub struct Coordinate {
151    values: BTreeMap<Dimension, i32>,
152}
153
154impl Coordinate {
155    pub fn new() -> Self {
156        Self::default()
157    }
158
159    pub fn with(mut self, dimension: Dimension, value: i32) -> Self {
160        self.set(dimension, value);
161        self
162    }
163
164    pub fn set(&mut self, dimension: Dimension, value: i32) {
165        self.values.insert(dimension, value);
166    }
167
168    pub fn get(&self, dimension: Dimension) -> Option<i32> {
169        self.values.get(&dimension).copied()
170    }
171
172    pub fn contains(&self, dimension: Dimension) -> bool {
173        self.values.contains_key(&dimension)
174    }
175
176    pub fn iter(&self) -> impl Iterator<Item = (Dimension, i32)> + '_ {
177        self.values
178            .iter()
179            .map(|(dimension, value)| (*dimension, *value))
180    }
181}
182
183#[derive(Clone, Debug, Default, PartialEq, Eq)]
184pub struct PlaneIndex {
185    values: BTreeMap<Dimension, usize>,
186}
187
188impl PlaneIndex {
189    pub fn new() -> Self {
190        Self::default()
191    }
192
193    pub fn with(mut self, dimension: Dimension, value: usize) -> Self {
194        self.set(dimension, value);
195        self
196    }
197
198    pub fn set(&mut self, dimension: Dimension, value: usize) {
199        self.values.insert(dimension, value);
200    }
201
202    pub fn get(&self, dimension: Dimension) -> Option<usize> {
203        self.values.get(&dimension).copied()
204    }
205
206    pub fn iter(&self) -> impl Iterator<Item = (Dimension, usize)> + '_ {
207        self.values
208            .iter()
209            .map(|(dimension, value)| (*dimension, *value))
210    }
211}
212
213#[derive(Copy, Clone, Debug, PartialEq, Eq)]
214pub struct IntRect {
215    pub x: i32,
216    pub y: i32,
217    pub w: i32,
218    pub h: i32,
219}
220
221impl IntRect {
222    pub fn new(x: i32, y: i32, w: i32, h: i32) -> Self {
223        Self { x, y, w, h }
224    }
225
226    pub fn is_valid(self) -> bool {
227        self.w >= 0 && self.h >= 0
228    }
229
230    pub fn is_non_empty(self) -> bool {
231        self.w > 0 && self.h > 0
232    }
233
234    pub fn right(self) -> i32 {
235        self.x + self.w
236    }
237
238    pub fn bottom(self) -> i32 {
239        self.y + self.h
240    }
241
242    pub fn intersect(self, other: Self) -> Option<Self> {
243        let x1 = self.x.max(other.x);
244        let y1 = self.y.max(other.y);
245        let x2 = self.right().min(other.right());
246        let y2 = self.bottom().min(other.bottom());
247        if x2 <= x1 || y2 <= y1 {
248            return None;
249        }
250        Some(Self::new(x1, y1, x2 - x1, y2 - y1))
251    }
252
253    pub(crate) fn union_with(&mut self, other: Self) {
254        if !self.is_valid() {
255            *self = other;
256            return;
257        }
258
259        let x1 = self.x.min(other.x);
260        let y1 = self.y.min(other.y);
261        let x2 = self.right().max(other.right());
262        let y2 = self.bottom().max(other.bottom());
263        *self = Self::new(x1, y1, x2 - x1, y2 - y1);
264    }
265}
266
267#[derive(Copy, Clone, Debug, PartialEq, Eq)]
268pub struct IntSize {
269    pub w: u32,
270    pub h: u32,
271}
272
273#[derive(Copy, Clone, Debug, PartialEq, Eq)]
274pub enum PixelType {
275    Gray8,
276    Gray16,
277    Gray32Float,
278    Bgr24,
279    Bgr48,
280    Bgr96Float,
281    Bgra32,
282    Gray64ComplexFloat,
283    Bgr192ComplexFloat,
284    Gray32,
285    Gray64Float,
286}
287
288impl PixelType {
289    pub fn from_raw(value: i32) -> Option<Self> {
290        match value {
291            0 => Some(Self::Gray8),
292            1 => Some(Self::Gray16),
293            2 => Some(Self::Gray32Float),
294            3 => Some(Self::Bgr24),
295            4 => Some(Self::Bgr48),
296            8 => Some(Self::Bgr96Float),
297            9 => Some(Self::Bgra32),
298            10 => Some(Self::Gray64ComplexFloat),
299            11 => Some(Self::Bgr192ComplexFloat),
300            12 => Some(Self::Gray32),
301            13 => Some(Self::Gray64Float),
302            _ => None,
303        }
304    }
305
306    pub fn from_name(name: &str) -> Option<Self> {
307        match name.trim() {
308            "Gray8" => Some(Self::Gray8),
309            "Gray16" => Some(Self::Gray16),
310            "Gray32Float" => Some(Self::Gray32Float),
311            "Bgr24" => Some(Self::Bgr24),
312            "Bgr48" => Some(Self::Bgr48),
313            "Bgr96Float" => Some(Self::Bgr96Float),
314            "Bgra32" => Some(Self::Bgra32),
315            "Gray64ComplexFloat" => Some(Self::Gray64ComplexFloat),
316            "Bgr192ComplexFloat" => Some(Self::Bgr192ComplexFloat),
317            "Gray32" => Some(Self::Gray32),
318            "Gray64Float" => Some(Self::Gray64Float),
319            _ => None,
320        }
321    }
322
323    pub fn as_str(self) -> &'static str {
324        match self {
325            Self::Gray8 => "Gray8",
326            Self::Gray16 => "Gray16",
327            Self::Gray32Float => "Gray32Float",
328            Self::Bgr24 => "Bgr24",
329            Self::Bgr48 => "Bgr48",
330            Self::Bgr96Float => "Bgr96Float",
331            Self::Bgra32 => "Bgra32",
332            Self::Gray64ComplexFloat => "Gray64ComplexFloat",
333            Self::Bgr192ComplexFloat => "Bgr192ComplexFloat",
334            Self::Gray32 => "Gray32",
335            Self::Gray64Float => "Gray64Float",
336        }
337    }
338
339    pub fn bytes_per_pixel(self) -> usize {
340        match self {
341            Self::Gray8 => 1,
342            Self::Gray16 => 2,
343            Self::Gray32Float => 4,
344            Self::Bgr24 => 3,
345            Self::Bgr48 => 6,
346            Self::Bgr96Float => 12,
347            Self::Bgra32 => 4,
348            Self::Gray64ComplexFloat => 16,
349            Self::Bgr192ComplexFloat => 24,
350            Self::Gray32 => 4,
351            Self::Gray64Float => 8,
352        }
353    }
354}
355
356impl std::fmt::Display for PixelType {
357    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
358        f.write_str(self.as_str())
359    }
360}
361
362#[derive(Copy, Clone, Debug, PartialEq, Eq)]
363pub enum CompressionMode {
364    UnCompressed,
365    Jpg,
366    JpgXr,
367    Zstd0,
368    Zstd1,
369}
370
371impl CompressionMode {
372    pub fn from_raw(value: i32) -> Option<Self> {
373        match value {
374            0 => Some(Self::UnCompressed),
375            1 => Some(Self::Jpg),
376            4 => Some(Self::JpgXr),
377            5 => Some(Self::Zstd0),
378            6 => Some(Self::Zstd1),
379            _ => None,
380        }
381    }
382
383    pub fn as_str(self) -> &'static str {
384        match self {
385            Self::UnCompressed => "UnCompressed",
386            Self::Jpg => "Jpg",
387            Self::JpgXr => "JpgXr",
388            Self::Zstd0 => "Zstd0",
389            Self::Zstd1 => "Zstd1",
390        }
391    }
392}
393
394impl std::fmt::Display for CompressionMode {
395    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
396        f.write_str(self.as_str())
397    }
398}
399
400#[derive(Copy, Clone, Debug, PartialEq, Eq)]
401pub enum SubBlockPyramidType {
402    None,
403    SingleSubBlock,
404    MultiSubBlock,
405}
406
407impl SubBlockPyramidType {
408    pub fn from_raw(value: u8) -> Option<Self> {
409        match value {
410            0 => Some(Self::None),
411            1 => Some(Self::SingleSubBlock),
412            2 => Some(Self::MultiSubBlock),
413            _ => None,
414        }
415    }
416}
417
418#[derive(Clone, Debug, PartialEq, Eq)]
419pub struct FileHeaderInfo {
420    pub major: i32,
421    pub minor: i32,
422    pub primary_file_guid: String,
423    pub file_guid: String,
424    pub file_part: i32,
425    pub subblock_directory_position: u64,
426    pub metadata_position: u64,
427    pub attachment_directory_position: u64,
428    pub update_pending: bool,
429}
430
431#[derive(Clone, Debug, PartialEq, Eq)]
432pub struct DirectorySubBlockInfo {
433    pub index: usize,
434    pub file_position: u64,
435    pub file_part: i32,
436    pub pixel_type: PixelType,
437    pub compression: CompressionMode,
438    pub coordinate: Coordinate,
439    pub rect: IntRect,
440    pub stored_size: IntSize,
441    pub m_index: Option<i32>,
442    pub pyramid_type: Option<SubBlockPyramidType>,
443}
444
445impl DirectorySubBlockInfo {
446    pub fn is_layer0(&self) -> bool {
447        self.rect.w >= 0
448            && self.rect.h >= 0
449            && self.stored_size.w == self.rect.w as u32
450            && self.stored_size.h == self.rect.h as u32
451    }
452}
453
454#[derive(Clone, Debug, PartialEq, Eq)]
455pub struct AttachmentInfo {
456    pub index: usize,
457    pub file_position: u64,
458    pub file_part: i32,
459    pub content_guid: String,
460    pub content_file_type: String,
461    pub name: String,
462    pub data_size: u64,
463}
464
465#[derive(Clone, Debug, PartialEq, Eq)]
466pub struct AttachmentBlob {
467    pub info: AttachmentInfo,
468    pub data: Vec<u8>,
469}
470
471#[derive(Clone, Debug, PartialEq, Eq)]
472pub struct RawSubBlock {
473    pub info: DirectorySubBlockInfo,
474    pub metadata: Vec<u8>,
475    pub data: Vec<u8>,
476    pub attachment: Vec<u8>,
477}
478
479#[derive(Copy, Clone, Debug, PartialEq, Eq)]
480pub struct BoundingBoxes {
481    pub all: IntRect,
482    pub layer0: IntRect,
483}
484
485#[derive(Clone, Debug, PartialEq, Eq)]
486pub struct SubBlockStatistics {
487    pub subblock_count: usize,
488    pub dim_bounds: DimBounds,
489    pub bounding_box: Option<IntRect>,
490    pub bounding_box_layer0: Option<IntRect>,
491    pub scene_bounding_boxes: BTreeMap<i32, BoundingBoxes>,
492    pub min_m_index: Option<i32>,
493    pub max_m_index: Option<i32>,
494}
495
496impl Default for SubBlockStatistics {
497    fn default() -> Self {
498        Self {
499            subblock_count: 0,
500            dim_bounds: DimBounds::new(),
501            bounding_box: None,
502            bounding_box_layer0: None,
503            scene_bounding_boxes: BTreeMap::new(),
504            min_m_index: None,
505            max_m_index: None,
506        }
507    }
508}
509
510#[derive(Clone, Debug, Default, PartialEq)]
511pub struct ScalingInfo {
512    pub x: Option<f64>,
513    pub y: Option<f64>,
514    pub z: Option<f64>,
515    pub unit: Option<String>,
516}
517
518#[derive(Clone, Debug, Default, PartialEq, Eq)]
519pub struct ChannelInfo {
520    pub index: usize,
521    pub id: Option<String>,
522    pub name: Option<String>,
523    pub pixel_type: Option<PixelType>,
524    pub color: Option<String>,
525}
526
527#[derive(Clone, Debug, Default, PartialEq, Eq)]
528pub struct DocumentInfo {
529    pub name: Option<String>,
530    pub title: Option<String>,
531    pub comment: Option<String>,
532    pub author: Option<String>,
533    pub user_name: Option<String>,
534    pub creation_date: Option<String>,
535    pub description: Option<String>,
536    pub application_name: Option<String>,
537    pub application_version: Option<String>,
538}
539
540#[derive(Clone, Debug, Default, PartialEq, Eq)]
541pub struct ImageInfo {
542    pub pixel_type: Option<PixelType>,
543    pub sizes: BTreeMap<Dimension, usize>,
544}
545
546#[derive(Clone, Debug, Default, PartialEq)]
547pub struct MetadataSummary {
548    pub document: DocumentInfo,
549    pub image: ImageInfo,
550    pub scaling: ScalingInfo,
551    pub channels: Vec<ChannelInfo>,
552}
553
554#[derive(Clone, Debug, PartialEq, Eq)]
555pub struct Bitmap {
556    pub pixel_type: PixelType,
557    pub width: u32,
558    pub height: u32,
559    pub stride: usize,
560    pub data: Vec<u8>,
561}
562
563impl Bitmap {
564    pub fn new(pixel_type: PixelType, width: u32, height: u32, data: Vec<u8>) -> Result<Self> {
565        let stride = width as usize * pixel_type.bytes_per_pixel();
566        let expected = stride
567            .checked_mul(height as usize)
568            .ok_or_else(|| CziError::internal_overflow("bitmap size"))?;
569        if data.len() != expected {
570            return Err(CziError::file_invalid_format(format!(
571                "bitmap byte count mismatch: expected {expected}, got {}",
572                data.len()
573            )));
574        }
575
576        Ok(Self {
577            pixel_type,
578            width,
579            height,
580            stride,
581            data,
582        })
583    }
584
585    pub fn zeros(pixel_type: PixelType, width: u32, height: u32) -> Result<Self> {
586        let stride = width as usize * pixel_type.bytes_per_pixel();
587        let len = stride
588            .checked_mul(height as usize)
589            .ok_or_else(|| CziError::internal_overflow("bitmap allocation"))?;
590        Ok(Self {
591            pixel_type,
592            width,
593            height,
594            stride,
595            data: vec![0; len],
596        })
597    }
598
599    pub fn bytes_per_pixel(&self) -> usize {
600        self.pixel_type.bytes_per_pixel()
601    }
602
603    pub fn as_bytes(&self) -> &[u8] {
604        &self.data
605    }
606
607    pub fn into_bytes(self) -> Vec<u8> {
608        self.data
609    }
610
611    pub fn to_u16_vec(&self) -> Result<Vec<u16>> {
612        if self.bytes_per_pixel() % 2 != 0 {
613            return Err(CziError::unsupported_pixel_type(self.pixel_type.as_str()));
614        }
615
616        let mut values = Vec::with_capacity(self.data.len() / 2);
617        for chunk in self.data.chunks_exact(2) {
618            values.push(u16::from_le_bytes([chunk[0], chunk[1]]));
619        }
620        Ok(values)
621    }
622
623    pub fn to_f32_vec(&self) -> Result<Vec<f32>> {
624        if self.bytes_per_pixel() % 4 != 0 {
625            return Err(CziError::unsupported_pixel_type(self.pixel_type.as_str()));
626        }
627
628        let mut values = Vec::with_capacity(self.data.len() / 4);
629        for chunk in self.data.chunks_exact(4) {
630            values.push(f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
631        }
632        Ok(values)
633    }
634}