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. Handles byte-order, type conversion, and compression so you can focus on your science. SIMD-accelerated, mmap-enabled.

§Basic usage

These APIs are all you need for everyday work. They return typed data (zero-copy borrowed views) and auto-detect compression, endianness, and the file’s voxel mode.

§Quick start

use mrc::{read_as, write_as};

// Read an entire volume in one call
let (header, data): (_, Vec<f32>) = read_as("density.mrc")?;
println!("{}×{}×{} volume, {} voxels",
    header.nx, header.ny, header.nz, data.len());

// Write a volume in one call
write_as("output.mrc", &data, [512, 512, 256])?;

§Reading data

Open any MRC file — compression, byte order, and voxel type are detected automatically. No need to specify gzip, endianness, or mode.

use mrc::{open, Reader};

let reader = open("tilt_series.mrc")?;
println!("{}×{}×{} voxels, mode {:?}",
    reader.shape().nx, reader.shape().ny, reader.shape().nz,
    reader.mode());

Then choose an iteration method. Each returns DataBlock chunks whose DataView variant is determined by the file’s mode at runtime — no compile-time guessing, no ModeMismatch errors:

MethodReturnsUse case
slicesone Z-plane at a time2D processing per slice
slabsk Z-planesBatch processing
tilesarbitrary 3D blocksLocal analysis
subregionsingle block by coordinateRandom access
read_volumeentire volumeFull-volume processing
volumessub-volumesVolume stacks (ISPG 401+)
for slice in reader.slices() {
    let block = slice?;
    match block.data() {
        mrc::DataView::Float32(data) => { /* process f32 slice */ }
        mrc::DataView::Int16(data)   => { /* process i16 slice */ }
        _ => {} /* other modes handled transparently */
    }
}

Or use convert::<f32>() — the fire-and-forget option that reads any mode as f32:

for slice in reader.convert::<f32>().slices() {
    let block = slice?;
    println!("z={}: {} voxels", block.offset[2], block.data.len());
}
// Full volume in one call:
let block = reader.convert::<f32>().read_volume()?;

The same iterators — slabs, tiles, subregion — work with convert::<f32>() too, as do with_complex_strategy and with_m0_interpretation.

§Writing data

Use create() to get a WriterBuilder, set shape and mode, then call finish. Write data block by block — each write_data_block encodes and writes immediately (streaming, no intermediate buffering):

use mrc::{create, DataBlock, OwnedData};

let mut writer = create("output.mrc")
    .shape([256, 256, 128])
    .mode::<f32>()
    .finish()?;

let block = DataBlock::Owned {
    offset: [0, 0, 0],
    shape: [256, 256, 1],
    data: OwnedData::Float32(vec![0.0f32; 256 * 256]),
};
writer.write_data_block(&block)?;
writer.update_header_stats()?;
writer.finalize()?;

For a single call, use write_as() or Writer::set_data:

use mrc::write_as;
let data = vec![0.0f32; 256 * 256 * 128];
write_as("output.mrc", &data, [256, 256, 128])?;

The writer lifecycle:

  1. Write blocks with write_data_block or write_block_as for auto-conversion.
  2. Call update_header_stats to fill in dmin/dmax/dmean/rms.
  3. Require finalize — rewrites the header with final metadata.

§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.

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

When you don’t know the mode ahead of time, use convert::<f32>() which converts any mode to f32.

§Compression auto-detection

Reader::open reads the first two bytes to detect compression:

Magic bytesFormat
\x1f\x8bGzip
BZBzip2
anything elsePlain

Plain files use mmap (zero-copy) or buffered I/O. Compressed files are fully decompressed into memory on open (capped at 256 GiB). Use Reader::open_gzip_with_limit or [Reader::open_bzip2_with_limit] for a custom limit.

§Large files

Reader::open automatically uses memory-mapped I/O (requires the mmap feature). Same iterator API, zero-copy DataBlock views, OS-managed paging. For very large compressed files, decompress with gunzip/bunzip2 first, then use plain Reader::open for mmap.

§Reading from memory / streams

When data is already in memory (e.g. from a camera readout or network stream), use Reader::from_reader or Reader::from_bytes:

use mrc::Reader;
use std::io::Cursor;

let bytes = std::fs::read("density.mrc")?;
let reader = Reader::from_reader(Cursor::new(bytes))?;

§Troubleshooting

ErrorLikely causeWhat to try
InvalidHeaderNot an MRC filemrc validate file.mrc or try Reader::open_permissive
FileSizeMismatchTruncated / trailing garbageRe-download or run mrc validate
ModeMismatchBlock type != file modeUse Writer::write_block_as for auto-conversion
BoundsErrorBlock outside volumeCheck offset + shape ≤ dimensions
UnsupportedModeMode needs f16 featureEnable f16 or convert with another tool

§Advanced API

These APIs give you lower-level access to raw bytes, headers, extended metadata, and validation. They are intended for tools, pipelines, and developers who need more control than the basic iterators provide.

§Raw byte access

⚠️ The returned bytes are raw on-disk bytes — file byte order, no endian correction, no type conversion. Use reader.endian() and reader.mode() to interpret them correctly.

For typed zero-copy access, use subregion / slices for the basic API, or convert::<f32>() for fire-and-forget conversion.

MethodReturnsCopy cost
raw_bytes&[u8] — whole data regionzero-copy
read_block_bytes_cow[Cow<[u8]>] — sub-blockzero-copy for contiguous XY slabs
read_block_bytesVec<u8> — sub-blockalways copies

Contiguous = offset [0, y, z], shape [nx, ny, sz]. Any sub-XY offset or shape forces a row-by-row gather into owned memory.

⚠️ read_block_bytes always returns an owned Vec, even for contiguous blocks. For large volumes this causes a full copy of the block. Use read_block_bytes_cow when you want to avoid the allocation, or use subregion / slices for typed zero-copy access.

use std::borrow::Cow;

// Zero-copy: borrows from mmap for contiguous blocks
let cow: Cow<[u8]> = reader.read_block_bytes_cow([0, 0, 0], [256, 256, 64])?;

// Always owned: allocates + copies even for contiguous blocks
let bytes: Vec<u8> = reader.read_block_bytes([0, 0, 0], [256, 256, 64])?;

§Permissive mode / quirky files

Common microscope quirks (NVERSION left at 0, "MAP\0" instead of "MAP ") are handled transparently by open().

For severely non-standard files, use Reader::open_permissive which turns non-critical header issues into warnings instead of hard errors, and allows opening files that are shorter than the header declares. Use is_truncated to detect truncated data:

let (reader, warnings) = mrc::Reader::open_permissive("legacy.mrc")?;
for w in &warnings { eprintln!("note: {w}"); }
if reader.is_truncated() {
    eprintln!("warning: file is incomplete");
}

Permissive variants Reader::from_reader_permissive and Reader::from_bytes_permissive are also available for in-memory data.

§Special-mode reads (Packed4Bit and Mode 0)

These modes have no Voxel implementation — there is no single Rust type that maps to them safely at compile time — so dedicated methods read directly as u8 or f32:

§Headers

The Header struct mirrors the 1024-byte MRC-2014 fixed header. Every field is a typed public member.

use mrc::Header;
let h = Header::new();
assert_eq!(h.map, *b"MAP ");

For fluent construction, use HeaderBuilder:

use mrc::HeaderBuilder;
let header = HeaderBuilder::new()
    .shape([512, 512, 256])
    .mode::<f32>()
    .cell_lengths(1.0, 1.0, 1.0)
    .build()?;

Three validation levels:

Volume type helpers (configure ispg and mz):

let mut h = Header::new();
h.nx = 64; h.ny = 64; h.nz = 120;
h.mx = 64; h.my = 64; h.mz = 30;
h.set_volume_stack(30);
assert!(h.is_volume_stack());
assert_eq!(h.logical_shape(), [4, 30, 64, 64]);

Key computed properties: voxel_size(), sampling(), cell_lengths(), cell_angles(), cell_volume(), density_stats(), logical_shape(), exttyp(), get_labels(), detect_endian(), is_standard_map(), decode_from_bytes(), encode_to_bytes().

§Extended headers

Many MRC files carry metadata after the fixed header identified by a 4-byte exttyp field. Use parse_extended_header for auto-detection:

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::None => println!("No recognized extended header"),
    _ => {}
}

Typed convenience methods: fei1_metadata, fei2_metadata, ccp4_records, mrco_records, seri_records, agar_records, imod_metadata.

§Validation

validate_full checks header, file size, endianness, data statistics, and NaN/Inf. Returns a ValidationReport:

use mrc::{validate_full, Severity};

let report = mrc::validate_full("protein.mrc", false)?;
if !report.is_valid() {
    for issue in &report.issues {
        if issue.severity == Severity::Error {
            eprintln!("[{}] {}", issue.category, issue.message);
        }
    }
}

Use validate_reader if the file is already open.

§Error types

Fallible functions return Result<T, Error>. Match on variants:

use mrc::Error;

fn describe(err: &Error) -> &str {
    match err {
        Error::Io(_) => "I/O failure",
        Error::InvalidHeader => "not a valid MRC file",
        Error::ModeMismatch { .. } => "wrong voxel type for this file",
        Error::BoundsError { .. } => "block outside volume",
        Error::FileSizeMismatch { .. } => "file truncated or has trailing data",
        _ => "other",
    }
}

HeaderValidationError gives fine-grained header diagnostics.

§Feature flags

FeatureDescriptionDefault
mmapMemory-mapped readers and writers
f16Half-precision float via the half crate
simdAVX2 / NEON acceleration
parallelParallel decode/convert/encode via rayon
gzipGzip-compressed I/O
bzip2Bzip2-compressed I/O
ndarrayReturn volumes as ndarray::Array3<T>
serdeSerialize/Deserialize support
use ndarray::Array3;
let arr: Array3<f32> = reader.convert::<f32>().to_ndarray()?;
println!("{}×{}×{} array", arr.shape()[0], arr.shape()[1], arr.shape()[2]);

§Endianness

MRC files encode byte order via a 4-byte MACHST stamp at file offset 212. FileEndian represents the detected byte order:

use mrc::FileEndian;
let stamp: [u8; 4] = [0x44, 0x44, 0x00, 0x00]; // little-endian
assert_eq!(FileEndian::from_machst(&stamp), FileEndian::LittleEndian);

New files are always little-endian. The crate has a fallback: if the MODE field is invalid under the detected endianness, the opposite byte order is tried (handles files with a wrong MACHST stamp).

§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.

Structs§

AgarRecord
Agard extended header record.
Ccp4Record
A single CCP4 symmetry record — an 80-character text line containing space group symmetry operators.
ConvertReader
Auto-conversion wrapper returned by Reader::convert. A reader wrapper that auto-converts all voxel data to type T.
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.
MrcoRecord
A legacy MRCO extended header record.
Reader
Consolidated MRC reader with automatic mmap/buffered backend selection. MRC file reader with automatic backend selection.
SeriRecord
SerialEM extended header record.
ValidationIssue
Validation report types and comprehensive file checker. A single validation issue found during file inspection.
ValidationReport
Validation report types and comprehensive file checker. Structured result of a full MRC file validation.
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.
CompressionLevel
Compression level for compressed MRC writers.
DataBlock
A block of voxel data with a 3D offset and shape, returned by the default (non-convert) reader methods.
DataView
Borrowed typed slice into an MRC volume’s raw data.
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.
OwnedData
Owned typed data — returned when a copy is unavoidable (sub-block scatter/gather, endian mismatch).
Severity
Validation report types and comprehensive file checker. Severity of a validation issue.

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§

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 typed records.
parse_ccp4_records
Parse extended header bytes as typed 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 typed records.
parse_seri_records
Parse extended header bytes as typed records.
read_as
Read an entire MRC volume into a Vec<T> with auto-mode detection.
reinterpret_m0
Reinterpret Mode 0 (8-bit) data as signed or unsigned and convert to f32.
validate_full
Validation report types and comprehensive file checker. Run comprehensive validation on an MRC file.
validate_reader
Validation report types and comprehensive file checker. Run comprehensive validation on an already-opened Reader.
write_as
Write an entire MRC volume from a &[T] with a single call.