use std::collections::HashMap;
use std::fs;
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 SimfcsReader {
path: Option<PathBuf>,
meta: Option<ImageMetadata>,
}
impl SimfcsReader {
pub fn new() -> Self {
SimfcsReader {
path: None,
meta: None,
}
}
}
impl Default for SimfcsReader {
fn default() -> Self {
Self::new()
}
}
fn simfcs_pixel_type(ext: &str) -> Option<PixelType> {
match ext {
"b64" => Some(PixelType::Uint8),
"r64" => Some(PixelType::Float32),
"i64" => Some(PixelType::Int32),
_ => None,
}
}
impl FormatReader for SimfcsReader {
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("b64") | Some("r64") | Some("i64"))
}
fn is_this_type_by_bytes(&self, _header: &[u8]) -> bool {
false
}
fn set_id(&mut self, path: &Path) -> Result<()> {
let ext = path
.extension()
.and_then(|e| e.to_str())
.map(|e| e.to_ascii_lowercase())
.unwrap_or_default();
let pixel_type = simfcs_pixel_type(&ext)
.ok_or_else(|| BioFormatsError::Format(format!("Unknown SimFCS extension: {}", ext)))?;
let bps = pixel_type.bytes_per_sample();
let file_size = fs::metadata(path).map_err(BioFormatsError::Io)?.len() as usize;
let frame_bytes = 256 * 256 * bps;
if file_size == 0 || file_size % frame_bytes != 0 {
return Err(BioFormatsError::UnsupportedFormat(format!(
"SimFCS payload length {file_size} is not a whole number of 256x256 frames"
)));
}
let image_count = (file_size / frame_bytes) as u32;
let meta = ImageMetadata {
size_x: 256,
size_y: 256,
size_z: image_count,
size_c: 1,
size_t: 1,
pixel_type,
bits_per_pixel: (bps * 8) as u8,
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,
};
self.path = Some(path.to_path_buf());
self.meta = Some(meta);
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 = 256 * 256 * bps;
let offset = plane_index as u64 * plane_bytes as u64;
let path = self.path.as_ref().ok_or(BioFormatsError::NotInitialized)?;
let mut f = fs::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();
validate_region(meta, x, y, w, h)?;
let bps = meta.pixel_type.bytes_per_sample();
let row_bytes = 256 * 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)
}
}
fn validate_region(meta: &ImageMetadata, x: u32, y: u32, w: u32, h: u32) -> Result<()> {
let x2 = x
.checked_add(w)
.ok_or_else(|| BioFormatsError::Format("SimFCS region width overflows".to_string()))?;
let y2 = y
.checked_add(h)
.ok_or_else(|| BioFormatsError::Format("SimFCS region height overflows".to_string()))?;
if x2 > meta.size_x || y2 > meta.size_y {
return Err(BioFormatsError::Format(
"SimFCS region is outside image bounds".to_string(),
));
}
Ok(())
}
pub struct LambertFlimReader {
path: Option<PathBuf>,
meta: Option<ImageMetadata>,
}
impl LambertFlimReader {
pub fn new() -> Self {
LambertFlimReader {
path: None,
meta: None,
}
}
fn unsupported() -> BioFormatsError {
BioFormatsError::UnsupportedFormat(
"Lambert FLIM ASCII payload decoding is not implemented".to_string(),
)
}
}
impl Default for LambertFlimReader {
fn default() -> Self {
Self::new()
}
}
impl FormatReader for LambertFlimReader {
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("asc"))
}
fn is_this_type_by_bytes(&self, header: &[u8]) -> bool {
if header.len() < 8 {
return false;
}
let s = std::str::from_utf8(&header[..header.len().min(256)]).unwrap_or("");
s.contains("Lambert") || s.contains("GlobalImages") || s.starts_with('#')
}
fn set_id(&mut self, _path: &Path) -> Result<()> {
self.path = None;
self.meta = None;
Err(Self::unsupported())
}
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>> {
Err(Self::unsupported())
}
fn open_bytes_region(
&mut self,
_plane_index: u32,
_x: u32,
_y: u32,
_w: u32,
_h: u32,
) -> Result<Vec<u8>> {
Err(Self::unsupported())
}
fn open_thumb_bytes(&mut self, _plane_index: u32) -> Result<Vec<u8>> {
Err(Self::unsupported())
}
}