mrc
Type-safe MRC-2014 reader/writer for Rust — SIMD-accelerated, mmap-enabled, with full cryo-EM metadata support.
A type-safe Rust encoder/decoder for MRC files — the standard format in cryo-electron microscopy and structural biology. Automatically handles endianness, type conversion, and compression with SIMD acceleration, exposing a powerful yet intuitive and friendly read/write API so you can focus on your data.
Quick Start
One line to read any MRC file. One line to write one.
use ;
// Read — auto-detects gzip/bzip2, handles quirky headers
let : = read_as?;
println!;
// Write — type-safe, single call
write_as?;
Power & Simplicity at a Glance
The mrc API is designed so that common operations are one-liners and complex workflows read naturally.
| What you want | How you write it |
|---|---|
| Open any MRC file (plain / gzip / bzip2) | Reader::open("file.mrc")? |
| One-shot read (open + read_volume) | let (h, d): (_, Vec<f32>) = read_as("file.mrc")?; |
| One-shot write (create + write + finalize) | write_as("out.mrc", &data, [512, 512, 256])?; |
Read the whole volume as f32 |
reader.convert::<f32>().read_volume()? |
| Read a sub-region | reader.subregion([x, y, z], [sx, sy, sz])? |
| Iterate Z-slices | reader.slices() → for slice in ... |
| Iterate sub-volumes in a stack | reader.volumes()? → for vol in ... |
| Create a new file | create("out.mrc").shape([512, 512, 256]).mode::<f32>().finish()? |
| Write with auto-conversion (f32 → i16) | writer.write_block_as(&f32_block)? |
| Parse tilt-series metadata | reader.fei1_metadata() or reader.parse_extended_header() |
| Validate a file | validate_full("file.mrc", false)? |
| Open a quirky file | Reader::open_permissive("broken.mrc")? |
No trait imports required. Every one of these is an inherent method — no use SomeTrait needed.
Installation
[]
= "0.8"
Enable optional features in Cargo.toml:
= { = "0.7", = ["ndarray", "serde", "bzip2"] }
For the mrc-cli binary, install the companion crate:
| Feature | Default | What it adds |
|---|---|---|
mmap |
✅ | Memory-mapped I/O (auto-selected for large files) |
f16 |
✅ | Half-precision float (half::f16) support |
simd |
✅ | AVX2/NEON acceleration |
parallel |
✅ | Parallel decode/convert/encode via rayon — transparent for any block ≥512³ |
gzip |
✅ | Gzip auto-detection and compressed writer |
bzip2 |
❌ | Bzip2 auto-detection and compressed writer |
ndarray |
❌ | Return volumes as ndarray::Array3<T> via to_ndarray() |
serde |
❌ | Serialize/Deserialize for all public types |
Quick Tour
See docs.rs/mrc for the full API documentation, runnable examples, and detailed guidance. The examples below are just a few highlights.
Reading — any file, any mode, any shape
use Reader;
// Open — auto-detects compression and byte order
let reader = open?;
println!;
// Check the file's mode at runtime and dispatch accordingly:
match reader.mode
// Or just use slices() and match DataView:
for slice in reader.slices
// Full volume in one call
let block = reader.read_volume?;
let Float32 = block.data else ;
println!;
// Any sub-region by coordinate
let block = reader.subregion?;
let Float32 = block.data else ;
Auto-conversion — read any MRC mode as f32
Don't care whether the file is Int8, Int16, Uint16, Float16, or even Packed4Bit?
Use convert::<f32>() and the crate handles the rest — or match on reader.mode()
to handle each type individually.
// Option A: auto-convert everything to f32 in one call
for slice in reader..slices
// Option B: use default reader methods and match on DataView
match reader.mode
// Or read the whole converted volume in one call
let block = reader..read_volume?;
// The same converter also supports slabs, tiles, subregion,
// with_complex_strategy, with_m0_interpretation, and to_ndarray().
Writing — type-safe, flexible, fast
use create;
// Create a Float32 file
let mut writer = create
.shape
.
.finish?;
// Write one slice at a time
for z in 0..256
// Or write with auto-conversion from any supported type
writer.write_block_as?;
// Parallel encoding is auto-selected for full XY slabs on file-backed writers
writer.update_header_stats?; // fills dmin/dmax/dmean/rms
writer.finalize?; // **required** — rewrites header
Writing compressed files
use ;
// Gzip-compressed output — same API, just finish_gzip()
let mut writer = create
.shape
.
.compression
.finish_gzip?;
let block = Owned ;
writer.write_data_block?;
writer.finalize?; // compresses & writes to disk
Memory-mapped I/O — zero-copy for large files
Files too large for RAM? Reader::open automatically uses memory-mapped I/O (requires mmap feature). The OS pages data on demand. The default reader methods return DataBlock views that borrow directly from the mapped memory.
let reader = open?;
// Default methods return DataBlock with zero-copy DataView
for slice in reader.slices
Reading Extended Metadata — one method call
use ExtHeaderData;
// Auto-detect and parse whatever extended header the file has
match reader.parse_extended_header
// Or use typed convenience methods directly
if let Some = reader.fei1_metadata
if let Some = reader.imod_metadata
Volume stacks — iterate sub-volumes
Volume stacks (ISPG 401–630) pack multiple sub-volumes in one file.
for result in reader.volumes?
Validation — catch issues early
use ;
let report = validate_full?;
if !report.is_valid
Working with quirky files
Common microscope quirks (NVERSION left at 0, "MAP\0" instead of "MAP ") are handled transparently by open(). For truly broken files, permissive mode turns non-critical errors into warnings:
let = open_permissive?;
if reader.is_truncated
for w in &warnings
Real-world workflow — the full pipeline
use ;
// 1. Open a tilt series from any microscope format
let reader = open?;
println!;
// 2. Read FEI metadata (or CCP4, SerialEM, Agard...)
if let Some = reader.fei1_metadata
// 3. Process each slice as f32 (auto-converts from any mode)
for slice in reader..slices
// 4. Write the reconstructed volume
let mut writer = create
.shape
.
.finish?;
let block = Owned ;
writer.write_data_block?;
writer.update_header_stats?;
writer.finalize?;
CLI Tools
The mrc-cli crate provides the mrc-cli
command-line tool with subcommands for inspection, validation, conversion,
PNG/GIF export, and resampling.
See the mrc-cli crate on crates.io for the
full command reference and examples.
Further Reading
| Resource | What you'll find |
|---|---|
| docs.rs/mrc | Complete API reference with runnable examples on every method |
| APIs.md | Local API surface overview (offline-friendly) |
| mrc-cli on crates.io | CLI binary reference and examples |
| roadmap.md | Release history and planned features |
| AGENTS.md | Code organization & conventions for contributors |
| mrcfile-official.md | The MRC-2014 specification |
| update.md | Per-release changelogs |
Acknowledgments
- CCP-EM for the MRC-2014 specification
- EMDB for providing real-world test data
- The cryo-EM community for invaluable feedback
Contributing
Contributions are welcome — whatever your skill level.
This crate is built by and for the cryo-EM community. Whether you're fixing a typo, adding a test, implementing a new feature, or just asking a question, your input makes the project better.
- Report bugs — open an issue with steps to reproduce
- Request features — what format feature or workflow is missing from your pipeline?
- Submit PRs — see AGENTS.md for code organization and conventions
- Improve docs — better examples, clearer explanations, fix typos
- Share real files — MRC files with unusual extended headers or edge cases help us test
- Adapt the test suite to your own data — the
tests/real_data_tests.rsfile is designed to be a template. Place any MRC files (from EMDB, EMPIAR, or your own microscope) intoreal_data/and the tests exercise every API path against them. The more diverse the files, the more edge cases we catch.
All contributions are subject to the MIT License.
Format specs come and go, but cryo-EM data is forever — make yours readable by the next generation of tools.
MIT — see the LICENSE file.