use byteorder::{BigEndian, ByteOrder, LittleEndian, NativeEndian, ReadBytesExt, WriteBytesExt};
use chrono::{Local, TimeZone};
use std::collections::HashMap;
use std::fs::File;
use std::io::{self, Error, ErrorKind, Seek, SeekFrom, Write};
extern crate askama;
use crate::frame::{Array, Frame, Header, HeaderEntry};
use askama::Template;
use std::path::Path;
pub struct CbfFrame {
header: Header,
user_header: Header,
array: Array,
datetime: chrono::DateTime<chrono::Local>,
bin_size: usize,
data_start: u64,
dim1: usize,
dim2: usize,
size: usize,
packed: Vec<u8>,
}
impl CbfFrame {
fn new() -> CbfFrame {
CbfFrame {
header: HashMap::new(),
user_header: HashMap::new(),
datetime: Local::now(),
bin_size: 0,
data_start: 0,
array: Array::new(),
dim1: 0,
dim2: 0,
size: 0,
packed: vec![],
}
}
pub fn save_array<P: Write>(
array: &Array,
header: Header,
name: &str,
mut writer: &mut P,
) -> io::Result<()> {
let mut cbf = CbfFrame::new();
cbf.user_header = header;
cbf.dim1 = array.dim1();
cbf.dim2 = array.dim2();
cbf.size = cbf.dim1 * cbf.dim2;
let packed = compress(array.data())?;
cbf.write(&mut writer, &packed, name)
}
fn write<P: Write>(&self, writer: &mut P, packed: &[u8], name: &str) -> io::Result<()> {
let pixel_size = match self.header.get(KEY_PIXELSIZE) {
Some(entry) => match entry {
HeaderEntry::Pixels(pixels) => format!("{:e} m x {:e} m", pixels[0], pixels[1]),
_ => "".to_string(),
},
None => "".to_string(),
};
let beam_xy = match self.header.get(KEY_BEAM_XY) {
Some(entry) => match entry {
HeaderEntry::Pixels(pixels) => format!("({}, {})", pixels[0], pixels[1]),
_ => "".to_string(),
},
None => "".to_string(),
};
let digest = md5::compute(&packed);
let digest = base64::encode(&digest[..]);
let tmpl = CbfHeaderTemplate {
name,
detector: self.get_header_str_or_empty(KEY_DETECTOR),
datetime: self.datetime.format(DT_FMT).to_string(),
pixel_size,
silicon: self.get_header_str_or_empty(KEY_SILICON),
exposure_time: self.get_header_float_as_string_or_empty(KEY_EXPOSURE_TIME, false),
exposure_period: self.get_header_float_as_string_or_empty(KEY_EXPOSURE_PERIOD, false),
tau: self.get_header_float_as_string_or_empty(KEY_TAU, true),
count_cutoff: self.get_header_int_as_string_or_empty(KEY_COUNT_CUTOFF),
threshold_setting: self
.get_header_float_as_string_or_empty(KEY_THRESHOLD_SETTING, false),
gain_setting: self.get_header_str_or_empty(KEY_GAIN_SETTING),
n_excluded_pixels: self.get_header_int_as_string_or_empty(KEY_N_EXCLUDED_PIXELS),
excluded_pixels: self.get_header_str_or_empty(KEY_EXCLUDED_PIXELS),
flat_field: self.get_header_str_or_empty(KEY_FLAT_FIELD),
trim_file: self.get_header_str_or_empty(KEY_TRIM_FILE),
image_path: self.get_header_str_or_empty(KEY_IMAGE_PATH),
ratcorr_lut_directory: self.get_header_str_or_empty(KEY_RATECORR_LUT_DIRECTORY),
retrigger_mode: self.get_header_str_or_empty(KEY_RETRIGGER_MODE),
wavelength: self.get_header_float_as_string_or_empty(KEY_WAVELENGTH, false),
start_angle: self.get_header_float_as_string_or_empty(KEY_START_ANGLE, false),
angle_increment: self.get_header_float_as_string_or_empty(KEY_ANGLE_INCREMENT, false),
omega: self.get_header_float_as_string_or_empty(KEY_OMEGA, false),
omega_increment: self.get_header_float_as_string_or_empty(KEY_OMEGA_INCREMENT, false),
phi: self.get_header_float_as_string_or_empty(KEY_PHI, false),
phi_increment: self.get_header_float_as_string_or_empty(KEY_PHI_INCREMENT, false),
kappa: self.get_header_float_as_string_or_empty(KEY_KAPPA, false),
oscillation_axis: self.get_header_str_or_empty(KEY_OSCILLATION_AXIS),
detector_distance: self
.get_header_float_as_string_or_empty(KEY_DETECTOR_DISTANCE, false),
detector_voffset: self.get_header_float_as_string_or_empty(KEY_DETECTOR_VOFFSET, false),
beam_xy,
flux: match self.get_header_i64(KEY_FLUX) {
Ok(v) => v,
Err(_) => 0,
},
temperature: self.get_header_float_as_string_or_empty(KEY_TEMPERATURE, false),
blower: self.get_header_float_as_string_or_empty(KEY_BLOWER, false),
lakeshore: self.get_header_float_as_string_or_empty(KEY_LAKESHORE, false),
x_binary_size: packed.len(),
content_md5: digest,
byte_order: if cfg!(target_endian = "little") {
BYTE_LE
} else {
BYTE_BE
},
x_binary_number_of_elements: self.size,
x_binary_size_fastest_dimension: self.dim2,
x_binary_size_second_dimension: self.dim1,
x_binary_size_padding: PADDING_SIZE,
user_header: &self.user_header,
};
writer.write_all(tmpl.render().unwrap().as_bytes())?;
let mut written = 0;
while written < MAGIC_SIZE {
written += writer.write(&DATA_START[written..])?;
}
written = 0;
while written < packed.len() {
written += writer.write(&packed[written..])?;
}
written = 0;
while written < PADDING_SIZE {
written += writer.write(&CBF_PADDING[written..])?;
}
written = 0;
while written < CBF_TAIL.len() {
written += writer.write(&CBF_TAIL[written..])?;
}
Ok(())
}
pub fn write_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
let mut writer = io::BufWriter::new(File::create(&path)?);
let stem = path.as_ref();
let stem = stem.to_path_buf();
let stem = stem.file_stem();
let name = if let Some(stem) = stem {
if let Some(stem) = stem.to_str() {
stem
} else {
"unknown"
}
} else {
"unknown"
};
self.pack()?;
self.write(&mut writer, &self.packed, name)
}
pub fn created(&self) -> &chrono::DateTime<chrono::Local> {
&self.datetime
}
pub fn name(&self) -> &str {
self.get_header_str_or_empty(KEY_DATA)
}
pub fn read_file<P: AsRef<Path>>(path: P) -> io::Result<CbfFrame> {
let mut frame = CbfFrame::new();
let mut reader = io::BufReader::new(File::open(path)?);
frame.read_header(&mut reader)?;
frame.set_array_parameters()?;
frame.read_array(&mut reader)?;
frame.unpack()?;
Ok(frame)
}
pub fn read_buffer<R: io::BufRead + Seek>(mut reader: R, unpack: bool) -> io::Result<CbfFrame> {
let mut frame = CbfFrame::new();
frame.read_header(&mut reader)?;
frame.set_array_parameters()?;
frame.read_array(&mut reader)?;
if unpack {
frame.unpack()?;
}
Ok(frame)
}
fn read_array<R: io::BufRead + Seek>(&mut self, reader: &mut R) -> io::Result<()> {
reader.seek(SeekFrom::Start(self.data_start))?;
self.packed = Vec::with_capacity(self.bin_size);
unsafe { self.packed.set_len(self.bin_size) };
reader.read_exact(&mut self.packed)
}
pub fn unpack(&mut self) -> io::Result<()> {
if self.packed.is_empty() {
return Ok(());
}
self.check_md5()?;
match self.get_header_str(KEY_ENDIANNESS) {
Ok(value) => match value.trim() {
BYTE_LE => self.decompress::<LittleEndian>()?,
BYTE_BE => self.decompress::<BigEndian>()?,
_ => {
return Err(Error::new(
ErrorKind::InvalidData,
format!("byteorder is not recognized: {}", value),
))
}
},
Err(e) => return Err(e),
}
self.packed = vec![];
Ok(())
}
fn check_md5(&self) -> io::Result<()> {
let md5 = self.get_header_str(KEY_MD5)?;
let digest = md5::compute(&self.packed);
let digest = base64::encode(&digest[..]);
if md5 != digest {
return Err(Error::new(
ErrorKind::InvalidData,
"md5 header entry does not fit the binary data",
));
}
Ok(())
}
fn decompress<T: ByteOrder>(&mut self) -> io::Result<()> {
self.array = Array::with_dims(self.dim1, self.dim2);
let mut reader = io::Cursor::new(&self.packed);
let mut pixel: i32 = 0;
while self.array.len() < self.size {
let d8 = reader.read_i8()?;
if d8 == OVERFLOW_I8 {
let d16 = reader.read_i16::<T>()?;
if d16 == OVERFLOW_I16 {
pixel += reader.read_i32::<T>()?;
} else {
pixel += d16 as i32;
}
} else {
pixel += d8 as i32;
}
self.array.push(pixel as f64);
}
Ok(())
}
fn set_array_parameters(&mut self) -> io::Result<()> {
self.dim1 = self.get_header_i64(KEY_DIM1)? as usize;
self.dim2 = self.get_header_i64(KEY_DIM2)? as usize;
self.size = self.get_header_i64(KEY_N_ELEMENTS)? as usize;
self.bin_size = self.get_header_i64(KEY_BINARY_SIZE)? as usize;
if self.size != self.dim1 * self.dim2 {
return Err(Error::new(
ErrorKind::InvalidData,
"size and dimensions are not consistent",
));
}
Ok(())
}
fn check_magic_bytes<R: io::BufRead + Seek>(&mut self, reader: &mut R) -> io::Result<()> {
let mut n = 1;
for chr in DATA_START[1..].iter() {
if reader.read_u8()? == *chr {
n += 1;
} else {
break;
}
}
if n == MAGIC_SIZE {
self.data_start = reader.seek(SeekFrom::Current(0))?;
} else {
reader.seek(SeekFrom::Current(-(n as i64)))?;
}
Ok(())
}
fn read_header<R: io::BufRead + Seek>(&mut self, reader: &mut R) -> io::Result<()> {
let mut line: Vec<u8> = Vec::with_capacity(HEADER_CHUNK_SIZE);
while self.data_start == 0 {
let chr = reader.read_u8()?;
if chr == LINE_END {
self.parse_header_line(&line);
line.clear();
} else if chr == DATA_START[0] {
self.check_magic_bytes(reader)?;
} else {
line.push(chr);
}
}
Ok(())
}
pub fn header(&self) -> &Header {
&self.header
}
fn parse_header_line(&mut self, line: &[u8]) {
let line = unsafe { std::str::from_utf8_unchecked(line) }.trim();
if line.is_empty() {
return;
}
if line.starts_with(KEY_DATA) {
self.parse_string_header_value(line, KEY_DATA);
} else if line.starts_with(KEY_DETECTOR) {
self.parse_string_header_value(line, KEY_DETECTOR);
} else if line.starts_with(KEY_GAIN_SETTING) {
self.parse_string_header_value(line, KEY_GAIN_SETTING);
} else if line.starts_with(KEY_PIXELSIZE) {
self.parse_pixels_header_value(line, KEY_PIXELSIZE, 2, 5)
} else if line.starts_with(KEY_BEAM_XY) {
self.parse_pixels_header_value(line, KEY_BEAM_XY, 2, 3)
} else if line.starts_with(KEY_SILICON) {
self.parse_string_header_value(line, KEY_SILICON);
} else if line.starts_with(KEY_MD5) {
self.parse_string_header_value(line, KEY_MD5);
} else if line.starts_with(KEY_FLAT_FIELD) {
self.parse_string_header_value(line, KEY_FLAT_FIELD);
} else if line.starts_with(KEY_TRIM_FILE) {
self.parse_string_header_value(line, KEY_TRIM_FILE);
} else if line.starts_with(KEY_RETRIGGER_MODE) {
self.parse_string_header_value(line, KEY_RETRIGGER_MODE);
} else if line.starts_with(KEY_RATECORR_LUT_DIRECTORY) {
self.parse_string_header_value(line, KEY_RATECORR_LUT_DIRECTORY);
} else if line.starts_with(KEY_OSCILLATION_AXIS) {
self.parse_string_header_value(line, KEY_OSCILLATION_AXIS);
} else if line.starts_with(KEY_IMAGE_PATH) {
self.parse_string_header_value(line, KEY_IMAGE_PATH);
} else if line.starts_with(KEY_EXPOSURE_TIME) {
self.parse_float_header_value(line, KEY_EXPOSURE_TIME, 2);
} else if line.starts_with(KEY_EXPOSURE_PERIOD) {
self.parse_float_header_value(line, KEY_EXPOSURE_PERIOD, 2);
} else if line.starts_with(KEY_TAU) {
self.parse_float_header_value(line, KEY_TAU, 3);
} else if line.starts_with(KEY_THRESHOLD_SETTING) {
self.parse_float_header_value(line, KEY_THRESHOLD_SETTING, 2);
} else if line.starts_with(KEY_EXCLUDED_PIXELS) {
self.parse_string_header_value(line, KEY_EXCLUDED_PIXELS);
} else if line.starts_with(KEY_WAVELENGTH) {
self.parse_float_header_value(line, KEY_WAVELENGTH, 2);
} else if line.starts_with(KEY_START_ANGLE) {
self.parse_float_header_value(line, KEY_START_ANGLE, 2);
} else if line.starts_with(KEY_ANGLE_INCREMENT) {
self.parse_float_header_value(line, KEY_ANGLE_INCREMENT, 2);
} else if line.starts_with(KEY_OMEGA) {
self.parse_float_header_value(line, KEY_OMEGA, 2);
} else if line.starts_with(KEY_OMEGA_INCREMENT) {
self.parse_float_header_value(line, KEY_OMEGA_INCREMENT, 2);
} else if line.starts_with(KEY_PHI) {
self.parse_float_header_value(line, KEY_PHI, 2);
} else if line.starts_with(KEY_PHI_INCREMENT) {
self.parse_float_header_value(line, KEY_PHI_INCREMENT, 2);
} else if line.starts_with(KEY_KAPPA) {
self.parse_float_header_value(line, KEY_KAPPA, 2);
} else if line.starts_with(KEY_DETECTOR_DISTANCE) {
self.parse_float_header_value(line, KEY_DETECTOR_DISTANCE, 2);
} else if line.starts_with(KEY_DETECTOR_VOFFSET) {
self.parse_float_header_value(line, KEY_DETECTOR_VOFFSET, 2);
} else if line.starts_with(KEY_FLUX) {
self.parse_float_header_value(line, KEY_FLUX, 2);
} else if line.starts_with(KEY_TEMPERATURE) {
self.parse_float_header_value(line, KEY_TEMPERATURE, 2);
} else if line.starts_with(KEY_BLOWER) {
self.parse_float_header_value(line, KEY_BLOWER, 2);
} else if line.starts_with(KEY_LAKESHORE) {
self.parse_float_header_value(line, KEY_LAKESHORE, 2);
} else if line.starts_with(KEY_COUNT_CUTOFF) {
self.parse_int_header_value(line, KEY_COUNT_CUTOFF, 2);
} else if line.starts_with(KEY_N_EXCLUDED_PIXELS) {
self.parse_int_header_value(line, KEY_N_EXCLUDED_PIXELS, 3);
} else if line.starts_with(KEY_BINARY_SIZE) {
self.parse_int_header_value(line, KEY_BINARY_SIZE, 1);
} else if line.starts_with(KEY_N_ELEMENTS) {
self.parse_int_header_value(line, KEY_N_ELEMENTS, 1);
} else if line.starts_with(KEY_DIM1) {
self.parse_int_header_value(line, KEY_DIM1, 1);
} else if line.starts_with(KEY_DIM2) {
self.parse_int_header_value(line, KEY_DIM2, 1);
} else if line.starts_with(KEY_ENDIANNESS) {
self.parse_string_header_value(line, KEY_ENDIANNESS);
} else if let Ok(v) = chrono::Local.datetime_from_str(line, DT_FMT) {
self.datetime = v;
} else {
self.just_save_as_string(line);
}
}
fn just_save_as_string(&mut self, line: &str) {
let x: &[_] = &['#', ':', '='];
let line = line.trim_matches(x);
let sline: Vec<&str> = line.split_ascii_whitespace().collect();
if sline.len() > 1 {
self.header.insert(
sline[0].to_string(),
HeaderEntry::String(sline[1..].join(" ")),
);
} else {
self.header.insert(line.to_string(), HeaderEntry::Empty);
}
}
fn parse_float_header_value(&mut self, line: &str, key: &'static str, n: usize) {
let s: Vec<&str> = line.split_ascii_whitespace().collect();
if s.len() > n {
if let Ok(v) = s[n].parse::<f64>() {
self.header.insert(key.to_string(), HeaderEntry::Float(v));
}
}
}
fn parse_int_header_value(&mut self, line: &str, key: &'static str, n: usize) {
let s: Vec<&str> = line.split_ascii_whitespace().collect();
if s.len() > n {
if let Ok(v) = s[n].parse::<i64>() {
self.header.insert(key.to_string(), HeaderEntry::Number(v));
}
}
}
fn parse_pixels_header_value(&mut self, line: &str, key: &'static str, n1: usize, n2: usize) {
let x: &[_] = &['(', ')', ','];
let sline: Vec<&str> = line.split_ascii_whitespace().collect();
if sline.len() > n2 {
if let Ok(p1) = sline[n1].trim_matches(x).parse::<f64>() {
if let Ok(p2) = sline[n2].trim_matches(x).parse::<f64>() {
self.header
.insert(key.to_string(), HeaderEntry::Pixels([p1, p2]));
}
}
}
}
fn parse_string_header_value(&mut self, line: &str, key: &'static str) {
let n = key.len();
if n < line.len() {
self.header
.insert(key.to_string(), HeaderEntry::String(line[n..].to_string()));
}
}
fn pack(&mut self) -> io::Result<()> {
if self.packed.is_empty() {
self.packed = compress(&self.array.data())?;
}
Ok(())
}
}
fn compress(data: &[f64]) -> io::Result<Vec<u8>> {
let mut cval = 0;
let mut adiff;
let mut packed = vec![];
for value in data {
let nval = *value as i32;
let diff = nval - cval;
adiff = if diff >= 0 { diff } else { -diff };
if adiff < OVERFLOW_I32_1 {
packed.write_i8(diff as i8)?;
} else {
packed.write_i8(OVERFLOW_I8)?;
if adiff < OVERFLOW_I32_2 {
packed.write_i16::<NativeEndian>(diff as i16)?;
} else {
packed.write_i16::<NativeEndian>(OVERFLOW_I16)?;
packed.write_i32::<NativeEndian>(diff)?;
}
}
cval = nval;
}
return Ok(packed);
}
pub const KEY_DATA: &'static str = "data_";
pub const KEY_DETECTOR: &'static str = "# Detector: ";
pub const KEY_PIXELSIZE: &'static str = "# Pixel_size ";
pub const KEY_SILICON: &'static str = "# Silicon ";
pub const KEY_EXPOSURE_TIME: &'static str = "# Exposure_time ";
pub const KEY_EXPOSURE_PERIOD: &'static str = "# Exposure_period ";
pub const KEY_TAU: &'static str = "# Tau ";
pub const KEY_COUNT_CUTOFF: &'static str = "# Count_cutoff ";
pub const KEY_THRESHOLD_SETTING: &'static str = "# Threshold_setting: ";
pub const KEY_GAIN_SETTING: &'static str = "# Gain_setting: ";
pub const KEY_N_EXCLUDED_PIXELS: &'static str = "# N_excluded_pixels ";
pub const KEY_EXCLUDED_PIXELS: &'static str = "# Excluded_pixels: ";
pub const KEY_RATECORR_LUT_DIRECTORY: &'static str = "# Ratecorr_lut_directory: ";
pub const KEY_RETRIGGER_MODE: &'static str = "# Retrigger_mode: ";
pub const KEY_FLAT_FIELD: &'static str = "# Flat_field: ";
pub const KEY_TRIM_FILE: &'static str = "# Trim_file: ";
pub const KEY_IMAGE_PATH: &'static str = "# Image_path: ";
pub const KEY_OSCILLATION_AXIS: &'static str = "# Oscillation_axis ";
pub const KEY_BEAM_XY: &'static str = "# Beam_xy ";
pub const KEY_WAVELENGTH: &'static str = "# Wavelength ";
pub const KEY_START_ANGLE: &'static str = "# Start_angle ";
pub const KEY_ANGLE_INCREMENT: &'static str = "# Angle_increment ";
pub const KEY_OMEGA: &'static str = "# Omega ";
pub const KEY_OMEGA_INCREMENT: &'static str = "# Omega_increment ";
pub const KEY_PHI: &'static str = "# Phi ";
pub const KEY_PHI_INCREMENT: &'static str = "# Phi_increment ";
pub const KEY_KAPPA: &'static str = "# Kappa ";
pub const KEY_DETECTOR_DISTANCE: &'static str = "# Detector_distance ";
pub const KEY_DETECTOR_VOFFSET: &'static str = "# Detector_Voffset ";
pub const KEY_FLUX: &'static str = "# Flux ";
pub const KEY_TEMPERATURE: &'static str = "# Temperature ";
pub const KEY_BLOWER: &'static str = "# Blower ";
pub const KEY_LAKESHORE: &'static str = "# Lakeshore ";
const LINE_END: u8 = 0x0a;
const HEADER_CHUNK_SIZE: usize = 128;
const MAGIC_SIZE: usize = 4;
const DATA_START: [u8; MAGIC_SIZE] = [0x0c, 0x1a, 0x04, 0xd5];
const DT_FMT: &'static str = "# %Y-%m-%dT%H:%M:%S%.f";
const KEY_MD5: &'static str = "Content-MD5: ";
const KEY_BINARY_SIZE: &'static str = "X-Binary-Size: ";
const KEY_N_ELEMENTS: &'static str = "X-Binary-Number-of-Elements: ";
const KEY_DIM2: &'static str = "X-Binary-Size-Fastest-Dimension: ";
const KEY_DIM1: &'static str = "X-Binary-Size-Second-Dimension: ";
const KEY_ENDIANNESS: &'static str = "X-Binary-Element-Byte-Order: ";
const OVERFLOW_I8: i8 = -0x80;
const OVERFLOW_I16: i16 = -0x8000;
const OVERFLOW_I32_1: i32 = 0x80;
const OVERFLOW_I32_2: i32 = 0x8000;
const PADDING_SIZE: usize = 4095;
const CBF_PADDING: [u8; PADDING_SIZE] = [0; PADDING_SIZE];
const CBF_TAIL: &'static [u8] = b"\r\n--CIF-BINARY-FORMAT-SECTION----\r\n;\r\n\r\n";
const BYTE_LE: &'static str = "LITTLE_ENDIAN";
const BYTE_BE: &'static str = "BIG_ENDIAN";
#[derive(Template)]
#[template(path = "cbf_header.jinja2", escape = "none")]
struct CbfHeaderTemplate<'a> {
name: &'a str,
detector: &'a str,
datetime: String,
pixel_size: String,
silicon: &'a str,
exposure_time: String,
exposure_period: String,
tau: String,
count_cutoff: String,
threshold_setting: String,
gain_setting: &'a str,
n_excluded_pixels: String,
excluded_pixels: &'a str,
flat_field: &'a str,
trim_file: &'a str,
image_path: &'a str,
ratcorr_lut_directory: &'a str,
retrigger_mode: &'a str,
wavelength: String,
start_angle: String,
angle_increment: String,
omega: String,
omega_increment: String,
phi: String,
phi_increment: String,
kappa: String,
oscillation_axis: &'a str,
detector_distance: String,
detector_voffset: String,
beam_xy: String,
flux: i64,
temperature: String,
blower: String,
lakeshore: String,
x_binary_size: usize,
byte_order: &'a str,
content_md5: String,
x_binary_number_of_elements: usize,
x_binary_size_fastest_dimension: usize,
x_binary_size_second_dimension: usize,
x_binary_size_padding: usize,
user_header: &'a Header,
}
impl Frame for CbfFrame {
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;
}
}
#[cfg(test)]
mod tests {
use crate::cbf::CbfFrame;
use crate::frame::{Frame, HeaderEntry};
use std::collections::HashMap;
use std::fs::File;
use std::io;
use std::io::Read;
use std::path::Path;
fn test_header() -> HashMap<String, HeaderEntry> {
let mut header: HashMap<String, HeaderEntry> = HashMap::new();
header.insert(
String::from("# Detector: "),
HeaderEntry::String(String::from("PILATUS 2M 24-0111")),
);
header.insert(
String::from("# Pixel_size "),
HeaderEntry::Pixels([0.000172, 0.000172]),
);
header.insert(
String::from("# Silicon "),
HeaderEntry::String(String::from("sensor, thickness 0.000450 m")),
);
header.insert(
String::from("# Exposure_time "),
HeaderEntry::Float(19.9977),
);
header.insert(
String::from("# Exposure_period "),
HeaderEntry::Float(19.9977),
);
header.insert(String::from("# Tau "), HeaderEntry::Float(1.24e-07));
header.insert(
String::from("# Count_cutoff "),
HeaderEntry::Number(1055459),
);
header.insert(
String::from("# Threshold_setting: "),
HeaderEntry::Float(8997.0),
);
header.insert(
String::from("# Gain_setting: "),
HeaderEntry::String(String::from("low gain (vrf = -0.300)")),
);
header.insert(
String::from("# N_excluded_pixels "),
HeaderEntry::Number(184),
);
header.insert(
String::from("# Excluded_pixels: "),
HeaderEntry::String(String::from("badpix_mask.tif")),
);
header.insert(
String::from("# Flat_field: "),
HeaderEntry::String(String::from("FF_p2m0111_E26000_T13000_vrf_m0p30.tif")),
);
header.insert(
String::from("# Trim_file: "),
HeaderEntry::String(String::from("p2m0111_E26000_T13000_vrf_m0p30.bin")),
);
header.insert(
String::from("# Image_path: "),
HeaderEntry::String(String::from("/ramdisk/")),
);
header.insert(String::from("# Wavelength "), HeaderEntry::Float(0.68884));
header.insert(
String::from("# Detector_distance "),
HeaderEntry::Float(0.344),
);
header.insert(
String::from("# Detector_Voffset "),
HeaderEntry::Float(0.06),
);
header.insert(
String::from("# Beam_xy "),
HeaderEntry::Pixels([732.63, 1490.43]),
);
header.insert(String::from("# Flux "), HeaderEntry::Float(9335617.0));
header.insert(String::from("# Start_angle "), HeaderEntry::Float(0.0));
header.insert(
String::from("# Angle_increment "),
HeaderEntry::Float(180.0),
);
header.insert(String::from("# Kappa "), HeaderEntry::Float(0.0));
header.insert(String::from("# Phi "), HeaderEntry::Float(5.0));
header.insert(String::from("# Phi_increment "), HeaderEntry::Float(0.0));
header.insert(String::from("# Omega "), HeaderEntry::Float(0.0));
header.insert(
String::from("# Omega_increment "),
HeaderEntry::Float(180.0),
);
header.insert(
String::from("# Oscillation_axis "),
HeaderEntry::String(String::from("OMEGA")),
);
header.insert(
String::from("data_"),
HeaderEntry::String(String::from("lab6")),
);
header.insert(String::from("# Temperature "), HeaderEntry::Float(292.99));
header.insert(String::from("# Blower "), HeaderEntry::Float(10.0));
header.insert(String::from("# Lakeshore "), HeaderEntry::Float(20.0));
header
}
fn compare(frame: CbfFrame) {
let header1 = test_header();
let header2 = frame.header();
if frame.datetime.timestamp_nanos() != 1515157217431940000 {
panic!("Bad datetime");
};
if frame.dim1 != 64 || frame.dim2 != 64 || frame.size != 4096 {
panic!(
"Bad dimensions, should be 64x64, found {}x{}",
frame.dim1, frame.dim2
);
}
for (key, value1) in header1 {
match header2.get(key.as_str()) {
None => panic!("Key '{}' does not exist", key),
Some(value2) => {
if value2 != &value1 {
panic!(
"Values are different for key {}: expected '{:?}', found '{:?}'",
key, value1, value2
);
}
}
}
}
let sum = frame.sum();
if sum != 2024362444.0 {
panic!("Array sum is wrong: {}", sum);
}
}
fn test_cbf_read_filename<P: AsRef<Path>>(path: P) {
let frame = CbfFrame::read_file(path).unwrap();
compare(frame);
}
#[test]
fn test_cbf_read() {
test_cbf_read_filename("testdata/lab6.cbf");
}
#[test]
fn test_cbf_writer() {
let mut frame = CbfFrame::read_file("testdata/lab6.cbf").unwrap();
frame.write_file("/tmp/lab6.cbf").unwrap();
test_cbf_read_filename("/tmp/lab6.cbf");
}
#[test]
fn test_cbf_buffer_reader() {
let mut file = File::open("testdata/lab6.cbf").unwrap();
let mut data = vec![];
file.read_to_end(&mut data).unwrap();
let data = io::Cursor::new(data);
let frame = CbfFrame::read_buffer(data, true).unwrap();
compare(frame);
}
}