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}