Skip to main content

mrc/
mode.rs

1//! Voxel mode definitions and the [`Voxel`] trait.
2//!
3//! The MRC format stores voxel data in one of several numeric modes.
4//! The [`Mode`] enum maps mode constants to their Rust representations,
5//! and the [`Voxel`] trait connects Rust types to their corresponding modes
6//! at compile time for type-safe I/O.
7
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11/// Strategy for converting complex numbers to real values.
12///
13/// # Example
14///
15/// ```rust
16/// use mrc::ComplexToRealStrategy;
17///
18/// let s = ComplexToRealStrategy::Magnitude;
19/// assert!(matches!(s, ComplexToRealStrategy::Magnitude));
20/// ```
21#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23#[non_exhaustive]
24pub enum ComplexToRealStrategy {
25    /// Use the real component as the output value.
26    RealPart,
27    /// Use the imaginary component as the output value.
28    ImaginaryPart,
29    /// Compute `sqrt(real² + imag²)`.
30    Magnitude,
31    /// Compute `atan2(imag, real)`.
32    Phase,
33}
34
35/// Interpretation of Mode 0 (8-bit) data for legacy files.
36///
37/// Some MRC files store unsigned 8-bit data under Mode 0 (which normally
38/// represents `i8`). Use this enum to select the correct interpretation
39/// when reading such files.
40///
41/// # Example
42///
43/// ```rust
44/// use mrc::M0Interpretation;
45///
46/// let interp = M0Interpretation::Unsigned;
47/// assert!(matches!(interp, M0Interpretation::Unsigned));
48/// ```
49#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51#[non_exhaustive]
52pub enum M0Interpretation {
53    /// Treat bytes as signed `i8` values (standard Mode 0).
54    Signed,
55    /// Treat bytes as unsigned `u8` values (legacy convention).
56    Unsigned,
57}
58
59/// MRC data mode defining the on-disk representation of voxel values.
60///
61/// # Example
62///
63/// ```rust
64/// use mrc::Mode;
65///
66/// let mode = Mode::Float32;
67/// assert_eq!(mode.byte_size(), 4);
68/// assert!(mode.is_float());
69/// assert!(!mode.is_integer());
70/// ```
71#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73#[non_exhaustive]
74pub enum Mode {
75    /// Signed 8-bit integer (Mode 0).
76    Int8 = 0,
77    /// Signed 16-bit integer (Mode 1).
78    Int16 = 1,
79    /// 32-bit floating point (Mode 2).
80    Float32 = 2,
81    /// Complex number with 16-bit integer components (Mode 3)
82    ///
83    /// # Byte Order
84    /// The layout is `[real i16 (2 bytes), imag i16 (2 bytes)]` which matches the
85    /// de facto standard used by CCP4, IMOD, and other MRC implementations.
86    /// This is not explicitly specified in MRC2014 but is universally adopted.
87    Int16Complex = 3,
88    /// Complex number with 32-bit float components (Mode 4)
89    ///
90    /// # Byte Order
91    /// The layout is `[real f32 (4 bytes), imag f32 (4 bytes)]` which matches the
92    /// de facto standard used by CCP4, IMOD, and other MRC implementations.
93    /// This is not explicitly specified in MRC2014 but is universally adopted.
94    Float32Complex = 4,
95    /// Unsigned 16-bit integer (Mode 6).
96    Uint16 = 6,
97    /// 16-bit floating point (Mode 12).
98    Float16 = 12,
99    /// 4-bit data packed two values per byte (Mode 101).
100    ///
101    /// Each byte stores two 4-bit nibbles: low nibble = first pixel,
102    /// high nibble = second pixel. Read via [`slices_u8`](crate::Reader::slices_u8)
103    /// or [`convert::<f32>()`](crate::Reader::convert); write via
104    /// [`write_u4_block`](crate::Writer::write_u4_block).
105    Packed4Bit = 101,
106}
107
108impl Mode {
109    /// Return the MRC mode constant as an `i32` value.
110    ///
111    /// # Example
112    ///
113    /// ```rust
114    /// use mrc::Mode;
115    ///
116    /// assert_eq!(Mode::Int8.as_i32(), 0);
117    /// assert_eq!(Mode::Float32.as_i32(), 2);
118    /// ```
119    #[inline]
120    pub const fn as_i32(self) -> i32 {
121        self as i32
122    }
123
124    /// Convert an MRC mode integer to a [`Mode`] enum value.
125    ///
126    /// Returns `None` for unrecognized mode values.
127    ///
128    /// # Example
129    ///
130    /// ```rust
131    /// use mrc::Mode;
132    ///
133    /// assert_eq!(Mode::from_i32(2), Some(Mode::Float32));
134    /// assert_eq!(Mode::from_i32(99), None);
135    /// ```
136    #[inline]
137    pub fn from_i32(mode: i32) -> Option<Self> {
138        match mode {
139            0 => Some(Self::Int8),
140            1 => Some(Self::Int16),
141            2 => Some(Self::Float32),
142            3 => Some(Self::Int16Complex),
143            4 => Some(Self::Float32Complex),
144            6 => Some(Self::Uint16),
145            12 => Some(Self::Float16),
146            101 => Some(Self::Packed4Bit),
147            _ => None,
148        }
149    }
150
151    /// Number of bytes required to store one voxel in this mode.
152    ///
153    /// For [`Packed4Bit`](Mode::Packed4Bit) this returns `1` (two voxels per byte);
154    /// use [`byte_size_for_count`](Mode::byte_size_for_count) for per-voxel sizing.
155    ///
156    /// # Example
157    ///
158    /// ```rust
159    /// use mrc::Mode;
160    ///
161    /// assert_eq!(Mode::Int16.byte_size(), 2);
162    /// assert_eq!(Mode::Float32Complex.byte_size(), 8);
163    /// ```
164    #[inline]
165    pub fn byte_size(&self) -> usize {
166        match self {
167            Self::Int8 => 1,
168            Self::Int16 => 2,
169            Self::Float32 => 4,
170            Self::Int16Complex => 4,   // 2 bytes real + 2 bytes imaginary
171            Self::Float32Complex => 8, // 4 bytes real + 4 bytes imaginary
172            Self::Uint16 => 2,
173            Self::Float16 => 2,
174            Self::Packed4Bit => 1, // 4 bits per value, 2 values per byte
175        }
176    }
177
178    /// Returns `true` if this mode stores complex numbers (real + imaginary components).
179    ///
180    /// # Example
181    ///
182    /// ```rust
183    /// use mrc::Mode;
184    ///
185    /// assert!(Mode::Int16Complex.is_complex());
186    /// assert!(!Mode::Float32.is_complex());
187    /// ```
188    #[inline]
189    pub fn is_complex(&self) -> bool {
190        matches!(self, Self::Int16Complex | Self::Float32Complex)
191    }
192
193    /// Returns `true` if this mode stores integer-valued data (including complex integers).
194    ///
195    /// # Example
196    ///
197    /// ```rust
198    /// use mrc::Mode;
199    ///
200    /// assert!(Mode::Uint16.is_integer());
201    /// assert!(!Mode::Float32.is_integer());
202    /// ```
203    #[inline]
204    pub fn is_integer(&self) -> bool {
205        matches!(
206            self,
207            Self::Int8 | Self::Int16 | Self::Int16Complex | Self::Uint16 | Self::Packed4Bit
208        )
209    }
210
211    /// Returns `true` if this mode stores floating-point data (including complex float).
212    ///
213    /// # Example
214    ///
215    /// ```rust
216    /// use mrc::Mode;
217    ///
218    /// assert!(Mode::Float16.is_float());
219    /// assert!(!Mode::Int8.is_float());
220    /// ```
221    #[inline]
222    pub fn is_float(&self) -> bool {
223        matches!(self, Self::Float32 | Self::Float32Complex | Self::Float16)
224    }
225
226    /// Byte size for a given number of voxels.
227    ///
228    /// For most modes this is `n * byte_size()`, but `Packed4Bit`
229    /// stores two voxels per byte so the result is `n.div_ceil(2)`.
230    ///
231    /// # Example
232    ///
233    /// ```rust
234    /// use mrc::Mode;
235    ///
236    /// assert_eq!(Mode::Int16.byte_size_for_count(3), 6);
237    /// assert_eq!(Mode::Packed4Bit.byte_size_for_count(3), 2);
238    /// ```
239    #[inline]
240    pub fn byte_size_for_count(&self, n: usize) -> usize {
241        match self {
242            Self::Packed4Bit => n.div_ceil(2),
243            _ => n * self.byte_size(),
244        }
245    }
246}
247
248/// A complex number with 16-bit signed integer real and imaginary components.
249///
250/// Corresponds to MRC Mode 3. The byte layout is `[real i16, imag i16]`
251/// (4 bytes total), stored in file byte order.
252#[derive(Debug, Clone, Copy, PartialEq, Default)]
253#[repr(C)]
254#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
255pub struct Int16Complex {
256    /// Real component.
257    pub real: i16,
258    /// Imaginary component.
259    pub imag: i16,
260}
261
262impl Int16Complex {
263    /// Convert this complex number to a real value using the given strategy.
264    ///
265    /// # Example
266    ///
267    /// ```rust
268    /// use mrc::{Int16Complex, ComplexToRealStrategy};
269    ///
270    /// let c = Int16Complex { real: 3, imag: 4 };
271    /// assert_eq!(c.to_real(ComplexToRealStrategy::RealPart), 3.0);
272    /// ```
273    #[inline]
274    pub fn to_real(&self, strategy: ComplexToRealStrategy) -> f32 {
275        match strategy {
276            ComplexToRealStrategy::RealPart => self.real as f32,
277            ComplexToRealStrategy::ImaginaryPart => self.imag as f32,
278            ComplexToRealStrategy::Magnitude => {
279                let r = self.real as f32;
280                let i = self.imag as f32;
281                (r * r + i * i).sqrt()
282            }
283            ComplexToRealStrategy::Phase => (self.imag as f32).atan2(self.real as f32),
284        }
285    }
286}
287
288/// A complex number with 32-bit float real and imaginary components.
289///
290/// Corresponds to MRC Mode 4. The byte layout is `[real f32, imag f32]`
291/// (8 bytes total), stored in file byte order.
292#[derive(Debug, Clone, Copy, PartialEq, Default)]
293#[repr(C)]
294#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
295pub struct Float32Complex {
296    /// Real component.
297    pub real: f32,
298    /// Imaginary component.
299    pub imag: f32,
300}
301
302impl Float32Complex {
303    /// Convert this complex number to a real value using the given strategy.
304    ///
305    /// # Example
306    ///
307    /// ```rust
308    /// use mrc::{Float32Complex, ComplexToRealStrategy};
309    ///
310    /// let c = Float32Complex { real: 3.0, imag: 4.0 };
311    /// let mag = c.to_real(ComplexToRealStrategy::Magnitude);
312    /// assert!((mag - 5.0).abs() < 1e-6);
313    /// ```
314    #[inline]
315    pub fn to_real(&self, strategy: ComplexToRealStrategy) -> f32 {
316        match strategy {
317            ComplexToRealStrategy::RealPart => self.real,
318            ComplexToRealStrategy::ImaginaryPart => self.imag,
319            ComplexToRealStrategy::Magnitude => {
320                (self.real * self.real + self.imag * self.imag).sqrt()
321            }
322            ComplexToRealStrategy::Phase => self.imag.atan2(self.real),
323        }
324    }
325}
326
327/// Trait for MRC voxel types with compile-time mode tracking.
328///
329/// Each voxel type knows its MRC mode constant, enabling type-safe I/O
330/// without runtime mode dispatch.
331///
332/// Note: `BYTE_SIZE` is inherited from the `EndianCodec` supertrait.
333pub trait Voxel:
334    crate::engine::codec::EndianCodec + Copy + Send + Sync + Default + 'static
335{
336    /// The MRC mode constant for this voxel type
337    const MODE: Mode;
338}
339
340impl Voxel for i8 {
341    const MODE: Mode = Mode::Int8;
342}
343
344impl Voxel for i16 {
345    const MODE: Mode = Mode::Int16;
346}
347
348impl Voxel for f32 {
349    const MODE: Mode = Mode::Float32;
350}
351
352impl Voxel for Int16Complex {
353    const MODE: Mode = Mode::Int16Complex;
354}
355
356impl Voxel for Float32Complex {
357    const MODE: Mode = Mode::Float32Complex;
358}
359
360impl Voxel for u16 {
361    const MODE: Mode = Mode::Uint16;
362}
363
364#[cfg(feature = "f16")]
365impl Voxel for crate::f16 {
366    const MODE: Mode = Mode::Float16;
367}