Expand description
Read and write MRC-2014 files — the standard in cryo-EM and structural biology.
This crate handles file I/O, byte-order detection, and type-safe data access so you can focus on your science. It’s fast (SIMD, parallel encoding) and works with plain, gzip, and bzip2 files out of the box.
See the README for installation instructions, CLI tools, and the project roadmap.
§Quick example
use mrc::{open, create, VoxelBlock};
// Read — auto-detects gzip/bzip2 compression
let reader = open("density.mrc")?;
for slice in reader.convert::<f32>().slices() {
let _block = slice?; // VoxelBlock<f32>
}
// Write
let mut writer = create("output.mrc")
.shape([512, 512, 256])
.mode::<f32>()
.finish()?;
writer.write_block(&VoxelBlock::new(
[0, 0, 0], [512, 512, 1],
vec![0.0f32; 512 * 512],
)?)?;
writer.finalize()?;§Reading files
Open any MRC file with open() or Reader::open. Compression is
detected from magic bytes — no need to hint gzip or bzip2.
use mrc::Reader;
let reader = Reader::open("tilt_series.mrc")?;
println!("{}×{}×{} voxels, mode {:?}",
reader.shape().nx, reader.shape().ny, reader.shape().nz,
reader.mode());Then pick an iteration method:
slices— one Z-plane at a timeslabs— batches ofkZ-planestiles— arbitrary 3D blockssubregion— a single block by coordinate
Trait imports (optional): Iterator and conversion methods are available as inherent methods on
ReaderandMmapReaderwithout any import. TheReaderMethodsandConvertMethodstraits are also re-exported for advanced use (e.g. generic code over reader types).
For automatic mode conversion, use convert:
for slice in reader.convert::<f32>().slices() {
let block = slice?;
println!("slice {} mean density: {:.2}",
block.offset[2],
block.data.iter().sum::<f32>() / block.data.len() as f32);
}Or read the full volume in one call:
let block = reader.convert::<f32>().read_volume()?;
println!("read {} voxels", block.data.len());When the ndarray feature is enabled, get numpy-like multidimensional access:
let arr = reader.to_ndarray::<f32>()?;
// arr is ndarray::Array3<f32> with shape [nz, ny, nx]
let center = arr[[arr.shape()[0] / 2, arr.shape()[1] / 2, arr.shape()[2] / 2]];§Large files
When the file does not fit in RAM, use MmapReader (requires the
mmap feature). Same iterator API, zero-copy slab_as,
OS-managed paging.
§Quirky files
Common microscope quirks (NVERSION left at 0, "MAP\0" instead of "MAP ")
are handled transparently by open() — no special flags needed.
For esoteric or severely non-standard files, use
Reader::open_permissive which turns non-critical header issues into
warnings instead of hard errors:
let (reader, warnings) = Reader::open_permissive("legacy.mrc")?;
for w in &warnings { eprintln!("note: {w}"); }§Writing files
Use create() to get a WriterBuilder, set the shape and voxel type,
then call finish.
use mrc::create;
let mut writer = create("output.mrc")
.shape([256, 256, 128])
.mode::<f32>()
.finish()?;The lifecycle:
- Write blocks with
write_block. The typeTmatches the file’s mode — a compile-time check that prevents accidentally treating bytes as the wrong kind of number. Usewrite_block_asfor automatic conversion (e.g. writef32data to an Int16 or Float16 file). - Optionally call
update_header_statsto fill indmin/dmax/dmean/rms. - Finalize with
finalizeto rewrite the header with final metadata. Required — without it the header is stale.
Four backends through the same builder:
| Backend | Builder method | Best for |
|---|---|---|
Writer | finish() | General use, writes straight to disk |
MmapWriter | finish_mmap() | Very large files (mmap feature) |
GzipWriter | finish_gzip() | Compressed output (gzip feature) |
[Bzip2Writer] | finish_bzip2() | Compressed output (bzip2 feature) |
§Data modes
MRC files encode voxels in one of several numeric modes. Mode
represents them at runtime; Voxel ties each Rust type to its mode
at compile time, catching mismatches before any data flows.
| Mode | Rust type | Typical use |
|---|---|---|
Int8 (0) | i8 | Binary masks |
Int16 (1) | i16 | Raw cryo-EM density |
Float32 (2) | f32 | Processed / reconstructed density |
Int16Complex (3) | Int16Complex | Complex data (i16 real + i16 imag) |
Float32Complex (4) | Float32Complex | Complex data (f32 real + f32 imag) |
Uint16 (6) | u16 | Segmentation labels |
Float16 (12) | f16 | Half-precision storage (feature f16) |
Packed4Bit (101) | u8 via slices_u8 | 4-bit packed data; no Voxel impl |
Packed 4-bit data is handled transparently by the unified API:
convert::<f32>() unpacks nibbles to f32,
slices_u8 / slabs_u8 unpack
to u8 (0–15), and write_u4_block packs
u8 values back.
When you don’t know the mode ahead of time, use
convert::<f32>() which converts any mode to f32.
§Headers
The Header struct mirrors the 1024-byte MRC-2014 fixed header.
Every field is a typed public member — dimensions, cell parameters,
axis mapping, density statistics, text labels, and more.
use mrc::Header;
let h = Header::new();
assert_eq!(h.map, *b"MAP ");For fluent construction with validation, use HeaderBuilder:
use mrc::HeaderBuilder;
let header = HeaderBuilder::new()
.shape([512, 512, 256])
.mode::<f32>()
.build()?;Three validation levels:
validate— quick yes / novalidate_detailed— tells you exactly what is wrong viaHeaderValidationErrorvalidate_permissive— warnings for non-critical issues
§Convenience API
The Header provides computed properties for common queries:
use mrc::Header;
let h = Header::new();
let vol = h.cell_volume(); // unit cell volume in ų
let (dmin, dmax, dmean, rms) = h.density_stats();
let sampling = h.sampling(); // [mx, my, mz]
let label = h.label_at(0); // first label, or None
assert!(h.is_standard_map()); // MAP field is "MAP "§Manual header parsing
Decode a raw 1024-byte header block with automatic endianness detection:
use mrc::Header;
let raw = [0u8; 1024];
// ... fill raw bytes from file ...
let header = Header::decode_from_bytes(&raw);When the MACHST byte-order stamp is wrong (common in some EPU files), the decoder tries the opposite endianness automatically:
let (header, warning) = Header::decode_from_bytes_with_info(&raw);
if let Some(w) = warning {
eprintln!("byte-order fallback used: {w}");
}§Extended headers
Many MRC files carry additional metadata after the 1024-byte fixed header
in an extended header region. The type is identified by the 4-byte
exttyp field in the header’s extra[8..12].
The ExtHeaderType enum identifies the format without parsing:
use mrc::{Header, ExtHeaderType};
let header = Header::new();
match ExtHeaderType::from_header(&header) {
ExtHeaderType::Fei1 => println!("FEI Type 1"),
ExtHeaderType::Ccp4 => println!("CCP4"),
ExtHeaderType::Unknown(id) => {
println!("Unknown: {:?}", std::str::from_utf8(&id));
}
_ => {}
}Instead of calling individual parser functions, use the auto-dispatch method on any open reader:
use mrc::ExtHeaderData;
match reader.parse_extended_header() {
ExtHeaderData::Fei1(records) => {
println!("FEI1 tilt series ({} records)", records.len());
for r in &records {
println!(" tilt {:.1}°, defocus {:.1} µm",
r.alpha_tilt, r.defocus);
}
}
ExtHeaderData::Ccp4(records) => {
println!("CCP4 symmetry ({} records)", records.len());
}
ExtHeaderData::Seri(records) => {
println!(" first tilt: {:.1}°", records[0].alpha_tilt);
}
ExtHeaderData::None => println!("No recognised extended header"),
_ => {}
}Typed convenience methods give direct access without pattern matching:
if let Some(records) = reader.fei1_metadata() {
println!("{} FEI1 records", records.len());
}
if let Some(imod) = reader.imod_metadata() {
println!("IMOD type {:?}, tilt increment {:.1}°",
imod.image_type, imod.tilt_increment);
}Available: fei1_metadata,
fei2_metadata,
ccp4_records,
mrco_records,
seri_records,
agar_records,
imod_metadata.
§Feature flags
| Feature | Description | Default |
|---|---|---|
mmap | Memory-mapped readers and writers | ✅ |
f16 | Half-precision float via the half crate | ✅ |
simd | AVX2 / NEON acceleration for integer→f32, f16↔f32, byte-swap, stats | ✅ |
parallel | Parallel encoding via rayon | ✅ |
gzip | Gzip-compressed I/O | ✅ |
bzip2 | Bzip2-compressed I/O | ❌ |
ndarray | Return volumes as ndarray::Array3<T> via to_ndarray() | ❌ |
serde | Serialize/Deserialize support via serde | ❌ |
§Advanced topics
§Error handling
Fallible functions return Result<T, Error>. The errors you will
actually hit in practice:
Io— the file could not be read or writtenInvalidHeader— not a valid MRC fileModeMismatch— callingslices::<f32>()on an Int16 file; useconvert::<f32>()insteadBoundsError— read or write outside the volumeFileSizeMismatch— file truncated or has trailing garbage
HeaderValidationError gives fine-grained diagnostics for header
problems (bad dimensions, wrong MAP field, invalid NVERSION …).
§Endianness
MRC files encode byte order via a 4-byte MACHST stamp. FileEndian
handles detection and conversion automatically. New files are always
little-endian, matching modern hardware and the Python mrcfile library.
The crate has a fallback: if the MODE field is invalid under the detected endianness, the opposite byte order is tried. This handles files with a wrong MACHST stamp but correct data.
§Compression auto-detection
Reader::open reads the first two bytes of the file:
| Magic bytes | Format |
|---|---|
\x1f\x8b | Gzip |
BZ | Bzip2 |
| anything else | Plain |
Plain MRC files are memory-mapped or buffered directly. Compressed files
are fully decompressed into memory on open, with a hard cap of
DEFAULT_MAX_DECOMPRESSED_BYTES (256 GiB) to prevent bombs.
Use Reader::open_gzip_with_limit or
[Reader::open_bzip2_with_limit] for a custom limit.
Large compressed files: If the uncompressed data exceeds available RAM, decompress with
gunziporbunzip2first, then useMmapReaderfor zero-copy access — the OS pages data on demand without loading the whole file into memory.
§File validation
validate_full runs comprehensive checks
on a file — header, size, endianness, data statistics (1 % tolerance),
and NaN / Inf scanning. Returns a
ValidationReport with categorised issues.
If you already have an open Reader, use
validate_reader to avoid re-opening
the file.
§Real-world workflows
§1. Process a tilt series
A common cryo-EM workflow: open a tilt series, read the FEI metadata, then iterate over slices:
use mrc::{open, parse_fei1_records};
let reader = open("tiltseries.mrc")?;
println!("{}×{}×{} voxels, mode {:?}",
reader.shape().nx, reader.shape().ny, reader.shape().nz,
reader.mode());
// Read FEI extended header metadata
if let Some(records) = parse_fei1_records(reader.ext_header_bytes()) {
for (i, r) in records.iter().enumerate() {
println!("tilt {i}: α={:.1}°, defocus={:.1} µm",
r.alpha_tilt, r.defocus);
}
}
// Process each slice
for slice in reader.convert::<f32>().slices() {
let block = slice?;
// block.data: Vec<f32> — ready for filtering, CTF correction, etc.
}If a file fails to open, try open_permissive
for lenient header handling, or validate_full
to diagnose the issue.
§2. Write a processed map
Always call finalize — without it the header is
stale and density statistics will be wrong (tools display wrong contrast).
use mrc::create;
let mut writer = create("reconstructed.mrc")
.shape([512, 512, 256])
.mode::<f32>()
.finish()?;
for z in 0..256 {
let slice = vec![0.0f32; 512 * 512];
writer.write_block(&mrc::VoxelBlock::new(
[0, 0, z], [512, 512, 1], slice,
)?)?;
}
writer.update_header_stats()?;
writer.finalize()?;§3. Read subtomogram averages from a volume stack
Volume stacks (ISPG 401–630) pack multiple sub-volumes into one file,
each mz slices thick. Use volumes to iterate:
for volume in reader.volumes::<f32>()? {
let vol = volume?;
println!("sub-volume at z={} ({}×{}×{} voxels)",
vol.offset[2], vol.shape[0], vol.shape[1], vol.shape[2]);
}§Troubleshooting
| Error | Likely cause | What to try |
|---|---|---|
InvalidHeader | Not an MRC file, or header corruption | Run mrc-validate file.mrc; try open_permissive |
FileSizeMismatch | File truncated or has trailing garbage | Re-download or check mrc-validate output |
ModeMismatch | Using slices::<f32>() on an Int16 file | Use convert::<f32>() — auto-converts any mode |
BoundsError | Block outside volume | Check offset + shape against dimensions |
UnsupportedMode | Unrecognised mode, or mode needs the f16 feature | Enable f16 feature or convert with another tool |
Io error | File permissions, filesystem issue | Check the file path and permissions |
| Values look wrong | Endianness mismatch | The endianness fallback handles most cases; try mrc-validate |
§Philosophy
This crate does one thing — read and write MRC files. It does no array
arithmetic, image processing, or type conversion beyond MRC-specific
shortcuts (convert::<f32>(), slices_mode0, slices_u8).
Leave those to crates like ndarray, or your own code.
Modules§
- validate
- MRC file validation infrastructure.
Structs§
- Agar
Record - Agard extended header record.
- Ccp4
Record - A single CCP4 symmetry record — an 80-character text line containing space group symmetry operators.
- Fei1
Metadata - Common FEI1 metadata fields.
- Fei2
Metadata - FEI2 metadata extends FEI1 with additional v2 fields.
- Float32
Complex - A complex number with 32-bit float real and imaginary components.
- Header
- Mirror of the 1024-byte MRC-2014 fixed header.
- Header
Builder - Builder for constructing validated MRC headers.
- Imod
Info - IMOD-specific metadata parsed from the
extrablock (bytes 56-63). - Imod
Metadata - IMOD-specific metadata parsed from the main header’s
extrabytes. - Int16
Complex - A complex number with 16-bit signed integer real and imaginary components.
- Mmap
Reader - Memory-mapped MRC reader (requires
mmapfeature). Memory-mapped MRC file reader. - Mmap
Writer - Memory-mapped MRC writer (requires
mmapfeature). Memory-mapped MRC file writer. - Mrco
Record - A legacy MRCO extended header record.
- Reader
- Buffered MRC reader with lazy slice/slab iterators. In-memory buffered MRC file reader.
- Region
Iter - Lazy iterator over MRC voxel blocks.
Lazy iterator over a volume as a sequence of
VoxelBlocks. - Seri
Record - SerialEM extended header record.
- Slab
Stepper - Stepping strategies for
RegionIter. Stepkcontiguous Z-slices at a time ([nx, ny, k]). - Slice
Stepper - Stepping strategies for
RegionIter. Step one Z-plane at a time ([nx, ny, 1]). - Tile
Stepper - Stepping strategies for
RegionIter. Step arbitrary 3D tiles across a volume. - Volume
Shape - Volume geometry in voxels.
- Voxel
Block - A contiguous chunk of voxel data with a 3D offset and shape.
- Writer
- MRC file writer and its builder. MRC file writer using standard file I/O.
- Writer
Builder - MRC file writer and its builder. Builder for configuring and creating a new MRC file writer.
- f16
- Half-precision floating point type (requires
f16feature). A 16-bit floating point type implementing the IEEE 754-2008 standardbinary16a.k.a “half” format.
Enums§
- Complex
ToReal Strategy - Strategy for converting complex numbers to real values.
- Error
- The top-level error type for MRC I/O operations.
- ExtHeader
Data - Parsed extended header data, dispatched by
ExtHeaderType. - ExtHeader
Type - Known extended header types identified by the 4-byte EXTTYP field.
- File
Endian - Endianness of MRC file data. Endianness of MRC file data.
- Header
Validation Error - Errors that can occur during detailed header validation.
- Imod
Image Type - IMOD image type classification from the
idtypefield. - M0Interpretation
- Interpretation of Mode 0 (8-bit) data for legacy files.
- Mode
- MRC data mode defining the on-disk representation of voxel values.
Constants§
- AGAR_
RECORD_ SIZE - Size of a single AGAR record, in bytes.
- CCP4_
RECORD_ SIZE - Size of a single CCP4 symmetry record, in bytes.
- DEFAULT_
MAX_ DECOMPRESSED_ BYTES - Default decompression safety limit for gzip/bzip2 files (256 GiB).
- FEI1_
RECORD_ SIZE - Size of a single FEI1 metadata record, in bytes.
- FEI2_
RECORD_ SIZE - Size of a single FEI2 metadata record, in bytes.
- MRCO_
RECORD_ SIZE - Size of a single MRCO record, in bytes.
- SERI_
RECORD_ SIZE - Size of a single SERI (SerialEM) record, in bytes.
Traits§
- Convert
Methods - Auto-conversion API for MRC readers.
- Reader
Methods - Universal iterator / read API for all MRC reader types.
- Voxel
- Trait for MRC voxel types with compile-time mode tracking.
Functions§
- convert_
u8_ slice_ to_ u16 - Widen a
u8slice tou16for writing as Mode 6 (Uint16). - convert_
u16_ slice_ to_ u8 - Narrow a
u16slice tou8, returningErrif any value exceeds 255. - create
- Create a new MRC file for writing.
- open
- Open an MRC file for reading, auto-detecting gzip or bzip2 compression.
- parse_
agar_ records - Parse extended header bytes as Agard records.
- parse_
ccp4_ records - Parse extended header bytes as CCP4 symmetry records.
- parse_
fei1_ records - Parse a raw extended header byte slice as a vector of FEI1 records.
- parse_
fei2_ records - Parse a raw extended header byte slice as a vector of FEI2 records.
- parse_
imod_ metadata - Parse IMOD metadata from the main header’s
extrabytes. - parse_
mrco_ records - Parse extended header bytes as MRCO records.
- parse_
seri_ records - Parse extended header bytes as SerialEM records.
- reinterpret_
m0 - Reinterpret Mode 0 (8-bit) data as signed or unsigned and convert to
f32.
Type Aliases§
- Gzip
Writer - Gzip-compressed MRC writer (requires
gzipfeature). Gzip-compressed MRC file writer.