use crate::error::{Error, Result};
use crate::tag::{Tag, TagGroup, TagId};
use crate::value::format_g15;
use crate::value::Value;
fn mk(name: &str, value: Value) -> Tag {
let pv = value.to_display_string();
Tag {
id: TagId::Text(name.to_string()),
name: name.to_string(),
description: name.to_string(),
group: TagGroup {
family0: "File".into(),
family1: "File".into(),
family2: "Image".into(),
},
raw_value: value,
print_value: pv,
priority: 0,
}
}
fn mk_print(name: &str, value: Value, print: String) -> Tag {
Tag {
id: TagId::Text(name.to_string()),
name: name.to_string(),
description: name.to_string(),
group: TagGroup {
family0: "File".into(),
family1: "File".into(),
family2: "Image".into(),
},
raw_value: value,
print_value: print,
priority: 0,
}
}
fn mk_time(name: &str, value: Value, print: String) -> Tag {
Tag {
id: TagId::Text(name.to_string()),
name: name.to_string(),
description: name.to_string(),
group: TagGroup {
family0: "File".into(),
family1: "File".into(),
family2: "Time".into(),
},
raw_value: value,
print_value: print,
priority: 0,
}
}
fn read_u32_le(data: &[u8], offset: usize) -> u32 {
if offset + 4 > data.len() {
return 0;
}
u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
}
fn read_i32_le(data: &[u8], offset: usize) -> i32 {
if offset + 4 > data.len() {
return 0;
}
i32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
}
fn read_f32_le(data: &[u8], offset: usize) -> f32 {
if offset + 4 > data.len() {
return 0.0;
}
f32::from_bits(u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]))
}
fn read_f64_le(data: &[u8], offset: usize) -> f64 {
if offset + 8 > data.len() {
return 0.0;
}
f64::from_bits(u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]))
}
fn read_u64_le(data: &[u8], offset: usize) -> u64 {
if offset + 8 > data.len() {
return 0;
}
u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
])
}
fn read_str(data: &[u8], offset: usize, len: usize) -> String {
if offset + len > data.len() {
return String::new();
}
let s = &data[offset..offset + len];
let end = s.iter().position(|&b| b == 0).unwrap_or(len);
crate::encoding::decode_utf8_or_latin1(&s[..end])
.trim_end()
.to_string()
}
fn unix_to_exif_datetime(secs: i64) -> String {
if secs < 0 {
return String::new();
}
let days = secs / 86400;
let time_secs = secs % 86400;
let h = time_secs / 3600;
let m = (time_secs % 3600) / 60;
let s = time_secs % 60;
let mut y = 1970i32;
let mut rem = days;
loop {
let dy = if (y % 4 == 0 && y % 100 != 0) || y % 400 == 0 {
366
} else {
365
};
if rem < dy {
break;
}
rem -= dy;
y += 1;
}
let leap = (y % 4 == 0 && y % 100 != 0) || y % 400 == 0;
let months = [
31i64,
if leap { 29 } else { 28 },
31,
30,
31,
30,
31,
31,
30,
31,
30,
31,
];
let mut mo = 1;
for &dm in &months {
if rem < dm {
break;
}
rem -= dm;
mo += 1;
}
format!(
"{:04}:{:02}:{:02} {:02}:{:02}:{:02}",
y,
mo,
rem + 1,
h,
m,
s
)
}
fn ole_date_to_datetime(ole_days: f64) -> String {
let unix_secs_f = (ole_days - 25569.0) * 86400.0;
let itime = unix_secs_f.floor() as i64;
let frac = unix_secs_f - itime as f64;
let rounded = if frac >= 0.5 { itime + 1 } else { itime };
unix_to_exif_datetime(rounded)
}
fn usecs_to_datetime(usecs: u64) -> String {
let secs = usecs / 1_000_000;
let frac_usecs = usecs % 1_000_000;
let base = unix_to_exif_datetime(secs as i64);
if frac_usecs == 0 {
format!("{}Z", base)
} else {
format!("{}.{:06}Z", base, frac_usecs)
}
}
fn process_mrc_header(data: &[u8], tags: &mut Vec<Tag>) -> (u32, u32, String) {
let image_width = read_u32_le(data, 0);
tags.push(mk("ImageWidth", Value::U32(image_width)));
let image_height = read_u32_le(data, 4);
tags.push(mk("ImageHeight", Value::U32(image_height)));
let image_depth = read_u32_le(data, 8);
tags.push(mk("ImageDepth", Value::U32(image_depth)));
let image_mode = read_u32_le(data, 12);
let mode_str = match image_mode {
0 => "8-bit signed integer",
1 => "16-bit signed integer",
2 => "32-bit signed real",
3 => "complex 16-bit integer",
4 => "complex 32-bit real",
6 => "16-bit unsigned integer",
_ => "Unknown",
};
tags.push(mk_print(
"ImageMode",
Value::U32(image_mode),
mode_str.to_string(),
));
{
let a = read_i32_le(data, 16);
let b = read_i32_le(data, 20);
let c = read_i32_le(data, 24);
let v = Value::List(vec![Value::I32(a), Value::I32(b), Value::I32(c)]);
let print = format!("{} {} {}", a, b, c);
tags.push(mk_print("StartPoint", v, print));
}
{
let a = read_u32_le(data, 28);
let b = read_u32_le(data, 32);
let c = read_u32_le(data, 36);
let v = Value::List(vec![Value::U32(a), Value::U32(b), Value::U32(c)]);
let print = format!("{} {} {}", a, b, c);
tags.push(mk_print("GridSize", v, print));
}
{
let v = read_f32_le(data, 40) as f64;
tags.push(mk_print("CellWidth", Value::F64(v), format_g15(v)));
}
{
let v = read_f32_le(data, 44) as f64;
tags.push(mk_print("CellHeight", Value::F64(v), format_g15(v)));
}
{
let v = read_f32_le(data, 48) as f64;
tags.push(mk_print("CellDepth", Value::F64(v), format_g15(v)));
}
{
let v = read_f32_le(data, 52) as f64;
tags.push(mk_print("CellAlpha", Value::F64(v), format_g15(v)));
}
{
let v = read_f32_le(data, 56) as f64;
tags.push(mk_print("CellBeta", Value::F64(v), format_g15(v)));
}
{
let v = read_f32_le(data, 60) as f64;
tags.push(mk_print("CellGamma", Value::F64(v), format_g15(v)));
}
{
let v = read_u32_le(data, 64);
let print = match v {
1 => "X",
2 => "Y",
3 => "Z",
_ => "Unknown",
};
tags.push(mk_print("ImageWidthAxis", Value::U32(v), print.to_string()));
}
{
let v = read_u32_le(data, 68);
let print = match v {
1 => "X",
2 => "Y",
3 => "Z",
_ => "Unknown",
};
tags.push(mk_print(
"ImageHeightAxis",
Value::U32(v),
print.to_string(),
));
}
{
let v = read_u32_le(data, 72);
let print = match v {
1 => "X",
2 => "Y",
3 => "Z",
_ => "Unknown",
};
tags.push(mk_print("ImageDepthAxis", Value::U32(v), print.to_string()));
}
{
let v = read_f32_le(data, 76) as f64;
tags.push(mk_print("DensityMin", Value::F64(v), format_g15(v)));
}
{
let v = read_f32_le(data, 80) as f64;
tags.push(mk_print("DensityMax", Value::F64(v), format_g15(v)));
}
{
let v = read_f32_le(data, 84) as f64;
tags.push(mk_print("DensityMean", Value::F64(v), format_g15(v)));
}
{
let v = read_u32_le(data, 88);
tags.push(mk("SpaceGroupNumber", Value::U32(v)));
}
let ext_hdr_size = read_u32_le(data, 92);
tags.push(mk("ExtendedHeaderSize", Value::U32(ext_hdr_size)));
let ext_hdr_type = read_str(data, 104, 4);
tags.push(mk(
"ExtendedHeaderType",
Value::String(ext_hdr_type.clone()),
));
{
let v = read_u32_le(data, 108);
tags.push(mk("MRCVersion", Value::U32(v)));
}
{
let a = read_f32_le(data, 196) as f64;
let b = read_f32_le(data, 200) as f64;
let c = read_f32_le(data, 204) as f64;
let v = Value::List(vec![Value::F64(a), Value::F64(b), Value::F64(c)]);
let print = format!("{} {} {}", format_g15(a), format_g15(b), format_g15(c));
tags.push(mk_print("Origin", v, print));
}
if data.len() >= 216 {
let b0 = data[212];
let b1 = data[213];
let b2 = data[214];
let b3 = data[215];
let print = format!("0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x}", b0, b1, b2, b3);
let v = Value::List(vec![
Value::U8(b0),
Value::U8(b1),
Value::U8(b2),
Value::U8(b3),
]);
tags.push(mk_print("MachineStamp", v, print));
}
{
let v = read_f32_le(data, 216) as f64;
tags.push(mk_print("RMSDeviation", Value::F64(v), format_g15(v)));
}
let n_lab = read_u32_le(data, 220);
tags.push(mk("NumberOfLabels", Value::U32(n_lab)));
let label_names = [
"Label0", "Label1", "Label2", "Label3", "Label4", "Label5", "Label6", "Label7", "Label8",
"Label9",
];
for (i, &label_name) in label_names.iter().enumerate() {
if n_lab as usize > i {
let offset = 224 + i * 80;
let s = read_str(data, offset, 80);
if !s.is_empty() {
tags.push(mk(label_name, Value::String(s)));
}
}
}
(ext_hdr_size, image_depth, ext_hdr_type)
}
fn process_fei12_header(data: &[u8], tags: &mut Vec<Tag>) {
if data.len() < 4 {
return;
}
let metadata_size = read_u32_le(data, 0);
tags.push(mk("MetadataSize", Value::U32(metadata_size)));
let metadata_version = read_u32_le(data, 4);
tags.push(mk("MetadataVersion", Value::U32(metadata_version)));
let bitmask1 = read_u32_le(data, 8);
let bm1_print = format!("0x{:08x}", bitmask1);
tags.push(mk_print("Bitmask1", Value::U32(bitmask1), bm1_print));
let mut bitm = bitmask1;
if bitm & 0x01 != 0 {
let v = read_f64_le(data, 12);
let dt = ole_date_to_datetime(v);
tags.push(mk_time("TimeStamp", Value::F64(v), dt));
}
if bitm & 0x02 != 0 {
let s = read_str(data, 20, 16);
tags.push(mk("MicroscopeType", Value::String(s)));
}
if bitm & 0x04 != 0 {
let s = read_str(data, 36, 16);
tags.push(mk("MicroscopeID", Value::String(s)));
}
if bitm & 0x08 != 0 {
let s = read_str(data, 52, 16);
tags.push(mk("Application", Value::String(s)));
}
if bitm & 0x10 != 0 {
let s = read_str(data, 68, 16);
tags.push(mk("AppVersion", Value::String(s)));
}
if bitm & 0x20 != 0 {
let v = read_f64_le(data, 84);
tags.push(mk_print("HighTension", Value::F64(v), format_g15(v)));
}
if bitm & 0x40 != 0 {
let v = read_f64_le(data, 92);
tags.push(mk_print("Dose", Value::F64(v), format_g15(v)));
}
if bitm & 0x80 != 0 {
let v = read_f64_le(data, 100);
tags.push(mk_print("AlphaTilt", Value::F64(v), format_g15(v)));
}
if bitm & 0x100 != 0 {
let v = read_f64_le(data, 108);
tags.push(mk_print("BetaTilt", Value::F64(v), format_g15(v)));
}
if bitm & 0x200 != 0 {
let v = read_f64_le(data, 116);
tags.push(mk_print("XStage", Value::F64(v), format_g15(v)));
}
if bitm & 0x400 != 0 {
let v = read_f64_le(data, 124);
tags.push(mk_print("YStage", Value::F64(v), format_g15(v)));
}
if bitm & 0x800 != 0 {
let v = read_f64_le(data, 132);
tags.push(mk_print("ZStage", Value::F64(v), format_g15(v)));
}
if bitm & 0x1000 != 0 {
let v = read_f64_le(data, 140);
tags.push(mk_print("TiltAxisAngle", Value::F64(v), format_g15(v)));
}
if bitm & 0x2000 != 0 {
let v = read_f64_le(data, 148);
tags.push(mk_print("DualAxisRot", Value::F64(v), format_g15(v)));
}
if bitm & 0x4000 != 0 {
let v = read_f64_le(data, 156);
tags.push(mk_print("PixelSizeX", Value::F64(v), format_g15(v)));
}
if bitm & 0x8000 != 0 {
let v = read_f64_le(data, 164);
tags.push(mk_print("PixelSizeY", Value::F64(v), format_g15(v)));
}
if bitm & 0x400000 != 0 {
let v = read_f64_le(data, 220);
tags.push(mk_print("Defocus", Value::F64(v), format_g15(v)));
}
if bitm & 0x800000 != 0 {
let v = read_f64_le(data, 228);
tags.push(mk_print("STEMDefocus", Value::F64(v), format_g15(v)));
}
if bitm & 0x1000000 != 0 {
let v = read_f64_le(data, 236);
tags.push(mk_print("AppliedDefocus", Value::F64(v), format_g15(v)));
}
if bitm & 0x2000000 != 0 {
let v = read_u32_le(data, 244);
let print = match v {
1 => "TEM",
2 => "STEM",
_ => "Unknown",
};
tags.push(mk_print("InstrumentMode", Value::U32(v), print.to_string()));
}
if bitm & 0x4000000 != 0 {
let v = read_u32_le(data, 248);
let print = match v {
1 => "Diffraction",
2 => "Imaging",
_ => "Unknown",
};
tags.push(mk_print("ProjectionMode", Value::U32(v), print.to_string()));
}
if bitm & 0x8000000 != 0 {
let s = read_str(data, 252, 16);
tags.push(mk("ObjectiveLens", Value::String(s)));
}
if bitm & 0x10000000 != 0 {
let s = read_str(data, 268, 16);
tags.push(mk("HighMagnificationMode", Value::String(s)));
}
if bitm & 0x20000000 != 0 {
let v = read_u32_le(data, 284);
let print = match v {
1 => "Nano",
2 => "Micro",
_ => "Unknown",
};
tags.push(mk_print("ProbeMode", Value::U32(v), print.to_string()));
}
if bitm & 0x40000000 != 0 {
let v = if data.len() > 288 { data[288] } else { 0 };
let print = if v == 0 { "No" } else { "Yes" };
tags.push(mk_print("EFTEMOn", Value::U8(v), print.to_string()));
}
if bitm & 0x80000000 != 0 {
let v = read_f64_le(data, 289);
tags.push(mk_print("Magnification", Value::F64(v), format_g15(v)));
}
if data.len() >= 301 {
let bitmask2 = read_u32_le(data, 297);
let bm2_print = format!("0x{:08x}", bitmask2);
tags.push(mk_print("Bitmask2", Value::U32(bitmask2), bm2_print));
bitm = bitmask2;
if bitm & 0x01 != 0 {
let v = read_f64_le(data, 301);
tags.push(mk_print("CameraLength", Value::F64(v), format_g15(v)));
}
if bitm & 0x02 != 0 {
let v = read_u32_le(data, 309);
tags.push(mk("SpotIndex", Value::U32(v)));
}
if bitm & 0x04 != 0 {
let v = read_f64_le(data, 313);
tags.push(mk_print("IlluminationArea", Value::F64(v), format_g15(v)));
}
if bitm & 0x08 != 0 {
let v = read_f64_le(data, 321);
tags.push(mk_print("Intensity", Value::F64(v), format_g15(v)));
}
if bitm & 0x10 != 0 {
let v = read_f64_le(data, 329);
tags.push(mk_print("ConvergenceAngle", Value::F64(v), format_g15(v)));
}
if bitm & 0x20 != 0 {
let s = read_str(data, 337, 16);
tags.push(mk("IlluminationMode", Value::String(s)));
}
if bitm & 0x40 != 0 && data.len() > 353 {
let v = data[353];
let print = if v == 0 { "No" } else { "Yes" };
tags.push(mk_print(
"WideConvergenceAngleRange",
Value::U8(v),
print.to_string(),
));
}
if bitm & 0x80 != 0 && data.len() > 354 {
let v = data[354];
let print = if v == 0 { "No" } else { "Yes" };
tags.push(mk_print("SlitInserted", Value::U8(v), print.to_string()));
}
if bitm & 0x100 != 0 {
let v = read_f64_le(data, 355);
tags.push(mk_print("SlitWidth", Value::F64(v), format_g15(v)));
}
if bitm & 0x200 != 0 {
let v = read_f64_le(data, 363);
tags.push(mk_print("AccelVoltOffset", Value::F64(v), format_g15(v)));
}
if bitm & 0x400 != 0 {
let v = read_f64_le(data, 371);
tags.push(mk_print("DriftTubeVolt", Value::F64(v), format_g15(v)));
}
if bitm & 0x800 != 0 {
let v = read_f64_le(data, 379);
tags.push(mk_print("EnergyShift", Value::F64(v), format_g15(v)));
}
if bitm & 0x1000 != 0 {
let v = read_f64_le(data, 387);
tags.push(mk_print("ShiftOffsetX", Value::F64(v), format_g15(v)));
}
if bitm & 0x2000 != 0 {
let v = read_f64_le(data, 395);
tags.push(mk_print("ShiftOffsetY", Value::F64(v), format_g15(v)));
}
if bitm & 0x4000 != 0 {
let v = read_f64_le(data, 403);
tags.push(mk_print("ShiftX", Value::F64(v), format_g15(v)));
}
if bitm & 0x8000 != 0 {
let v = read_f64_le(data, 411);
tags.push(mk_print("ShiftY", Value::F64(v), format_g15(v)));
}
if bitm & 0x10000 != 0 {
let v = read_f64_le(data, 419);
tags.push(mk_print("IntegrationTime", Value::F64(v), format_g15(v)));
}
if bitm & 0x20000 != 0 {
let v = read_u32_le(data, 427);
tags.push(mk("BinningWidth", Value::U32(v)));
}
if bitm & 0x40000 != 0 {
let v = read_u32_le(data, 431);
tags.push(mk("BinningHeight", Value::U32(v)));
}
if bitm & 0x80000 != 0 {
let s = read_str(data, 435, 16);
tags.push(mk("CameraName", Value::String(s)));
}
if bitm & 0x100000 != 0 {
let v = read_u32_le(data, 451);
tags.push(mk("ReadoutAreaLeft", Value::U32(v)));
}
if bitm & 0x200000 != 0 {
let v = read_u32_le(data, 455);
tags.push(mk("ReadoutAreaTop", Value::U32(v)));
}
if bitm & 0x400000 != 0 {
let v = read_u32_le(data, 459);
tags.push(mk("ReadoutAreaRight", Value::U32(v)));
}
if bitm & 0x800000 != 0 {
let v = read_u32_le(data, 463);
tags.push(mk("ReadoutAreaBottom", Value::U32(v)));
}
if bitm & 0x1000000 != 0 && data.len() > 467 {
let v = data[467];
let print = if v == 0 { "No" } else { "Yes" };
tags.push(mk_print("CetaNoiseReduct", Value::U8(v), print.to_string()));
}
if bitm & 0x2000000 != 0 {
let v = read_u32_le(data, 468);
tags.push(mk("CetaFramesSummed", Value::U32(v)));
}
if bitm & 0x4000000 != 0 && data.len() > 472 {
let v = data[472];
let print = if v == 0 { "No" } else { "Yes" };
tags.push(mk_print(
"DirectDetElectronCounting",
Value::U8(v),
print.to_string(),
));
}
if bitm & 0x8000000 != 0 && data.len() > 473 {
let v = data[473];
let print = if v == 0 { "No" } else { "Yes" };
tags.push(mk_print(
"DirectDetAlignFrames",
Value::U8(v),
print.to_string(),
));
}
}
if data.len() >= 494 {
let bitmask3 = read_u32_le(data, 490);
let bm3_print = format!("0x{:08x}", bitmask3);
tags.push(mk_print("Bitmask3", Value::U32(bitmask3), bm3_print));
bitm = bitmask3;
if bitm & 0x40 != 0 && data.len() > 518 {
let v = data[518];
let print = if v == 0 { "No" } else { "Yes" };
tags.push(mk_print("PhasePlate", Value::U8(v), print.to_string()));
}
if bitm & 0x80 != 0 {
let s = read_str(data, 519, 16);
tags.push(mk("STEMDetectorName", Value::String(s)));
}
if bitm & 0x100 != 0 {
let v = read_f64_le(data, 535);
tags.push(mk_print("Gain", Value::F64(v), format_g15(v)));
}
if bitm & 0x200 != 0 {
let v = read_f64_le(data, 543);
tags.push(mk_print("Offset", Value::F64(v), format_g15(v)));
}
if bitm & 0x8000 != 0 {
let v = read_f64_le(data, 571);
tags.push(mk_print("DwellTime", Value::F64(v), format_g15(v)));
}
if bitm & 0x10000 != 0 {
let v = read_f64_le(data, 579);
tags.push(mk_print("FrameTime", Value::F64(v), format_g15(v)));
}
if bitm & 0x20000 != 0 {
let v = read_u32_le(data, 587);
tags.push(mk("ScanSizeLeft", Value::U32(v)));
}
if bitm & 0x40000 != 0 {
let v = read_u32_le(data, 591);
tags.push(mk("ScanSizeTop", Value::U32(v)));
}
if bitm & 0x80000 != 0 {
let v = read_u32_le(data, 595);
tags.push(mk("ScanSizeRight", Value::U32(v)));
}
if bitm & 0x100000 != 0 {
let v = read_u32_le(data, 599);
tags.push(mk("ScanSizeBottom", Value::U32(v)));
}
if bitm & 0x200000 != 0 {
let v = read_f64_le(data, 603);
tags.push(mk_print("FullScanFOV_X", Value::F64(v), format_g15(v)));
}
if bitm & 0x400000 != 0 {
let v = read_f64_le(data, 611);
tags.push(mk_print("FullScanFOV_Y", Value::F64(v), format_g15(v)));
}
if bitm & 0x800000 != 0 {
let s = read_str(data, 619, 16);
tags.push(mk("Element", Value::String(s)));
}
if bitm & 0x1000000 != 0 {
let v = read_f64_le(data, 635);
tags.push(mk_print(
"EnergyIntervalLower",
Value::F64(v),
format_g15(v),
));
}
if bitm & 0x2000000 != 0 {
let v = read_f64_le(data, 643);
tags.push(mk_print(
"EnergyIntervalHigher",
Value::F64(v),
format_g15(v),
));
}
if bitm & 0x4000000 != 0 {
let v = read_u32_le(data, 651);
tags.push(mk("Method", Value::U32(v)));
}
if bitm & 0x8000000 != 0 && data.len() > 655 {
let v = data[655];
let print = if v == 0 { "No" } else { "Yes" };
tags.push(mk_print("IsDoseFraction", Value::U8(v), print.to_string()));
}
if bitm & 0x10000000 != 0 {
let v = read_u32_le(data, 656);
tags.push(mk("FractionNumber", Value::U32(v)));
}
if bitm & 0x20000000 != 0 {
let v = read_u32_le(data, 660);
tags.push(mk("StartFrame", Value::U32(v)));
}
if bitm & 0x40000000 != 0 {
let v = read_u32_le(data, 664);
tags.push(mk("EndFrame", Value::U32(v)));
}
if bitm & 0x80000000 != 0 {
let s = read_str(data, 668, 80);
tags.push(mk("InputStackFilename", Value::String(s)));
}
}
if data.len() >= 752 {
let bitmask4 = read_u32_le(data, 748);
let bm4_print = format!("0x{:08x}", bitmask4);
tags.push(mk_print("Bitmask4", Value::U32(bitmask4), bm4_print));
bitm = bitmask4;
if bitm & 0x01 != 0 {
let v = read_f64_le(data, 752);
tags.push(mk_print("AlphaTiltMin", Value::F64(v), format_g15(v)));
}
if bitm & 0x02 != 0 {
let v = read_f64_le(data, 760);
tags.push(mk_print("AlphaTiltMax", Value::F64(v), format_g15(v)));
}
if bitm & 0x04 != 0 {
let v = read_f64_le(data, 768);
tags.push(mk_print("ScanRotation", Value::F64(v), format_g15(v)));
}
if bitm & 0x08 != 0 {
let v = read_f64_le(data, 776);
tags.push(mk_print(
"DiffractionPatternRotation",
Value::F64(v),
format_g15(v),
));
}
if bitm & 0x10 != 0 {
let v = read_f64_le(data, 784);
tags.push(mk_print("ImageRotation", Value::F64(v), format_g15(v)));
}
if bitm & 0x20 != 0 {
let v = read_u32_le(data, 792);
let print = match v {
0 => "Other",
1 => "Raster",
2 => "Serpentine",
_ => "Unknown",
};
tags.push(mk_print(
"ScanModeEnumeration",
Value::U32(v),
print.to_string(),
));
}
if bitm & 0x40 != 0 {
let v = read_u64_le(data, 796);
let dt = usecs_to_datetime(v);
tags.push(mk_time("AcquisitionTimeStamp", Value::U32(v as u32), dt));
}
if bitm & 0x80 != 0 {
let s = read_str(data, 804, 16);
tags.push(mk("DetectorCommercialName", Value::String(s)));
}
if bitm & 0x100 != 0 {
let v = read_f64_le(data, 820);
tags.push(mk_print("StartTiltAngle", Value::F64(v), format_g15(v)));
}
if bitm & 0x200 != 0 {
let v = read_f64_le(data, 828);
tags.push(mk_print("EndTiltAngle", Value::F64(v), format_g15(v)));
}
if bitm & 0x400 != 0 {
let v = read_f64_le(data, 836);
tags.push(mk_print("TiltPerImage", Value::F64(v), format_g15(v)));
}
if bitm & 0x800 != 0 {
let v = read_f64_le(data, 844);
tags.push(mk_print("TitlSpeed", Value::F64(v), format_g15(v)));
}
if bitm & 0x1000 != 0 {
let v = read_u32_le(data, 852);
tags.push(mk("BeamCenterX", Value::U32(v)));
}
if bitm & 0x2000 != 0 {
let v = read_u32_le(data, 856);
tags.push(mk("BeamCenterY", Value::U32(v)));
}
if bitm & 0x4000 != 0 {
let v = read_u64_le(data, 860);
let dt = usecs_to_datetime(v);
tags.push(mk_time("CFEGFlashTimeStamp", Value::U32(v as u32), dt));
}
if bitm & 0x8000 != 0 {
let v = read_u32_le(data, 868);
tags.push(mk("PhasePlatePosition", Value::U32(v)));
}
if bitm & 0x10000 != 0 {
let s = read_str(data, 872, 16);
tags.push(mk("ObjectiveAperture", Value::String(s)));
}
}
}
pub fn read_mrc(data: &[u8]) -> Result<Vec<Tag>> {
if data.len() < 1024 {
return Err(Error::InvalidData("MRC file too small".into()));
}
let ax1 = read_u32_le(data, 64);
let ax2 = read_u32_le(data, 68);
let ax3 = read_u32_le(data, 72);
if !(1..=3).contains(&ax1) || !(1..=3).contains(&ax2) || !(1..=3).contains(&ax3) {
return Err(Error::InvalidData("Invalid MRC axis values".into()));
}
if &data[208..211] != b"MAP" {
return Err(Error::InvalidData("Missing MRC MAP signature".into()));
}
let ms0 = data[212];
let ms1 = data[213];
let valid_stamp = (ms1 == 0x41 || ms1 == 0x44) && ms0 == 0x44 || (ms0 == 0x11 && ms1 == 0x11);
if !valid_stamp {
return Err(Error::InvalidData("Invalid MRC machine stamp".into()));
}
let mut tags = Vec::new();
let (ext_hdr_size, image_depth, ext_hdr_type) = process_mrc_header(&data[..1024], &mut tags);
if ext_hdr_size > 0 && (ext_hdr_type.starts_with("FEI1") || ext_hdr_type.starts_with("FEI2")) {
let ext_start = 1024;
let ext_end = ext_start + ext_hdr_size as usize;
if ext_end <= data.len() {
let section_size = read_u32_le(data, ext_start) as usize;
if section_size > 0 && section_size <= ext_hdr_size as usize {
if section_size * (image_depth as usize) <= ext_hdr_size as usize {
let section_data =
&data[ext_start..ext_start + section_size.min(data.len() - ext_start)];
process_fei12_header(section_data, &mut tags);
if image_depth > 1 {
tags.push(mk_print(
"Warning",
Value::String("[minor] Use the ExtractEmbedded option to read metadata for all frames".into()),
"[minor] Use the ExtractEmbedded option to read metadata for all frames".into(),
));
}
} else {
tags.push(mk_print(
"Warning",
Value::String("Corrupted extended header".into()),
"Corrupted extended header".into(),
));
}
}
} else {
tags.push(mk_print(
"Warning",
Value::String("Error reading extended header".into()),
"Error reading extended header".into(),
));
}
}
Ok(tags)
}