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.7"
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 encoding via rayon |
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: f32 data → i16 file
writer.write_block_as?;
// Write in parallel (requires `parallel` feature)
writer.write_block_parallel?;
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?;
writer.write_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?;
writer.write_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
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.