macro_rules! write_u4_block_body {
($self:ident, $block:ident) => {{
if $self.mode() != Mode::Packed4Bit {
return Err(Error::ModeMismatch {
file_mode: $self.mode(),
requested_mode: Mode::Packed4Bit,
});
}
if !$self.shape().contains_block($block.offset, $block.shape) {
return Err(Error::BoundsError);
}
for &v in &$block.data {
if v > 15 {
return Err(crate::Error::TypeMismatch {
expected: 4,
actual: 8,
});
}
}
let nx = $block.shape[0];
let ny = $block.shape[1];
let nz = $block.shape[2];
let packed = crate::engine::convert::pack_u8_to_u4_bytes(&$block.data, nx, ny * nz);
$self.write_block_bytes(&packed, $block.offset, $block.shape)?;
Ok(())
}};
}
macro_rules! write_block_as_body {
($self:ident, $block:ident) => {{
if !$self.shape().contains_block($block.offset, $block.shape) {
return Err(Error::BoundsError);
}
match $self.mode() {
#[cfg(feature = "f16")]
Mode::Float16 => {
let data = crate::engine::convert::convert_f32_slice_to_f16(&$block.data);
$self.write_block::<crate::f16>(&VoxelBlock {
offset: $block.offset,
shape: $block.shape,
data,
})
}
_ => Err(Error::UnsupportedMode),
}
}};
}
use crate::engine::block::{VolumeShape, VoxelBlock};
#[cfg(feature = "parallel")]
use crate::engine::codec::encode_block_parallel;
use crate::engine::codec::encode_slice;
use crate::engine::endian::FileEndian;
use crate::mode::Voxel;
use crate::{Error, Header, Mode};
use std::path::PathBuf;
use std::vec::Vec;
macro_rules! builder_setters {
() => {
pub fn shape(mut self, shape: [usize; 3]) -> Self {
self.header.nx = shape[0] as i32;
self.header.ny = shape[1] as i32;
self.header.nz = shape[2] as i32;
self.header.mx = self.header.nx;
self.header.my = self.header.ny;
self.header.mz = self.header.nz;
self
}
pub fn mode<T: Voxel>(mut self) -> Self {
self.header.mode = T::MODE.as_i32();
self
}
pub fn mode_raw(mut self, mode: i32) -> Self {
self.header.mode = mode;
self
}
pub fn cell_lengths(mut self, xlen: f32, ylen: f32, zlen: f32) -> Self {
self.header.xlen = xlen;
self.header.ylen = ylen;
self.header.zlen = zlen;
self
}
pub fn ispg(mut self, ispg: i32) -> Self {
self.header.ispg = ispg;
self
}
pub fn exttyp(mut self, exttyp: [u8; 4]) -> Self {
self.header.set_exttyp(exttyp);
self
}
pub fn nsymbt(mut self, nsymbt: i32) -> Self {
self.header.nsymbt = nsymbt;
self
}
pub fn origin(mut self, origin: [f32; 3]) -> Self {
self.header.origin = origin;
self
}
};
}
#[derive(Debug)]
pub struct WriterBuilder {
path: PathBuf,
header: Header,
ext_header: Vec<u8>,
}
impl WriterBuilder {
pub fn new<P: AsRef<std::path::Path>>(path: P) -> Self {
Self {
path: path.as_ref().to_path_buf(),
header: Header::new(),
ext_header: Vec::new(),
}
}
builder_setters!();
pub fn ext_header_bytes(mut self, bytes: Vec<u8>) -> Self {
self.header.nsymbt = bytes.len() as i32;
self.ext_header = bytes;
self
}
pub fn finish(self) -> Result<Writer, Error> {
Writer::create(self.path, self.header, &self.ext_header)
}
#[cfg(feature = "mmap")]
pub fn finish_mmap(self) -> Result<MmapWriter, Error> {
MmapWriter::create(self.path, self.header, &self.ext_header)
}
#[cfg(feature = "gzip")]
pub fn finish_gzip(self) -> Result<crate::GzipWriter, Error> {
CompressedWriter::create(self.path, self.header, &self.ext_header)
}
#[cfg(feature = "bzip2")]
pub fn finish_bzip2(self) -> Result<crate::Bzip2Writer, Error> {
CompressedWriter::create(self.path, self.header, &self.ext_header)
}
}
#[derive(Debug)]
pub struct Writer {
file: std::fs::File,
header: Header,
data_offset: u64,
bytes_per_voxel: usize,
shape: VolumeShape,
}
impl Writer {
pub(crate) fn create<P: AsRef<std::path::Path>>(
path: P,
mut header: Header,
ext_header: &[u8],
) -> Result<Self, Error> {
use std::io::Write;
header.set_file_endian(FileEndian::LittleEndian);
header.validate_detailed()?;
let mut file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)?;
let mut header_bytes = [0u8; 1024];
header.encode_to_bytes(&mut header_bytes);
file.write_all(&header_bytes)?;
let ext_size = header.nsymbt as usize;
if ext_size > 0 {
if ext_header.len() >= ext_size {
file.write_all(&ext_header[..ext_size])?;
} else {
file.write_all(ext_header)?;
let remaining = ext_size - ext_header.len();
let zeros = vec![0u8; remaining];
file.write_all(&zeros)?;
}
}
let data_offset = header.data_offset() as u64;
let mode = Mode::from_i32(header.mode).ok_or(Error::UnsupportedMode)?;
if mode == Mode::Int16Complex {
eprintln!(
"Warning: Mode 3 (Int16Complex) is obsolete and should not be used for writing new files."
);
}
let bytes_per_voxel = mode.byte_size();
let shape = VolumeShape::new(header.nx as usize, header.ny as usize, header.nz as usize);
Ok(Self {
file,
header,
data_offset,
bytes_per_voxel,
shape,
})
}
pub fn shape(&self) -> VolumeShape {
self.shape
}
pub fn mode(&self) -> Mode {
Mode::from_i32(self.header.mode).unwrap_or(Mode::Float32)
}
pub fn header(&self) -> &Header {
&self.header
}
pub fn write_block<T: Voxel>(&mut self, block: &VoxelBlock<T>) -> Result<(), Error> {
if T::MODE != self.mode() {
return Err(Error::ModeMismatch {
file_mode: self.mode(),
requested_mode: T::MODE,
});
}
if !self.shape.contains_block(block.offset, block.shape) {
return Err(Error::BoundsError);
}
let [nx, ny, _nz] = [self.shape.nx, self.shape.ny, self.shape.nz];
let [ox, oy, oz] = block.offset;
let [sx, sy, sz] = block.shape;
let b = self.bytes_per_voxel;
let file_endian = self.header.detect_endian();
if ox == 0 && sx == nx && oy == 0 && sy == ny {
let linear =
(ox as u64) + (oy as u64) * (nx as u64) + (oz as u64) * (nx as u64) * (ny as u64);
let start_offset = self.data_offset + linear * (b as u64);
let byte_len = (sx as u64) * (sy as u64) * (sz as u64) * (b as u64);
let byte_len_usize = byte_len.try_into().map_err(|_| Error::BoundsError)?;
let mut buffer = vec![0u8; byte_len_usize];
encode_slice(&block.data, &mut buffer, file_endian)?;
use std::io::{Seek, SeekFrom, Write};
self.file.seek(SeekFrom::Start(start_offset))?;
self.file.write_all(&buffer)?;
return Ok(());
}
let mut row_bytes = vec![0u8; sx * b];
use std::io::{Seek, SeekFrom, Write};
for z in 0..sz {
for y in 0..sy {
let file_linear = ox + (oy + y) * nx + (oz + z) * nx * ny;
let file_offset = self.data_offset + (file_linear as u64) * (b as u64);
let block_idx = y * sx + z * sx * sy;
if block_idx + sx > block.data.len() {
return Err(Error::BoundsError);
}
let row_values = &block.data[block_idx..block_idx + sx];
encode_slice(row_values, &mut row_bytes, file_endian)?;
self.file.seek(SeekFrom::Start(file_offset))?;
self.file.write_all(&row_bytes)?;
}
}
Ok(())
}
pub fn write_u8_block(&mut self, block: &VoxelBlock<u8>) -> Result<(), Error> {
if self.mode() != Mode::Uint16 {
return Err(Error::ModeMismatch {
file_mode: self.mode(),
requested_mode: Mode::Uint16,
});
}
let widened = crate::engine::convert::convert_u8_slice_to_u16(&block.data);
self.write_block(&VoxelBlock {
offset: block.offset,
shape: block.shape,
data: widened,
})
}
pub fn write_block_as(&mut self, block: &VoxelBlock<f32>) -> Result<(), Error> {
write_block_as_body!(self, block)
}
#[cfg(feature = "parallel")]
pub fn write_block_parallel<T: Voxel>(&mut self, block: &VoxelBlock<T>) -> Result<(), Error> {
if T::MODE != self.mode() {
return Err(Error::ModeMismatch {
file_mode: self.mode(),
requested_mode: T::MODE,
});
}
if !self.shape.contains_block(block.offset, block.shape) {
return Err(Error::BoundsError);
}
let [nx, ny, _nz] = [self.shape.nx, self.shape.ny, self.shape.nz];
let [ox, oy, oz] = block.offset;
let [sx, sy, _sz] = block.shape;
if ox != 0 || sx != nx || oy != 0 || sy != ny {
return self.write_block(block);
}
let chunk_size = 1024 * 1024; let linear =
(ox as u64) + (oy as u64) * (nx as u64) + (oz as u64) * (nx as u64) * (ny as u64);
let base_offset = self.data_offset + linear * (self.bytes_per_voxel as u64);
let file_endian = self.header.detect_endian();
let encoded_chunks = encode_block_parallel(&block.data, chunk_size, file_endian);
use std::io::{Seek, SeekFrom, Write};
for (chunk_idx, encoded) in encoded_chunks {
let offset = base_offset
+ (chunk_idx as u64) * (chunk_size as u64) * (self.bytes_per_voxel as u64);
self.file.seek(SeekFrom::Start(offset))?;
self.file.write_all(&encoded)?;
}
Ok(())
}
pub fn write_u4_block(&mut self, block: &VoxelBlock<u8>) -> Result<(), Error> {
write_u4_block_body!(self, block)
}
fn write_block_bytes(
&mut self,
packed: &[u8],
offset: [usize; 3],
shape: [usize; 3],
) -> Result<(), Error> {
use std::io::{Seek, SeekFrom, Write};
let [nx, ny, _nz] = [self.shape.nx, self.shape.ny, self.shape.nz];
let [ox, oy, oz] = offset;
let [sx, sy, sz] = shape;
let file_row_bytes = nx.div_ceil(2);
let block_row_bytes = sx.div_ceil(2);
debug_assert!(ox == 0, "write_block_bytes requires ox == 0");
if sx == nx && oy == 0 && sy == ny {
let slice_bytes = ny * file_row_bytes;
let start_offset = (self.data_offset as usize) + oz * slice_bytes;
let byte_len = sz * slice_bytes;
let end_offset = start_offset + byte_len;
if end_offset > (self.data_offset as usize) + self.header.data_size().unwrap_or(0) {
return Err(Error::BoundsError);
}
self.file.seek(SeekFrom::Start(start_offset as u64))?;
self.file.write_all(&packed[..byte_len])?;
return Ok(());
}
for z in 0..sz {
for y in 0..sy {
let vol_row = (oz + z) * ny + (oy + y);
let file_offset = (self.data_offset as usize) + vol_row * file_row_bytes;
let packed_start = (y + z * sy) * block_row_bytes;
let packed_end = packed_start + block_row_bytes;
if packed_end > packed.len() {
return Err(Error::BoundsError);
}
self.file.seek(SeekFrom::Start(file_offset as u64))?;
self.file.write_all(&packed[packed_start..packed_end])?;
}
}
Ok(())
}
pub fn finalize(&mut self) -> Result<(), Error> {
use std::io::{Seek, SeekFrom, Write};
self.file.seek(SeekFrom::Start(0))?;
let mut header_bytes = [0u8; 1024];
self.header.encode_to_bytes(&mut header_bytes);
self.file.write_all(&header_bytes)?;
Ok(())
}
pub fn update_header_stats(&mut self) -> Result<(), Error> {
use std::io::{Read, Seek, SeekFrom};
let data_size = self.header.data_size().ok_or(Error::InvalidHeader)?;
self.file.seek(SeekFrom::Start(self.data_offset))?;
let mut buf = vec![0u8; data_size];
self.file.read_exact(&mut buf)?;
update_header_stats_from_bytes(&mut self.header, &buf)?;
Ok(())
}
}
fn update_header_stats_from_bytes(header: &mut Header, bytes: &[u8]) -> Result<(), Error> {
let endian = header.detect_endian();
let mode = Mode::from_i32(header.mode).ok_or(Error::UnsupportedMode)?;
let nx = header.nx.max(0) as usize;
let ny = header.ny.max(0) as usize;
let nz = header.nz.max(0) as usize;
let (dmin, dmax, dmean, rms) =
crate::engine::stats::compute_stats(bytes, mode, endian, nx, ny * nz)?;
header.dmin = dmin;
header.dmax = dmax;
header.dmean = dmean;
header.rms = rms;
Ok(())
}
#[cfg(feature = "mmap")]
#[derive(Debug)]
pub struct MmapWriter {
mmap: memmap2::MmapMut,
header: Header,
data_offset: usize,
bytes_per_voxel: usize,
shape: VolumeShape,
}
#[cfg(feature = "mmap")]
impl MmapWriter {
pub(crate) fn create<P: AsRef<std::path::Path>>(
path: P,
mut header: Header,
ext_header: &[u8],
) -> Result<Self, Error> {
use std::fs::OpenOptions;
use std::io::Write;
header.set_file_endian(FileEndian::LittleEndian);
header.validate_detailed()?;
let total_size = header
.data_offset()
.checked_add(header.data_size().ok_or(Error::InvalidHeader)?)
.ok_or(Error::InvalidHeader)?;
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)?;
file.set_len(total_size as u64)?;
let mut header_bytes = [0u8; 1024];
header.encode_to_bytes(&mut header_bytes);
file.write_all(&header_bytes)?;
let ext_size = header.nsymbt as usize;
if ext_size > 0 {
if ext_header.len() >= ext_size {
file.write_all(&ext_header[..ext_size])?;
} else {
file.write_all(ext_header)?;
let remaining = ext_size - ext_header.len();
let zeros = vec![0u8; remaining];
file.write_all(&zeros)?;
}
}
let mmap = unsafe {
memmap2::MmapOptions::new()
.map_mut(&file)
.map_err(|_| Error::Mmap)?
};
let data_offset = header.data_offset();
let mode = Mode::from_i32(header.mode).ok_or(Error::UnsupportedMode)?;
if mode == Mode::Int16Complex {
eprintln!(
"Warning: Mode 3 (Int16Complex) is obsolete and should not be used for writing new files."
);
}
let bytes_per_voxel = mode.byte_size();
let shape = VolumeShape::new(header.nx as usize, header.ny as usize, header.nz as usize);
Ok(Self {
mmap,
header,
data_offset,
bytes_per_voxel,
shape,
})
}
pub fn shape(&self) -> VolumeShape {
self.shape
}
pub fn mode(&self) -> Mode {
Mode::from_i32(self.header.mode).unwrap_or(Mode::Float32)
}
pub fn header(&self) -> &Header {
&self.header
}
pub fn write_u8_block(&mut self, block: &VoxelBlock<u8>) -> Result<(), Error> {
if self.mode() != Mode::Uint16 {
return Err(Error::ModeMismatch {
file_mode: self.mode(),
requested_mode: Mode::Uint16,
});
}
let widened = crate::engine::convert::convert_u8_slice_to_u16(&block.data);
self.write_block(&VoxelBlock {
offset: block.offset,
shape: block.shape,
data: widened,
})
}
pub fn write_block_as(&mut self, block: &VoxelBlock<f32>) -> Result<(), Error> {
write_block_as_body!(self, block)
}
pub fn write_block<T: Voxel>(&mut self, block: &VoxelBlock<T>) -> Result<(), Error> {
if T::MODE != self.mode() {
return Err(Error::ModeMismatch {
file_mode: self.mode(),
requested_mode: T::MODE,
});
}
if !self.shape.contains_block(block.offset, block.shape) {
return Err(Error::BoundsError);
}
let file_endian = self.header.detect_endian();
crate::io::reader_common::encode_block_to_buf(
block,
self.shape,
self.bytes_per_voxel,
file_endian,
self.data_offset,
&mut self.mmap,
)
}
#[cfg(feature = "parallel")]
pub fn write_block_parallel<T: Voxel>(&mut self, block: &VoxelBlock<T>) -> Result<(), Error> {
use rayon::prelude::*;
if T::MODE != self.mode() {
return Err(Error::ModeMismatch {
file_mode: self.mode(),
requested_mode: T::MODE,
});
}
if !self.shape.contains_block(block.offset, block.shape) {
return Err(Error::BoundsError);
}
let [nx, ny, _nz] = [self.shape.nx, self.shape.ny, self.shape.nz];
let [ox, oy, _oz] = block.offset;
let [sx, sy, _sz] = block.shape;
if ox != 0 || sx != nx || oy != 0 || sy != ny {
return self.write_block(block);
}
let chunk_size = 1024 * 1024; let linear = self
.shape
.checked_linear_index(block.offset)
.ok_or(Error::BoundsError)?;
let base_offset = self
.data_offset
.checked_add(
linear
.checked_mul(self.bytes_per_voxel)
.ok_or(Error::BoundsError)?,
)
.ok_or(Error::BoundsError)?;
let file_endian = self.header.detect_endian();
let mmap_ptr = self.mmap.as_mut_ptr() as usize;
block
.data
.par_chunks(chunk_size)
.enumerate()
.try_for_each(|(chunk_idx, chunk)| {
let start_offset = base_offset + chunk_idx * chunk_size * self.bytes_per_voxel;
let ptr = (mmap_ptr + start_offset) as *mut u8;
let dst = unsafe {
core::slice::from_raw_parts_mut(ptr, chunk.len() * self.bytes_per_voxel)
};
encode_slice(chunk, dst, file_endian)
})?;
Ok(())
}
pub fn write_u4_block(&mut self, block: &VoxelBlock<u8>) -> Result<(), Error> {
write_u4_block_body!(self, block)
}
fn write_block_bytes(
&mut self,
packed: &[u8],
offset: [usize; 3],
shape: [usize; 3],
) -> Result<(), Error> {
crate::io::reader_common::write_block_bytes(
packed,
self.shape,
offset,
shape,
self.data_offset,
&mut self.mmap,
)
}
pub fn update_header_stats(&mut self) -> Result<(), Error> {
let data_size = self.header.data_size().ok_or(Error::InvalidHeader)?;
let end = self
.data_offset
.checked_add(data_size)
.ok_or(Error::InvalidHeader)?;
if end > self.mmap.len() {
return Err(Error::BoundsError);
}
update_header_stats_from_bytes(&mut self.header, &self.mmap[self.data_offset..end])?;
Ok(())
}
}
#[cfg(feature = "mmap")]
impl MmapWriter {
pub fn finalize(&mut self) -> Result<(), Error> {
let mut header_bytes = [0u8; 1024];
self.header.encode_to_bytes(&mut header_bytes);
self.mmap[0..1024].copy_from_slice(&header_bytes);
self.mmap.flush()?;
Ok(())
}
}
#[doc(hidden)]
pub trait Compressor {
fn compress(data: &[u8]) -> Result<Vec<u8>, Error>;
}
#[derive(Debug)]
pub struct CompressedWriter<C: Compressor> {
header: Header,
data: Vec<u8>,
ext_header: Vec<u8>,
path: std::path::PathBuf,
bytes_per_voxel: usize,
shape: VolumeShape,
_marker: std::marker::PhantomData<C>,
}
impl<C: Compressor> CompressedWriter<C> {
pub(crate) fn create<P: AsRef<std::path::Path>>(
path: P,
header: Header,
ext_header: &[u8],
) -> Result<Self, Error> {
let mut header = header;
header.set_file_endian(FileEndian::LittleEndian);
if !ext_header.is_empty() {
header.nsymbt = ext_header.len() as i32;
}
header.validate_detailed()?;
let ext_size = header.nsymbt as usize;
let ext_header_stored = if ext_header.len() >= ext_size {
ext_header[..ext_size].to_vec()
} else {
let mut v = ext_header.to_vec();
v.resize(ext_size, 0);
v
};
let data_size = header.data_size().ok_or(Error::InvalidHeader)?;
let data = vec![0u8; data_size];
let mode = Mode::from_i32(header.mode).ok_or(Error::UnsupportedMode)?;
let bytes_per_voxel = mode.byte_size();
let shape = VolumeShape::new(header.nx as usize, header.ny as usize, header.nz as usize);
Ok(Self {
header,
data,
ext_header: ext_header_stored,
path: path.as_ref().to_path_buf(),
bytes_per_voxel,
shape,
_marker: std::marker::PhantomData,
})
}
pub fn shape(&self) -> VolumeShape {
self.shape
}
pub fn mode(&self) -> Mode {
Mode::from_i32(self.header.mode).unwrap_or(Mode::Float32)
}
pub fn header(&self) -> &Header {
&self.header
}
pub fn write_block<T: Voxel>(&mut self, block: &VoxelBlock<T>) -> Result<(), Error> {
if T::MODE != self.mode() {
return Err(Error::ModeMismatch {
file_mode: self.mode(),
requested_mode: T::MODE,
});
}
if !self.shape.contains_block(block.offset, block.shape) {
return Err(Error::BoundsError);
}
let file_endian = self.header.detect_endian();
crate::io::reader_common::encode_block_to_buf(
block,
self.shape,
self.bytes_per_voxel,
file_endian,
0,
&mut self.data,
)
}
pub fn write_u8_block(&mut self, block: &VoxelBlock<u8>) -> Result<(), Error> {
if self.mode() != Mode::Uint16 {
return Err(Error::ModeMismatch {
file_mode: self.mode(),
requested_mode: Mode::Uint16,
});
}
let widened = crate::engine::convert::convert_u8_slice_to_u16(&block.data);
self.write_block(&VoxelBlock {
offset: block.offset,
shape: block.shape,
data: widened,
})
}
pub fn write_block_as(&mut self, block: &VoxelBlock<f32>) -> Result<(), Error> {
write_block_as_body!(self, block)
}
pub fn write_u4_block(&mut self, block: &VoxelBlock<u8>) -> Result<(), Error> {
write_u4_block_body!(self, block)
}
fn write_block_bytes(
&mut self,
packed: &[u8],
offset: [usize; 3],
shape: [usize; 3],
) -> Result<(), Error> {
crate::io::reader_common::write_block_bytes(
packed,
self.shape,
offset,
shape,
0, &mut self.data,
)
}
pub fn update_header_stats(&mut self) -> Result<(), Error> {
update_header_stats_from_bytes(&mut self.header, &self.data)
}
pub fn finalize(self) -> Result<(), Error> {
let mut header_bytes = [0u8; 1024];
self.header.encode_to_bytes(&mut header_bytes);
let ext_size = self.header.nsymbt as usize;
let mut file_bytes = Vec::with_capacity(1024 + ext_size + self.data.len());
file_bytes.extend_from_slice(&header_bytes);
if ext_size > 0 {
file_bytes.extend_from_slice(&self.ext_header);
}
file_bytes.extend_from_slice(&self.data);
let compressed = C::compress(&file_bytes)?;
std::fs::write(&self.path, compressed)?;
Ok(())
}
}