use mrc::*;
struct TempMrc(std::path::PathBuf);
impl TempMrc {
fn new(suffix: &str) -> Self {
let mut p = std::env::temp_dir();
p.push(format!("mrc_test_{}_{}.mrc", std::process::id(), suffix));
let _ = std::fs::remove_file(&p);
Self(p)
}
fn path(&self) -> &std::path::Path {
&self.0
}
}
impl Drop for TempMrc {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.0);
}
}
#[test]
fn roundtrip_f32() {
let f = TempMrc::new("f32");
let nx = 16;
let ny = 8;
let nz = 4;
let data: Vec<f32> = (0..nx * ny * nz).map(|i| i as f32).collect();
{
let mut w = create(f.path())
.shape([nx, ny, nz])
.mode::<f32>()
.finish()
.unwrap();
w.write_block(&VoxelBlock::new([0, 0, 0], [nx, ny, nz], data.clone()).unwrap())
.unwrap();
w.finalize().unwrap();
}
let r = Reader::open(f.path()).unwrap();
let block = r.read_volume::<f32>().unwrap();
assert_eq!(block.data, data);
assert_eq!(&block.offset, &[0, 0, 0]);
assert_eq!(&block.shape, &[nx, ny, nz]);
let block2 = r.convert::<f32>().read_volume().unwrap();
assert_eq!(block2.data, data);
}
#[test]
fn roundtrip_i16_to_f32() {
let f = TempMrc::new("i16");
let nx = 16;
let ny = 8;
let nz = 4;
let total = nx * ny * nz;
let src: Vec<i16> = (0..total).map(|i| (i as i16) - 100).collect();
let expected_f32: Vec<f32> = src.iter().map(|&v| v as f32).collect();
{
let mut w = create(f.path())
.shape([nx, ny, nz])
.mode::<i16>()
.finish()
.unwrap();
w.write_block(&VoxelBlock::new([0, 0, 0], [nx, ny, nz], src).unwrap())
.unwrap();
w.finalize().unwrap();
}
let r = Reader::open(f.path()).unwrap();
let block = r.convert::<f32>().read_volume().unwrap();
assert_eq!(block.data, expected_f32);
let all: Vec<f32> = r
.convert::<f32>()
.slices()
.flat_map(|s| s.unwrap().data)
.collect();
assert_eq!(all, expected_f32);
}
#[test]
fn roundtrip_u16() {
let f = TempMrc::new("u16");
let nx = 8;
let ny = 8;
let nz = 2;
let src: Vec<u16> = (0..nx * ny * nz).map(|i| (i * 2) as u16).collect();
{
let mut w = create(f.path())
.shape([nx, ny, nz])
.mode::<u16>()
.finish()
.unwrap();
w.write_block(&VoxelBlock::new([0, 0, 0], [nx, ny, nz], src.clone()).unwrap())
.unwrap();
w.finalize().unwrap();
}
let r = Reader::open(f.path()).unwrap();
let block = r.read_volume::<u16>().unwrap();
assert_eq!(block.data, src);
}
#[test]
fn roundtrip_subregion() {
let f = TempMrc::new("subregion");
let nx = 32;
let ny = 32;
let nz = 8;
let mut w = create(f.path())
.shape([nx, ny, nz])
.mode::<f32>()
.finish()
.unwrap();
for z in 0..nz {
let slice = vec![(z * nx * ny) as f32; nx * ny];
w.write_block(&VoxelBlock::new([0, 0, z], [nx, ny, 1], slice).unwrap())
.unwrap();
}
w.finalize().unwrap();
let r = Reader::open(f.path()).unwrap();
let block = r.subregion::<f32>([4, 4, 2], [8, 8, 3]).unwrap();
assert_eq!(block.offset, [4, 4, 2]);
assert_eq!(block.shape, [8, 8, 3]);
assert_eq!(block.data.len(), 8 * 8 * 3);
}
#[test]
fn read_volume_via_convert_slices_f32() {
let f = TempMrc::new("vol_vs_slices");
let nx = 10;
let ny = 10;
let nz = 5;
let data: Vec<f32> = (0..nx * ny * nz).map(|i| i as f32).collect();
{
let mut w = create(f.path())
.shape([nx, ny, nz])
.mode::<f32>()
.finish()
.unwrap();
w.write_block(&VoxelBlock::new([0, 0, 0], [nx, ny, nz], data.clone()).unwrap())
.unwrap();
w.finalize().unwrap();
}
let r = Reader::open(f.path()).unwrap();
let vol = r.read_volume::<f32>().unwrap();
let collected: Vec<f32> = r
.convert::<f32>()
.slices()
.flat_map(|s| s.unwrap().data)
.collect();
assert_eq!(vol.data, collected);
}
#[cfg(feature = "gzip")]
#[test]
fn roundtrip_gzip() {
let f = TempMrc::new("gzip");
let nx = 8;
let ny = 8;
let nz = 4;
let data: Vec<f32> = (0..nx * ny * nz).map(|i| i as f32).collect();
{
let mut w = create(f.path())
.shape([nx, ny, nz])
.mode::<f32>()
.finish_gzip()
.unwrap();
w.write_block(&VoxelBlock::new([0, 0, 0], [nx, ny, nz], data.clone()).unwrap())
.unwrap();
w.finalize().unwrap();
}
let r = Reader::open(f.path()).unwrap();
let block = r.read_volume::<f32>().unwrap();
assert_eq!(block.data, data);
}
#[test]
fn update_header_stats_roundtrip() {
let f = TempMrc::new("stats");
let nx = 4;
let ny = 4;
let nz = 2;
let total = nx * ny * nz;
let data: Vec<f32> = (0..total).map(|i| i as f32).collect();
{
let mut w = create(f.path())
.shape([nx, ny, nz])
.mode::<f32>()
.finish()
.unwrap();
w.write_block(&VoxelBlock::new([0, 0, 0], [nx, ny, nz], data.clone()).unwrap())
.unwrap();
w.update_header_stats().unwrap();
w.finalize().unwrap();
}
let r = Reader::open(f.path()).unwrap();
assert!(r.validate_header_stats().is_ok());
}
#[test]
fn mode101_roundtrip_even() {
let f = TempMrc::new("m101_even");
let nx = 4;
let ny = 4;
let nz = 2;
let total = nx * ny * nz;
let src: Vec<u8> = (0..total).map(|i| (i % 16) as u8).collect();
{
let mut w = create(f.path())
.shape([nx, ny, nz])
.mode_raw(101)
.finish()
.unwrap();
w.write_u4_block(&VoxelBlock::new([0, 0, 0], [nx, ny, nz], src.clone()).unwrap())
.unwrap();
w.finalize().unwrap();
}
let r = Reader::open(f.path()).unwrap();
let block = r.read_volume_u8().unwrap();
assert_eq!(block.data, src);
let collected: Vec<u8> = r.slices_u8().flat_map(|s| s.unwrap().data).collect();
assert_eq!(collected, src);
}
#[test]
fn mode101_roundtrip_odd() {
let f = TempMrc::new("m101_odd");
let nx = 3;
let ny = 2;
let nz = 1;
let total = nx * ny * nz;
let src: Vec<u8> = (0..total).map(|i| (i % 16) as u8).collect();
{
let mut w = create(f.path())
.shape([nx, ny, nz])
.mode_raw(101)
.finish()
.unwrap();
w.write_u4_block(&VoxelBlock::new([0, 0, 0], [nx, ny, nz], src.clone()).unwrap())
.unwrap();
w.finalize().unwrap();
}
let r = Reader::open(f.path()).unwrap();
let block = r.read_volume_u8().unwrap();
assert_eq!(block.data, src);
}
#[test]
fn mode101_header_stats() {
let f = TempMrc::new("m101_stats");
let nx = 8;
let ny = 4;
let nz = 1;
let total = nx * ny * nz;
let src: Vec<u8> = (0..total).map(|i| (i % 16) as u8).collect();
{
let mut w = create(f.path())
.shape([nx, ny, nz])
.mode_raw(101)
.finish()
.unwrap();
w.write_u4_block(&VoxelBlock::new([0, 0, 0], [nx, ny, nz], src.clone()).unwrap())
.unwrap();
w.update_header_stats().unwrap();
w.finalize().unwrap();
}
let r = Reader::open(f.path()).unwrap();
assert!(r.validate_header_stats().is_ok());
}
#[test]
fn mode101_value_overflow() {
let f = TempMrc::new("m101_overflow");
let src = vec![16u8]; let mut w = create(f.path())
.shape([1, 1, 1])
.mode_raw(101)
.finish()
.unwrap();
let result = w.write_u4_block(&VoxelBlock::new([0, 0, 0], [1, 1, 1], src).unwrap());
assert!(result.is_err());
}
#[test]
fn roundtrip_complex_i16() {
let f = TempMrc::new("cpx_i16");
let nx = 4;
let ny = 4;
let nz = 2;
let total = nx * ny * nz;
let src: Vec<Int16Complex> = (0..total)
.map(|i| Int16Complex {
real: i as i16,
imag: (i * 10) as i16,
})
.collect();
{
let mut w = create(f.path())
.shape([nx, ny, nz])
.mode::<Int16Complex>()
.finish()
.unwrap();
w.write_block(&VoxelBlock::new([0, 0, 0], [nx, ny, nz], src.clone()).unwrap())
.unwrap();
w.finalize().unwrap();
}
let r = Reader::open(f.path()).unwrap();
let block = r.read_volume::<Int16Complex>().unwrap();
assert_eq!(block.data.len(), total);
for (a, b) in block.data.iter().zip(src.iter()) {
assert_eq!(a.real, b.real);
assert_eq!(a.imag, b.imag);
}
let mag_block = r.convert::<f32>().read_volume().unwrap();
for (i, val) in mag_block.data.iter().enumerate() {
let expected = ((i as f32).powi(2) + ((i * 10) as f32).powi(2)).sqrt();
assert!((val - expected).abs() < 1e-4);
}
}
#[test]
fn roundtrip_complex_f32() {
let f = TempMrc::new("cpx_f32");
let nx = 4;
let ny = 4;
let nz = 2;
let total = nx * ny * nz;
let src: Vec<Float32Complex> = (0..total)
.map(|i| Float32Complex {
real: i as f32,
imag: (i as f32) * 1.5,
})
.collect();
{
let mut w = create(f.path())
.shape([nx, ny, nz])
.mode::<Float32Complex>()
.finish()
.unwrap();
w.write_block(&VoxelBlock::new([0, 0, 0], [nx, ny, nz], src.clone()).unwrap())
.unwrap();
w.finalize().unwrap();
}
let r = Reader::open(f.path()).unwrap();
let block = r.read_volume::<Float32Complex>().unwrap();
assert_eq!(block.data.len(), total);
for (a, b) in block.data.iter().zip(src.iter()) {
assert_eq!(a.real, b.real);
assert_eq!(a.imag, b.imag);
}
}
#[cfg(feature = "mmap")]
#[test]
fn mmap_roundtrip_f32() {
let f = TempMrc::new("mmap_f32");
let nx = 16;
let ny = 8;
let nz = 4;
let total = nx * ny * nz;
let data: Vec<f32> = (0..total).map(|i| i as f32).collect();
{
let mut w = create(f.path())
.shape([nx, ny, nz])
.mode::<f32>()
.finish()
.unwrap();
w.write_block(&VoxelBlock::new([0, 0, 0], [nx, ny, nz], data.clone()).unwrap())
.unwrap();
w.finalize().unwrap();
}
let r = Reader::open(f.path()).unwrap();
let block = r.read_volume::<f32>().unwrap();
assert_eq!(block.data, data);
let slab: &[f32] = r.slab_as::<f32>(0, 1).unwrap();
assert_eq!(slab.len(), nx * ny);
assert_eq!(slab, &data[..nx * ny]);
}
#[cfg(feature = "bzip2")]
#[test]
fn roundtrip_bzip2() {
let f = TempMrc::new("bzip2");
let nx = 8;
let ny = 8;
let nz = 4;
let data: Vec<f32> = (0..nx * ny * nz).map(|i| i as f32).collect();
{
let mut w = create(f.path())
.shape([nx, ny, nz])
.mode::<f32>()
.finish_bzip2()
.unwrap();
w.write_block(&VoxelBlock::new([0, 0, 0], [nx, ny, nz], data.clone()).unwrap())
.unwrap();
w.finalize().unwrap();
}
let r = Reader::open(f.path()).unwrap();
let block = r.read_volume::<f32>().unwrap();
assert_eq!(block.data, data);
}
#[test]
fn write_block_as_i16() {
let f = TempMrc::new("wba_i16");
let nx = 4;
let ny = 4;
let nz = 2;
let total = nx * ny * nz;
let src: Vec<f32> = (0..total).map(|i| (i as f32) - 8.0).collect();
let expected_i16: Vec<i16> = src.iter().map(|&v| v as i16).collect();
{
let mut w = WriterBuilder::new(f.path())
.shape([nx, ny, nz])
.mode::<i16>()
.finish()
.unwrap();
w.write_block_as(&VoxelBlock::new([0, 0, 0], [nx, ny, nz], src).unwrap())
.unwrap();
w.finalize().unwrap();
}
let r = Reader::open(f.path()).unwrap();
let block = r.read_volume::<i16>().unwrap();
assert_eq!(block.data, expected_i16);
}
#[test]
fn write_block_as_u16() {
let f = TempMrc::new("wba_u16");
let total = 32usize;
let src: Vec<f32> = (0..total).map(|i| (i * 100) as f32).collect();
let expected_u16: Vec<u16> = src.iter().map(|&v| v as u16).collect();
{
let mut w = WriterBuilder::new(f.path())
.shape([4, 4, 2])
.mode::<u16>()
.finish()
.unwrap();
w.write_block_as(&VoxelBlock::new([0, 0, 0], [4, 4, 2], src).unwrap())
.unwrap();
w.finalize().unwrap();
}
let r = Reader::open(f.path()).unwrap();
let block = r.read_volume::<u16>().unwrap();
assert_eq!(block.data, expected_u16);
}
#[test]
fn write_block_as_i8() {
let f = TempMrc::new("wba_i8");
let total = 32usize;
let src: Vec<f32> = (0..total).map(|i| (i as f32) - 16.0).collect();
let expected_i8: Vec<i8> = src.iter().map(|&v| v as i8).collect();
{
let mut w = WriterBuilder::new(f.path())
.shape([4, 4, 2])
.mode::<i8>()
.finish()
.unwrap();
w.write_block_as(&VoxelBlock::new([0, 0, 0], [4, 4, 2], src).unwrap())
.unwrap();
w.finalize().unwrap();
}
let r = Reader::open(f.path()).unwrap();
let block = r.read_volume::<i8>().unwrap();
assert_eq!(block.data, expected_i8);
}
#[test]
fn volume_stack_error_on_plain_volume() {
let f = TempMrc::new("volstack_err");
{
let mut w = create(f.path())
.shape([8, 8, 4])
.mode::<f32>()
.finish()
.unwrap();
let data = vec![0.0f32; 8 * 8 * 4];
w.write_block(&VoxelBlock::new([0, 0, 0], [8, 8, 4], data).unwrap())
.unwrap();
w.finalize().unwrap();
}
let r = Reader::open(f.path()).unwrap();
match r.volumes::<f32>() {
Err(Error::NotAVolumeStack { .. }) => {} other => panic!("expected NotAVolumeStack, got {:?}", other.map(|_| ())),
}
}
#[test]
fn open_permissive_trailing_garbage() {
let f = TempMrc::new("perm_garbage");
{
let mut w = create(f.path())
.shape([4, 4, 1])
.mode::<f32>()
.finish()
.unwrap();
let data = vec![1.0f32; 16];
w.write_block(&VoxelBlock::new([0, 0, 0], [4, 4, 1], data).unwrap())
.unwrap();
w.finalize().unwrap();
}
{
use std::io::Write;
let mut file = std::fs::OpenOptions::new()
.append(true)
.open(f.path())
.unwrap();
file.write_all(b"TRAILING GARBAGE").unwrap();
}
assert!(Reader::open(f.path()).is_err());
let (reader, _warnings) = Reader::open_permissive(f.path()).unwrap();
let block = reader.read_volume::<f32>().unwrap();
assert_eq!(block.data, vec![1.0f32; 16]);
}
#[test]
fn open_permissive_bad_map() {
let f = TempMrc::new("perm_map");
{
let mut w = create(f.path())
.shape([4, 4, 1])
.mode::<f32>()
.finish()
.unwrap();
let data = vec![0.0f32; 16];
w.write_block(&VoxelBlock::new([0, 0, 0], [4, 4, 1], data).unwrap())
.unwrap();
w.finalize().unwrap();
}
{
use std::io::{Seek, SeekFrom, Write};
let mut file = std::fs::OpenOptions::new()
.write(true)
.open(f.path())
.unwrap();
file.seek(SeekFrom::Start(208)).unwrap();
file.write_all(b"XYZ_").unwrap();
}
assert!(Reader::open(f.path()).is_err());
let (reader, warnings) = Reader::open_permissive(f.path()).unwrap();
assert_eq!(reader.shape().nx, 4);
let has_map_warning = warnings.iter().any(|w| w.contains("MAP"));
assert!(
has_map_warning,
"expected MAP warning in permissive mode, got: {:?}",
warnings
);
}
#[test]
#[cfg(feature = "f16")]
fn roundtrip_f16() {
let f = TempMrc::new("f16");
let nx = 8;
let ny = 6;
let nz = 3;
let total = nx * ny * nz;
let src_f32: Vec<f32> = (0..total).map(|i| i as f32 * 1.25).collect();
let src_f16: Vec<half::f16> = src_f32.iter().map(|&v| half::f16::from_f32(v)).collect();
{
let mut w = create(f.path())
.shape([nx, ny, nz])
.mode::<half::f16>()
.finish()
.unwrap();
w.write_block(&VoxelBlock::new([0, 0, 0], [nx, ny, nz], src_f16.clone()).unwrap())
.unwrap();
w.finalize().unwrap();
}
let r = Reader::open(f.path()).unwrap();
let block_f16 = r.read_volume::<half::f16>().unwrap();
assert_eq!(block_f16.data, src_f16);
let block_f32 = r.convert::<f32>().read_volume().unwrap();
assert_eq!(block_f32.shape, [nx, ny, nz]);
for (got, expected) in block_f32.data.iter().zip(src_f32.iter()) {
let tol = (expected.abs() * 0.001).max(0.001);
assert!(
(got - expected).abs() <= tol,
"f16→f32 mismatch: expected {expected}, got {got}"
);
}
}
#[test]
fn volumes_iterator() {
let f = TempMrc::new("volstack");
let nx = 4;
let ny = 4;
let nz = 8;
let subvol_slices = 2; let nvol = nz / subvol_slices;
let all_data: Vec<f32> = (0..nx * ny * nz).map(|i| i as f32).collect();
{
let mut w = create(f.path())
.shape([nx, ny, nz])
.mode::<f32>()
.ispg(401) .finish()
.unwrap();
w.write_block(&VoxelBlock::new([0, 0, 0], [nx, ny, nz], all_data.clone()).unwrap())
.unwrap();
w.finalize().unwrap();
}
{
use std::io::{Seek, SeekFrom, Write};
let mut file = std::fs::OpenOptions::new()
.write(true)
.open(f.path())
.unwrap();
file.seek(SeekFrom::Start(36)).unwrap();
file.write_all(&(subvol_slices as i32).to_le_bytes())
.unwrap();
}
let r = Reader::open(f.path()).unwrap();
assert!(
r.header().is_volume_stack(),
"file should be a volume stack"
);
assert_eq!(r.header().mz, subvol_slices as i32, "mz should be patched");
let mut vol_count = 0;
for result in r.volumes::<f32>().unwrap() {
let vol = result.unwrap();
assert_eq!(vol.shape, [nx, ny, subvol_slices]);
let z_start = vol_count * subvol_slices;
let expected_slice = &all_data[z_start * nx * ny..(z_start + subvol_slices) * nx * ny];
assert_eq!(&vol.data, expected_slice);
vol_count += 1;
}
assert_eq!(vol_count, nvol, "expected {nvol} sub-volumes");
}