use std::collections::HashMap;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};
use crate::common::error::{BioFormatsError, Result};
use crate::common::metadata::{DimensionOrder, ImageMetadata, MetadataValue};
use crate::common::pixel_type::PixelType;
use crate::common::reader::FormatReader;
const HEADER_SIZE: u64 = 4100;
fn r_i16_le(b: &[u8], off: usize) -> i16 {
i16::from_le_bytes([b[off], b[off + 1]])
}
fn r_u16_le(b: &[u8], off: usize) -> u16 {
u16::from_le_bytes([b[off], b[off + 1]])
}
fn r_i32_le(b: &[u8], off: usize) -> i32 {
i32::from_le_bytes([b[off], b[off + 1], b[off + 2], b[off + 3]])
}
fn r_i64_le(b: &[u8], off: usize) -> i64 {
i64::from_le_bytes([
b[off],
b[off + 1],
b[off + 2],
b[off + 3],
b[off + 4],
b[off + 5],
b[off + 6],
b[off + 7],
])
}
fn spe_pixel_type(datatype: i16) -> (PixelType, u8) {
match datatype {
0 => (PixelType::Float32, 32),
1 => (PixelType::Int32, 32),
2 => (PixelType::Int16, 16),
3 => (PixelType::Uint16, 16),
4 => (PixelType::Uint32, 32),
_ => (PixelType::Uint16, 16),
}
}
fn read_xml_footer(f: &mut File, offset: u64) -> Result<String> {
let len = f.metadata().map_err(BioFormatsError::Io)?.len();
if offset >= len {
return Ok(String::new());
}
f.seek(SeekFrom::Start(offset))
.map_err(BioFormatsError::Io)?;
let mut buf = Vec::with_capacity((len - offset) as usize);
f.read_to_end(&mut buf).map_err(BioFormatsError::Io)?;
Ok(String::from_utf8_lossy(&buf)
.trim_matches(|c: char| c == '\0' || c.is_whitespace())
.to_string())
}
pub struct SpeReader {
path: Option<PathBuf>,
meta: Option<ImageMetadata>,
}
impl SpeReader {
pub fn new() -> Self {
SpeReader {
path: None,
meta: None,
}
}
}
impl Default for SpeReader {
fn default() -> Self {
Self::new()
}
}
impl FormatReader for SpeReader {
fn is_this_type_by_name(&self, path: &Path) -> bool {
path.extension()
.and_then(|e| e.to_str())
.map(|e| e.eq_ignore_ascii_case("spe"))
.unwrap_or(false)
}
fn is_this_type_by_bytes(&self, _header: &[u8]) -> bool {
false
}
fn set_id(&mut self, path: &Path) -> Result<()> {
let mut f = File::open(path).map_err(BioFormatsError::Io)?;
let mut hdr = vec![0u8; HEADER_SIZE as usize];
f.read_exact(&mut hdr).map_err(BioFormatsError::Io)?;
let datatype = r_i16_le(&hdr, 108);
let xdim = r_u16_le(&hdr, 42).max(1) as u32;
let ydim = r_u16_le(&hdr, 656).max(1) as u32;
let numframes = r_i32_le(&hdr, 1446).max(1) as u32;
let exposure = r_i32_le(&hdr, 10);
let header_ver = r_i32_le(&hdr, 1992);
let xml_offset = r_i64_le(&hdr, 678);
let date = std::str::from_utf8(&hdr[20..30])
.unwrap_or("")
.trim_end_matches('\0')
.trim()
.to_string();
if !matches!(datatype, 0..=4) {
return Err(BioFormatsError::Format(format!(
"SPE: invalid pixel type {datatype}"
)));
}
let (pixel_type, bpp) = spe_pixel_type(datatype);
let mut meta_map: HashMap<String, MetadataValue> = HashMap::new();
if exposure > 0 {
meta_map.insert("EXPOSURE".into(), MetadataValue::Int(exposure as i64));
}
if !date.is_empty() {
meta_map.insert("date".into(), MetadataValue::String(date));
}
meta_map.insert(
"HEADER_VER".into(),
MetadataValue::Int(header_ver as i64),
);
if header_ver >= 3 || xml_offset > 0 {
meta_map.insert(
"XML_OFFSET".into(),
MetadataValue::Int(xml_offset),
);
meta_map.insert("metadataComplete".into(), MetadataValue::Bool(false));
if xml_offset > 0 {
if let Ok(xml) = read_xml_footer(&mut f, xml_offset as u64) {
if !xml.is_empty() {
meta_map.insert("XMLFooter".into(), MetadataValue::String(xml));
}
}
}
} else {
meta_map.insert("metadataComplete".into(), MetadataValue::Bool(true));
}
self.meta = Some(ImageMetadata {
size_x: xdim,
size_y: ydim,
size_z: 1,
size_c: 1,
size_t: numframes,
pixel_type,
bits_per_pixel: bpp,
image_count: numframes,
dimension_order: DimensionOrder::XYZTC,
is_rgb: false,
is_interleaved: false,
is_indexed: false,
is_little_endian: true,
resolution_count: 1,
series_metadata: meta_map,
lookup_table: None,
modulo_z: None,
modulo_c: None,
modulo_t: None,
});
self.path = Some(path.to_path_buf());
Ok(())
}
fn close(&mut self) -> Result<()> {
self.path = None;
self.meta = None;
Ok(())
}
fn series_count(&self) -> usize {
1
}
fn set_series(&mut self, s: usize) -> Result<()> {
if s != 0 {
Err(BioFormatsError::SeriesOutOfRange(s))
} else {
Ok(())
}
}
fn series(&self) -> usize {
0
}
fn metadata(&self) -> &ImageMetadata {
self.meta.as_ref().expect("set_id not called")
}
fn open_bytes(&mut self, plane_index: u32) -> Result<Vec<u8>> {
let meta = self.meta.as_ref().ok_or(BioFormatsError::NotInitialized)?;
if plane_index >= meta.image_count {
return Err(BioFormatsError::PlaneOutOfRange(plane_index));
}
let bps = meta.pixel_type.bytes_per_sample();
let plane_bytes = (meta.size_x * meta.size_y) as usize * bps;
let offset = HEADER_SIZE + plane_index as u64 * plane_bytes as u64;
let path = self.path.as_ref().ok_or(BioFormatsError::NotInitialized)?;
let mut f = File::open(path).map_err(BioFormatsError::Io)?;
f.seek(SeekFrom::Start(offset))
.map_err(BioFormatsError::Io)?;
let mut buf = vec![0u8; plane_bytes];
f.read_exact(&mut buf).map_err(BioFormatsError::Io)?;
Ok(buf)
}
fn open_bytes_region(
&mut self,
plane_index: u32,
x: u32,
y: u32,
w: u32,
h: u32,
) -> Result<Vec<u8>> {
let full = self.open_bytes(plane_index)?;
let meta = self.meta.as_ref().unwrap();
let bps = meta.pixel_type.bytes_per_sample();
let row = meta.size_x as usize * bps;
let out_row = w as usize * bps;
let mut out = Vec::with_capacity(h as usize * out_row);
for r in 0..h as usize {
let src = &full[(y as usize + r) * row..];
out.extend_from_slice(&src[x as usize * bps..x as usize * bps + out_row]);
}
Ok(out)
}
fn open_thumb_bytes(&mut self, plane_index: u32) -> Result<Vec<u8>> {
let meta = self.meta.as_ref().ok_or(BioFormatsError::NotInitialized)?;
let (tw, th) = (meta.size_x.min(256), meta.size_y.min(256));
let (tx, ty) = ((meta.size_x - tw) / 2, (meta.size_y - th) / 2);
self.open_bytes_region(plane_index, tx, ty, tw, th)
}
fn ome_metadata(&self) -> Option<crate::common::ome_metadata::OmeMetadata> {
use crate::common::ome_metadata::OmeMetadata;
let meta = self.meta.as_ref()?;
Some(OmeMetadata::from_image_metadata(meta))
}
}