Skip to main content

Crate mrc

Crate mrc 

Source
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 time
  • slabs — batches of k Z-planes
  • tiles — arbitrary 3D blocks
  • subregion — a single block by coordinate

Trait imports (optional): Iterator and conversion methods are available as inherent methods on Reader and MmapReader without any import. The ReaderMethods and ConvertMethods traits 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:

  1. Write blocks with write_block. The type T matches the file’s mode — a compile-time check that prevents accidentally treating bytes as the wrong kind of number. Use write_block_as for automatic conversion (e.g. write f32 data to an Int16 or Float16 file).
  2. Optionally call update_header_stats to fill in dmin/dmax/dmean/rms.
  3. Finalize with finalize to rewrite the header with final metadata. Required — without it the header is stale.

Four backends through the same builder:

BackendBuilder methodBest for
Writerfinish()General use, writes straight to disk
MmapWriterfinish_mmap()Very large files (mmap feature)
GzipWriterfinish_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.

ModeRust typeTypical use
Int8 (0)i8Binary masks
Int16 (1)i16Raw cryo-EM density
Float32 (2)f32Processed / reconstructed density
Int16Complex (3)Int16ComplexComplex data (i16 real + i16 imag)
Float32Complex (4)Float32ComplexComplex data (f32 real + f32 imag)
Uint16 (6)u16Segmentation labels
Float16 (12)f16Half-precision storage (feature f16)
Packed4Bit (101)u8 via slices_u84-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:

§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

FeatureDescriptionDefault
mmapMemory-mapped readers and writers
f16Half-precision float via the half crate
simdAVX2 / NEON acceleration for integer→f32, f16↔f32, byte-swap, stats
parallelParallel encoding via rayon
gzipGzip-compressed I/O
bzip2Bzip2-compressed I/O
ndarrayReturn volumes as ndarray::Array3<T> via to_ndarray()
serdeSerialize/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 written
  • InvalidHeader — not a valid MRC file
  • ModeMismatch — calling slices::<f32>() on an Int16 file; use convert::<f32>() instead
  • BoundsError — read or write outside the volume
  • FileSizeMismatch — 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 bytesFormat
\x1f\x8bGzip
BZBzip2
anything elsePlain

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 gunzip or bunzip2 first, then use MmapReader for 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

ErrorLikely causeWhat to try
InvalidHeaderNot an MRC file, or header corruptionRun mrc-validate file.mrc; try open_permissive
FileSizeMismatchFile truncated or has trailing garbageRe-download or check mrc-validate output
ModeMismatchUsing slices::<f32>() on an Int16 fileUse convert::<f32>() — auto-converts any mode
BoundsErrorBlock outside volumeCheck offset + shape against dimensions
UnsupportedModeUnrecognised mode, or mode needs the f16 featureEnable f16 feature or convert with another tool
Io errorFile permissions, filesystem issueCheck the file path and permissions
Values look wrongEndianness mismatchThe 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§

AgarRecord
Agard extended header record.
Ccp4Record
A single CCP4 symmetry record — an 80-character text line containing space group symmetry operators.
Fei1Metadata
Common FEI1 metadata fields.
Fei2Metadata
FEI2 metadata extends FEI1 with additional v2 fields.
Float32Complex
A complex number with 32-bit float real and imaginary components.
Header
Mirror of the 1024-byte MRC-2014 fixed header.
HeaderBuilder
Builder for constructing validated MRC headers.
ImodInfo
IMOD-specific metadata parsed from the extra block (bytes 56-63).
ImodMetadata
IMOD-specific metadata parsed from the main header’s extra bytes.
Int16Complex
A complex number with 16-bit signed integer real and imaginary components.
MmapReader
Memory-mapped MRC reader (requires mmap feature). Memory-mapped MRC file reader.
MmapWriter
Memory-mapped MRC writer (requires mmap feature). Memory-mapped MRC file writer.
MrcoRecord
A legacy MRCO extended header record.
Reader
Buffered MRC reader with lazy slice/slab iterators. In-memory buffered MRC file reader.
RegionIter
Lazy iterator over MRC voxel blocks. Lazy iterator over a volume as a sequence of VoxelBlocks.
SeriRecord
SerialEM extended header record.
SlabStepper
Stepping strategies for RegionIter. Step k contiguous Z-slices at a time ([nx, ny, k]).
SliceStepper
Stepping strategies for RegionIter. Step one Z-plane at a time ([nx, ny, 1]).
TileStepper
Stepping strategies for RegionIter. Step arbitrary 3D tiles across a volume.
VolumeShape
Volume geometry in voxels.
VoxelBlock
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.
WriterBuilder
MRC file writer and its builder. Builder for configuring and creating a new MRC file writer.
f16
Half-precision floating point type (requires f16 feature). A 16-bit floating point type implementing the IEEE 754-2008 standard binary16 a.k.a “half” format.

Enums§

ComplexToRealStrategy
Strategy for converting complex numbers to real values.
Error
The top-level error type for MRC I/O operations.
ExtHeaderData
Parsed extended header data, dispatched by ExtHeaderType.
ExtHeaderType
Known extended header types identified by the 4-byte EXTTYP field.
FileEndian
Endianness of MRC file data. Endianness of MRC file data.
HeaderValidationError
Errors that can occur during detailed header validation.
ImodImageType
IMOD image type classification from the idtype field.
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§

ConvertMethods
Auto-conversion API for MRC readers.
ReaderMethods
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 u8 slice to u16 for writing as Mode 6 (Uint16).
convert_u16_slice_to_u8
Narrow a u16 slice to u8, returning Err if 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 extra bytes.
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§

GzipWriter
Gzip-compressed MRC writer (requires gzip feature). Gzip-compressed MRC file writer.