use log::{info, debug, warn};
use std::fs::File;
use std::io::{BufWriter, Write, BufReader};
use std::path::Path;
use image::{DynamicImage, GenericImageView};
use crate::tiff::TiffReader;
use crate::tiff::errors::{TiffError, TiffResult};
use crate::tiff::constants::tags;
use crate::utils::logger::Logger;
use super::region::Region;
use super::tile_reader::TileReader;
use super::strip_reader::StripReader;
use super::extractor_strategy::ExtractorStrategy;
#[derive(Debug, Clone)]
pub struct ArrayData {
pub width: u32,
pub height: u32,
pub data: Vec<u8>,
}
impl ArrayData {
pub fn from_image(image: &DynamicImage) -> Self {
let gray_image = image.to_luma8();
let width = gray_image.width();
let height = gray_image.height();
let data = gray_image.into_raw();
ArrayData {
width,
height,
data,
}
}
pub fn get(&self, x: u32, y: u32) -> Option<u8> {
if x >= self.width || y >= self.height {
return None;
}
let idx = (y * self.width + x) as usize;
self.data.get(idx).copied()
}
pub fn save_to_file(&self, path: &str, format: &str) -> TiffResult<()> {
match format.to_lowercase().as_str() {
"csv" => self.save_as_csv(path),
"json" => self.save_as_json(path),
"npy" => self.save_as_npy(path),
_ => Err(TiffError::GenericError(format!("Unsupported array format: {}", format))),
}
}
fn save_as_csv(&self, path: &str) -> TiffResult<()> {
let file = File::create(path)?;
let mut writer = BufWriter::new(file);
write!(writer, "row/col")?;
for x in 0..self.width {
write!(writer, ",{}", x)?;
}
writeln!(writer)?;
for y in 0..self.height {
write!(writer, "{}", y)?;
for x in 0..self.width {
if let Some(value) = self.get(x, y) {
write!(writer, ",{}", value)?;
} else {
write!(writer, ",")?;
}
}
writeln!(writer)?;
}
Ok(())
}
fn save_as_json(&self, path: &str) -> TiffResult<()> {
let file = File::create(path)?;
let mut writer = BufWriter::new(file);
writeln!(writer, "{{")?;
writeln!(writer, " \"width\": {},", self.width)?;
writeln!(writer, " \"height\": {},", self.height)?;
writeln!(writer, " \"data\": [")?;
for y in 0..self.height {
write!(writer, " [")?;
for x in 0..self.width {
if let Some(value) = self.get(x, y) {
write!(writer, "{}", value)?;
} else {
write!(writer, "0")?;
}
if x < self.width - 1 {
write!(writer, ", ")?;
}
}
if y < self.height - 1 {
writeln!(writer, "],")?;
} else {
writeln!(writer, "]")?;
}
}
writeln!(writer, " ]")?;
writeln!(writer, "}}")?;
Ok(())
}
fn save_as_npy(&self, path: &str) -> TiffResult<()> {
let mut file = File::create(path)?;
file.write_all(b"\x93NUMPY")?; file.write_all(&[0x01, 0x00])?;
let header_str = format!(
"{{'descr': '<u1', 'fortran_order': False, 'shape': ({}, {}), }}",
self.height, self.width
);
let header_len = header_str.len() + 1; let padding_len = (64 - ((header_len + 10) % 64)) % 64;
let padded_header = format!("{}{}\n", header_str, " ".repeat(padding_len));
file.write_all(&[(padded_header.len() as u8) & 0xFF])?;
file.write_all(&[0x00])?; file.write_all(padded_header.as_bytes())?;
file.write_all(&self.data)?;
Ok(())
}
}
pub struct ArrayExtractorStrategy<'a> {
logger: &'a Logger,
reader: TiffReader<'a>,
}
impl<'a> ArrayExtractorStrategy<'a> {
pub fn new(logger: &'a Logger) -> Self {
ArrayExtractorStrategy {
logger,
reader: TiffReader::new(logger),
}
}
}
impl<'a> ExtractorStrategy for ArrayExtractorStrategy<'a> {
fn extract_to_file(&mut self, source_path: &str, output_path: &str,
region: Option<Region>, shape: Option<&str>) -> TiffResult<()> {
self.extract_to_array(source_path, output_path, "csv", region)
}
fn extract_image(&mut self, source_path: &str,
region: Option<Region>) -> TiffResult<DynamicImage> {
let tiff = self.reader.load(source_path)?;
if tiff.ifds.is_empty() {
return Err(TiffError::GenericError("No IFDs found in TIFF file".to_string()));
}
let ifd = &tiff.ifds[0];
let region = crate::utils::tiff_extraction_utils::determine_extraction_region(region, ifd)?;
info!("Extracting region: ({}, {}) with size {}x{}",
region.x, region.y, region.width, region.height);
let file = File::open(source_path)?;
let reader = BufReader::with_capacity(1024 * 1024, file);
let mut image = image::ImageBuffer::<image::Rgb<u8>, Vec<u8>>::new(region.width, region.height);
let is_tiled = ifd.has_tag(tags::TILE_WIDTH) && ifd.has_tag(tags::TILE_LENGTH);
if is_tiled {
let mut tile_reader = TileReader::new(reader, ifd, &self.reader);
tile_reader.extract(&mut image, region)?;
} else {
let mut strip_reader = StripReader::new(reader, ifd, &self.reader);
strip_reader.extract(&mut image, region)?;
}
Ok(DynamicImage::ImageRgb8(image))
}
fn extract_to_array(&mut self, source_path: &str, output_path: &str,
format: &str, region: Option<Region>) -> TiffResult<()> {
info!("Extracting array data from {} to {} in {} format",
source_path, output_path, format);
let array_data = self.extract_array_data(source_path, region)?;
array_data.save_to_file(output_path, format)
}
fn extract_array_data(&mut self, source_path: &str,
region: Option<Region>) -> TiffResult<ArrayData> {
info!("Extracting array data from {} to memory", source_path);
let image = self.extract_image(source_path, region)?;
Ok(ArrayData::from_image(&image))
}
}