use std::collections::HashMap;
use std::io::{Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};
use crate::common::error::{BioFormatsError, Result};
use crate::common::metadata::{DimensionOrder, ImageMetadata};
use crate::common::pixel_type::PixelType;
use crate::common::reader::FormatReader;
pub struct AimReader {
path: Option<PathBuf>,
meta: Option<ImageMetadata>,
data_offset: u64,
}
impl AimReader {
pub fn new() -> Self {
AimReader {
path: None,
meta: None,
data_offset: 512,
}
}
}
impl Default for AimReader {
fn default() -> Self {
Self::new()
}
}
fn read_cstring(f: &mut std::fs::File) -> Result<(String, u64)> {
let mut bytes = Vec::new();
let mut byte = [0u8; 1];
loop {
let n = f.read(&mut byte).map_err(BioFormatsError::Io)?;
if n == 0 {
break; }
if byte[0] == 0 {
break;
}
bytes.push(byte[0]);
}
let pos = f.stream_position().map_err(BioFormatsError::Io)?;
Ok((String::from_utf8_lossy(&bytes).into_owned(), pos))
}
fn read_i32_le(f: &mut std::fs::File) -> Result<i32> {
let mut b = [0u8; 4];
f.read_exact(&mut b).map_err(BioFormatsError::Io)?;
Ok(i32::from_le_bytes(b))
}
fn read_i64_le(f: &mut std::fs::File) -> Result<i64> {
let mut b = [0u8; 8];
f.read_exact(&mut b).map_err(BioFormatsError::Io)?;
Ok(i64::from_le_bytes(b))
}
fn load_aim_header(path: &Path) -> Result<(ImageMetadata, u64)> {
let mut f = std::fs::File::open(path).map_err(BioFormatsError::Io)?;
let mut version = [0u8; 16];
let n = f.read(&mut version).map_err(BioFormatsError::Io)?;
let version_str = String::from_utf8_lossy(&version[..n]).into_owned();
let is_isq = n >= 16 && &version[..16] == b"CTDATA-HEADER_V1";
if is_isq {
f.seek(SeekFrom::Start(28)).map_err(BioFormatsError::Io)?;
let w = read_i32_le(&mut f)?.max(1) as u32;
let h = read_i32_le(&mut f)?.max(1) as u32;
let d = read_i32_le(&mut f)?.max(1) as u32;
let meta = aim_metadata(w, h, d);
return Ok((meta, 512));
}
let wider_offsets = version_str.starts_with("AIMDATA_V030");
let (w, h, d) = if wider_offsets {
f.seek(SeekFrom::Start(96)).map_err(BioFormatsError::Io)?;
let w = read_i64_le(&mut f)? as i32;
let h = read_i64_le(&mut f)? as i32;
let d = read_i64_le(&mut f)? as i32;
f.seek(SeekFrom::Start(280)).map_err(BioFormatsError::Io)?;
(w.max(1) as u32, h.max(1) as u32, d.max(1) as u32)
} else {
f.seek(SeekFrom::Start(56)).map_err(BioFormatsError::Io)?;
let w = read_i32_le(&mut f)?;
let h = read_i32_le(&mut f)?;
let d = read_i32_le(&mut f)?;
f.seek(SeekFrom::Start(160)).map_err(BioFormatsError::Io)?;
(w.max(1) as u32, h.max(1) as u32, d.max(1) as u32)
};
let (processing_log, pixel_offset) = read_cstring(&mut f)?;
let mut meta = aim_metadata(w, h, d);
for line in processing_log.split('\n') {
let line = line.trim();
if let Some(split) = line.find(" ") {
let key = line[..split].trim();
let value = line[split..].trim();
if !key.is_empty() {
meta.series_metadata.insert(
key.to_string(),
crate::common::metadata::MetadataValue::String(value.to_string()),
);
}
}
}
Ok((meta, pixel_offset))
}
fn aim_metadata(width: u32, height: u32, depth: u32) -> ImageMetadata {
let image_count = depth.max(1);
ImageMetadata {
size_x: width,
size_y: height,
size_z: image_count,
size_c: 1,
size_t: 1,
pixel_type: PixelType::Int16,
bits_per_pixel: 16,
image_count,
dimension_order: DimensionOrder::XYZCT,
is_rgb: false,
is_interleaved: false,
is_indexed: false,
is_little_endian: true,
resolution_count: 1,
series_metadata: HashMap::new(),
lookup_table: None,
modulo_z: None,
modulo_c: None,
modulo_t: None,
}
}
impl FormatReader for AimReader {
fn is_this_type_by_name(&self, path: &Path) -> bool {
let ext = path
.extension()
.and_then(|e| e.to_str())
.map(|e| e.to_ascii_lowercase());
matches!(ext.as_deref(), Some("aim") | Some("isq"))
}
fn is_this_type_by_bytes(&self, header: &[u8]) -> bool {
(header.len() >= 16 && &header[..16] == b"CTDATA-HEADER_V1")
|| (header.len() >= 12 && &header[..12] == b"AIMDATA_V030")
}
fn set_id(&mut self, path: &Path) -> Result<()> {
let (meta, data_offset) = load_aim_header(path)?;
self.path = Some(path.to_path_buf());
self.meta = Some(meta);
self.data_offset = data_offset;
Ok(())
}
fn close(&mut self) -> Result<()> {
self.path = None;
self.meta = None;
self.data_offset = 512;
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 as usize * meta.size_y as usize * bps;
let file_offset = self.data_offset + plane_index as u64 * plane_bytes as u64;
let path = self.path.as_ref().ok_or(BioFormatsError::NotInitialized)?;
let mut f = std::fs::File::open(path).map_err(BioFormatsError::Io)?;
f.seek(SeekFrom::Start(file_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_bytes = 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 row in 0..h as usize {
let src = &full[(y as usize + row) * row_bytes..];
let s = x as usize * bps;
out.extend_from_slice(&src[s..s + 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 = meta.size_x.min(256);
let th = meta.size_y.min(256);
let tx = (meta.size_x - tw) / 2;
let ty = (meta.size_y - th) / 2;
self.open_bytes_region(plane_index, tx, ty, tw, th)
}
}