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;
fn r_u32_le(b: &[u8], off: usize) -> u32 {
u32::from_le_bytes([b[off], b[off + 1], b[off + 2], b[off + 3]])
}
fn r_u64_le(b: &[u8], off: usize) -> u64 {
u64::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],
])
}
pub struct DcimgReader {
path: Option<PathBuf>,
meta: Option<ImageMetadata>,
data_offset: u64,
bytes_per_row: usize,
bytes_per_image: u64,
frame_footer_size: u64,
}
impl DcimgReader {
pub fn new() -> Self {
DcimgReader {
path: None,
meta: None,
data_offset: 0,
bytes_per_row: 0,
bytes_per_image: 0,
frame_footer_size: 0,
}
}
}
impl Default for DcimgReader {
fn default() -> Self {
Self::new()
}
}
impl FormatReader for DcimgReader {
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("dcimg"))
.unwrap_or(false)
}
fn is_this_type_by_bytes(&self, header: &[u8]) -> bool {
header.len() >= 5 && &header[..5] == b"DCIMG"
}
fn set_id(&mut self, path: &Path) -> Result<()> {
let mut f = File::open(path).map_err(BioFormatsError::Io)?;
let mut hdr = vec![0u8; 512];
let n = f.read(&mut hdr).map_err(BioFormatsError::Io)?;
hdr.truncate(n);
if hdr.len() < 64 {
return Err(BioFormatsError::Format(
"DCIMG header is shorter than 64 bytes".into(),
));
}
let version = r_u32_le(&hdr, 8);
let (
header_size,
n_frames,
width,
height,
pixel_type_code,
bytes_per_row,
data_offset,
bytes_per_image,
frame_footer_size,
) = if version >= 0x0100_0000 {
let header_size = r_u32_le(&hdr, 40) as u64;
let header_start = header_size as usize;
if hdr.len() < header_start + 124 {
return Err(BioFormatsError::Format(
"DCIMG version 1 header is truncated".into(),
));
}
let n_frames = r_u32_le(&hdr, header_start + 60).max(1);
let pixel_type_code = r_u32_le(&hdr, header_start + 64);
let width = r_u32_le(&hdr, header_start + 72).max(1);
let height = r_u32_le(&hdr, header_start + 76).max(1);
let bytes_per_row = r_u32_le(&hdr, header_start + 80) as usize;
let bytes_per_image = r_u32_le(&hdr, header_start + 84) as u64;
let data_offset = header_size + r_u64_le(&hdr, header_start + 96);
let frame_footer_size = r_u32_le(&hdr, header_start + 120) as u64;
(
header_size,
n_frames,
width,
height,
pixel_type_code,
bytes_per_row,
data_offset,
bytes_per_image,
frame_footer_size,
)
} else {
let header_size = r_u32_le(&hdr, 16) as u64;
let n_frames = r_u32_le(&hdr, 20).max(1);
let width = r_u32_le(&hdr, 32).max(1);
let height = r_u32_le(&hdr, 36).max(1);
let bit_depth = r_u32_le(&hdr, 40);
let bytes_per_row = r_u32_le(&hdr, 48) as usize;
(
header_size,
n_frames,
width,
height,
bit_depth,
bytes_per_row,
if header_size > 64 { header_size } else { 64 },
0,
0,
)
};
let (pixel_type, bpp): (PixelType, u8) = if version >= 0x0100_0000 {
match pixel_type_code {
1 => (PixelType::Uint8, 8),
2 => (PixelType::Uint16, 16),
other => {
return Err(BioFormatsError::Format(format!(
"unsupported DCIMG pixel type {other}"
)));
}
}
} else {
match pixel_type_code {
8 => (PixelType::Uint8, 8),
32 => (PixelType::Float32, 32),
_ => (PixelType::Uint16, 16), }
};
let bps = pixel_type.bytes_per_sample();
let bpr = if bytes_per_row > 0 {
bytes_per_row
} else {
width as usize * bps
};
let mut meta_map: HashMap<String, MetadataValue> = HashMap::new();
meta_map.insert(
"format".into(),
MetadataValue::String("Hamamatsu DCIMG".into()),
);
meta_map.insert("version".into(), MetadataValue::Int(version as i64));
meta_map.insert("bit_depth".into(), MetadataValue::Int(bpp as i64));
meta_map.insert("header_size".into(), MetadataValue::Int(header_size as i64));
self.meta = Some(ImageMetadata {
size_x: width,
size_y: height,
size_z: n_frames,
size_c: 1,
size_t: 1,
pixel_type,
bits_per_pixel: bpp,
image_count: n_frames,
dimension_order: DimensionOrder::XYZCT,
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.data_offset = data_offset;
self.bytes_per_row = bpr;
self.bytes_per_image = bytes_per_image;
self.frame_footer_size = frame_footer_size;
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 row_bytes = if self.bytes_per_row > 0 {
self.bytes_per_row
} else {
meta.size_x as usize * bps
};
let plane_bytes = row_bytes * meta.size_y as usize;
let frame_stride = if self.bytes_per_image > 0 {
self.bytes_per_image + self.frame_footer_size
} else {
plane_bytes as u64
};
let offset = self.data_offset + plane_index as u64 * frame_stride;
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 raw = vec![0u8; plane_bytes];
f.read_exact(&mut raw).map_err(BioFormatsError::Io)?;
if self.frame_footer_size as usize >= 4 * bps {
let footer_off = offset + self.bytes_per_image;
if f.seek(SeekFrom::Start(footer_off)).is_ok() {
let mut footer = vec![0u8; self.frame_footer_size as usize];
if f.read_exact(&mut footer).is_ok() {
let corner = &footer[footer.len() - 4 * bps..];
let n = (4 * bps).min(raw.len());
raw[..n].copy_from_slice(&corner[..n]);
}
}
}
let out_row = meta.size_x as usize * bps;
if row_bytes == out_row {
return Ok(raw);
}
let mut out = Vec::with_capacity(meta.size_y as usize * out_row);
for r in 0..meta.size_y as usize {
out.extend_from_slice(&raw[r * row_bytes..r * row_bytes + out_row]);
}
Ok(out)
}
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)
}
}