use crate::frame::{Array, Frame, Header};
use byteorder::{BigEndian, ByteOrder, LittleEndian, ReadBytesExt};
use std::collections::HashMap;
use std::fs::File;
use std::io;
use std::io::Read;
use std::path::Path;
pub struct MarFrame {
header: Header,
array: Array,
dim1: usize,
dim2: usize,
n_pixels: usize,
n_overflow: usize,
data_start: usize,
overflow_start: usize,
overflow_end: usize,
}
impl MarFrame {
pub fn new() -> MarFrame {
MarFrame {
header: HashMap::new(),
array: Array::new(),
dim1: 0,
dim2: 0,
n_pixels: 0,
n_overflow: 0,
data_start: 0,
overflow_start: HEADER_SIZE,
overflow_end: 0,
}
}
pub fn read_file<P: AsRef<Path>>(path: P) -> io::Result<MarFrame> {
let mut frame = MarFrame::new();
frame.read(path)?;
Ok(frame)
}
fn read<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
let mut reader = File::open(path)?;
let size = reader.metadata()?.len() as usize;
if size < HEADER_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"file is too short",
));
}
let mut data: Vec<u8> = Vec::with_capacity(size);
unsafe { data.set_len(size) };
reader.read_exact(&mut data)?;
let header: BinHeader = unsafe { std::ptr::read(data.as_ptr() as *const _) };
match header.format {
FORMAT_COMPRESSED => match header.magic_number {
MAGIC_NUMBER_LE => {
self.dim1 = header.dim1.to_le() as usize;
self.n_pixels = header.n_pixels.to_le() as usize;
self.dim2 = self.n_pixels / self.dim1;
self.n_overflow = header.n_overflow.to_le() as usize;
self.decompress(&data)?;
self.parse_overflow::<LittleEndian>(&data)
}
MAGIC_NUMBER_BE => {
self.dim1 = header.dim1.to_be() as usize;
self.n_pixels = header.n_pixels.to_be() as usize;
self.dim2 = self.n_pixels / self.dim1;
self.n_overflow = header.n_overflow.to_be() as usize;
self.decompress(&data)?;
self.parse_overflow::<BigEndian>(&data)
}
_ => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"could not parse magic number in the header",
))
}
},
FORMAT_SPIRAL => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"spiral format is not supported",
))
}
_ => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("file format is not recognized: {}", header.format),
))
}
}
}
fn decompress(&mut self, data: &[u8]) -> io::Result<()> {
let magic_string = format!(
"CCP4 packed image, X: {:04}, Y: {:04}\n",
self.dim1, self.dim2
);
let magic_bytes = magic_string.as_bytes();
for i in HEADER_SIZE..data.len() {
if data[i] != magic_bytes[0] {
continue;
}
if data.len() < i + magic_bytes.len() {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"not enough data in file",
));
}
let mut found = true;
for (c1, c2) in data[i..i + magic_bytes.len()].iter().zip(magic_bytes) {
if *c1 != *c2 {
found = false;
break;
}
}
if found {
self.overflow_end = i;
self.data_start = i + magic_bytes.len();
break;
}
}
if self.data_start == 0 {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
format!("could not find magic string: {}", magic_string),
));
}
if self.overflow_end - self.overflow_start < self.n_overflow * 2 {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"bad value for overflow",
));
}
self.unpack(&data[self.data_start..])?;
Ok(())
}
fn unpack(&mut self, data: &[u8]) -> io::Result<()> {
self.array = Array::with_data(
self.dim1 as usize,
self.dim2 as usize,
vec![0.; self.n_pixels as usize],
);
let mut i = 0;
let mut j = 0;
let mut num_error: u32 = 0;
let mut bit_offset: u32 = 0;
let mut num_bits: u32 = 0;
let mut t1 = data[j];
j += 1;
while i < self.n_pixels {
if num_error == 0 {
if bit_offset >= 8 - CCP4_PCK_BLOCK_HEADER_LENGTH {
if j > data.len() {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"not enough data in file",
));
}
let t2 = data[j];
j += 1;
t1 = (t1 >> bit_offset as u8) + (t2 << (8 - bit_offset) as u8);
num_error = CCP4_PCK_ERR_COUNT[(t1 & CCP4_PCK_MASK[3]) as usize];
num_bits = CCP4_PCK_BIT_COUNT[((t1 >> 3) & CCP4_PCK_MASK[3]) as usize];
bit_offset = CCP4_PCK_BLOCK_HEADER_LENGTH + bit_offset - 8;
t1 = t2;
} else {
num_error =
CCP4_PCK_ERR_COUNT[((t1 >> bit_offset as u8) & CCP4_PCK_MASK[3]) as usize];
num_bits = CCP4_PCK_BIT_COUNT
[((t1 >> (3 + bit_offset as u8)) & CCP4_PCK_MASK[3]) as usize];
bit_offset += CCP4_PCK_BLOCK_HEADER_LENGTH;
}
} else {
while num_error > 0 {
let mut err_val: i32 = 0;
let mut read_bits: u32 = 0;
while read_bits < num_bits {
if bit_offset + (num_bits - read_bits) >= 8 {
let conv =
(t1 >> bit_offset as u8) & CCP4_PCK_MASK[(8 - bit_offset) as usize];
err_val |= ((conv as u32) << read_bits) as i32;
read_bits += 8 - bit_offset;
bit_offset = 0;
if j > data.len() {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"not enough data in file",
));
}
t1 = data[j];
j += 1;
} else {
let conv = (t1 >> bit_offset as u8)
& CCP4_PCK_MASK[(num_bits - read_bits) as usize];
err_val |= ((conv as i32) << (read_bits as i32)) as i32;
bit_offset += num_bits - read_bits;
read_bits = num_bits;
}
}
if err_val & (1 << (num_bits - 1)) as i32 != 0 {
err_val |= -1 << (num_bits - 1) as i32;
}
if i > self.dim1 {
let x4 = (self.array[i - 1] as i16) as i32;
let x3 = (self.array[i - self.dim1 + 1] as i16) as i32;
let x2 = (self.array[i - self.dim1] as i16) as i32;
let x1 = (self.array[i - self.dim1 - 1] as i16) as i32;
self.array[i] = (err_val + (x4 + x3 + x2 + x1 + 2) / 4) as f64;
} else if i != 0 {
self.array[i] = (err_val + self.array[i - 1] as i32) as f64;
} else {
self.array[i] = err_val as f64;
}
i += 1;
num_error -= 1;
}
}
}
Ok(())
}
fn parse_overflow<F: ByteOrder>(&mut self, data: &[u8]) -> io::Result<()> {
let mut cursor = io::Cursor::new(&data[self.overflow_start..self.overflow_end]);
while self.n_overflow > 0 {
let address = cursor.read_u32::<F>()? as usize;
if address > 0 && address <= self.n_pixels {
self.array[address - 1] = cursor.read_u32::<F>()? as f64;
}
self.n_overflow -= 1;
}
Ok(())
}
}
impl Frame for MarFrame {
fn array(&self) -> &Array {
&self.array
}
fn header(&self) -> &Header {
&self.header
}
fn header_mut(&mut self) -> &mut Header {
&mut self.header
}
fn array_mut(&mut self) -> &mut Array {
&mut self.array
}
fn set_array(&mut self, array: Array) {
self.array = array;
}
}
#[repr(C)]
struct BinHeader {
magic_number: i32,
dim1: i32,
n_overflow: i32,
format: i32,
mode: i32,
n_pixels: i32,
pixel_length: i32,
pixel_height: i32,
wavelength: i32,
distance: i32,
start_phi: i32,
end_phi: i32,
start_omega: i32,
end_omega: i32,
chi: i32,
two_theta: i32,
}
const MAGIC_NUMBER: i32 = 1234;
const MAGIC_NUMBER_LE: i32 = MAGIC_NUMBER.to_le();
const MAGIC_NUMBER_BE: i32 = MAGIC_NUMBER.to_be();
const FORMAT_COMPRESSED: i32 = 1;
const FORMAT_SPIRAL: i32 = 2;
const HEADER_SIZE: usize = 4096;
const CCP4_PCK_BLOCK_HEADER_LENGTH: u32 = 6;
const CCP4_PCK_ERR_COUNT: [u32; 8] = [1, 2, 4, 8, 16, 32, 64, 128];
const CCP4_PCK_MASK: [u8; 9] = [0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF];
const CCP4_PCK_BIT_COUNT: [u32; 8] = [0, 4, 5, 6, 7, 8, 16, 32];
#[cfg(test)]
mod tests {
use crate::frame::Frame;
use crate::mar::MarFrame;
use std::path::Path;
fn read_test_file<P: AsRef<Path>>(path: P) {
let frame = MarFrame::read_file(path).unwrap();
if frame.dim1() != 32 || frame.dim2() != 32 {
panic!(
"Bad frame dimensions: expected 32x32, found {}x{}",
frame.dim1(),
frame.dim2(),
);
}
let sum = frame.sum();
if sum != 17347273. {
panic!("Expected sum is 17347273 but found {}", sum);
}
let min = frame.min();
if min != 9. {
panic!("Expected min is 9 but found {}", min);
}
let max = frame.max();
if max != 10000000. {
panic!("Expected max is 10000000. but found {}", max);
}
}
#[test]
fn test_mar_read() {
read_test_file("testdata/test.mar3450");
}
}