use scirs2_core::ndarray::{Array, ArrayView, Dimension, Ix1, Ix2, IxDyn};
use scirs2_core::numeric::{Float, FromPrimitive, NumCast};
use std::fs;
use std::path::Path;
use scirs2_core::memory_efficient::{
create_mmap, AccessMode, ChunkingStrategy, MemoryMappedArray, MemoryMappedChunkIter,
MemoryMappedChunks,
};
use crate::error::{NdimageError, NdimageResult};
#[allow(dead_code)]
pub fn loadimage_mmap<T, D, P>(
path: P,
shape: &[usize],
offset: usize,
access: AccessMode,
) -> NdimageResult<MemoryMappedArray<T>>
where
T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
D: Dimension,
P: AsRef<Path>,
{
let total_elements: usize = shape.iter().product();
let element_size = std::mem::size_of::<T>();
let total_bytes = total_elements * element_size;
let file_size = std::fs::metadata(path.as_ref())
.map_err(NdimageError::IoError)?
.len() as usize;
if file_size < offset + total_bytes {
return Err(NdimageError::InvalidInput(format!(
"File too small: expected at least {} bytes, got {}",
offset + total_bytes,
file_size
)));
}
let dummy_array = Array::<T, IxDyn>::zeros(IxDyn(shape));
let mmap = create_mmap(&dummy_array.view(), path.as_ref(), access, offset)
.map_err(NdimageError::CoreError)?;
Ok(mmap)
}
#[allow(dead_code)]
pub fn saveimage_mmap<T, D, P>(
array: &ArrayView<T, D>,
path: P,
offset: usize,
) -> NdimageResult<MemoryMappedArray<T>>
where
T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
D: Dimension,
P: AsRef<Path>,
{
let mmap = create_mmap(array, path.as_ref(), AccessMode::Write, offset)
.map_err(NdimageError::CoreError)?;
Ok(mmap)
}
#[allow(dead_code)]
pub fn create_temp_mmap<T>(
shape: &[usize],
) -> NdimageResult<(MemoryMappedArray<T>, tempfile::TempPath)>
where
T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
{
use tempfile::NamedTempFile;
let temp_file = NamedTempFile::new().map_err(NdimageError::IoError)?;
let temp_path = temp_file.into_temp_path();
let dummy_array = Array::<T, IxDyn>::zeros(IxDyn(shape));
let mmap = create_mmap(&dummy_array.view(), &temp_path, AccessMode::Write, 0)
.map_err(NdimageError::CoreError)?;
Ok((mmap, temp_path))
}
#[allow(dead_code)]
pub fn process_mmap_chunks<T, R, F>(
mmap: &MemoryMappedArray<T>,
strategy: ChunkingStrategy,
processor: F,
) -> NdimageResult<Vec<R>>
where
T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
F: Fn(&[T], usize) -> R,
R: Send,
{
let results = mmap.process_chunks(strategy, processor);
Ok(results)
}
pub struct MmapChunkIterator<'a, T>
where
T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
{
mmap: &'a MemoryMappedArray<T>,
strategy: ChunkingStrategy,
}
impl<'a, T> MmapChunkIterator<'a, T>
where
T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
{
pub fn new(mmap: &'a MemoryMappedArray<T>, strategy: ChunkingStrategy) -> Self {
Self { mmap, strategy }
}
pub fn iter(&self) -> impl Iterator<Item = Array<T, Ix1>> + '_ {
self.mmap.chunks(self.strategy.clone())
}
}
#[derive(Debug, Clone)]
pub struct MmapConfig {
pub auto_mmap_threshold: usize,
pub default_chunk_strategy: ChunkingStrategy,
pub parallel: bool,
pub prefetch: bool,
}
impl Default for MmapConfig {
fn default() -> Self {
Self {
auto_mmap_threshold: 100 * 1024 * 1024, default_chunk_strategy: ChunkingStrategy::Auto,
parallel: true,
prefetch: true,
}
}
}
#[allow(dead_code)]
pub fn load_regular_array<T, D, P>(path: P, shape: &[usize]) -> NdimageResult<Array<T, D>>
where
T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
D: Dimension + 'static,
P: AsRef<Path>,
{
use std::fs::File;
use std::io::Read;
let total_elements: usize = shape.iter().product();
let element_size = std::mem::size_of::<T>();
let expected_bytes = total_elements * element_size;
let mut file = File::open(path.as_ref()).map_err(NdimageError::IoError)?;
let file_size = file.metadata().map_err(NdimageError::IoError)?.len() as usize;
if file_size < expected_bytes {
return Err(NdimageError::InvalidInput(format!(
"File too small: expected {} bytes, got {}",
expected_bytes, file_size
)));
}
let mut buffer = vec![0u8; expected_bytes];
file.read_exact(&mut buffer)
.map_err(NdimageError::IoError)?;
let mut data = Vec::with_capacity(total_elements);
if std::mem::size_of::<T>() == std::mem::size_of::<f64>() {
for chunk in buffer.chunks_exact(8) {
let bytes: [u8; 8] = chunk
.try_into()
.map_err(|_| NdimageError::ProcessingError("Invalid byte alignment".into()))?;
let value = f64::from_le_bytes(bytes);
let converted = T::from_f64(value).ok_or_else(|| {
NdimageError::ProcessingError("Failed to convert f64 to target type".into())
})?;
data.push(converted);
}
} else if std::mem::size_of::<T>() == std::mem::size_of::<f32>() {
for chunk in buffer.chunks_exact(4) {
let bytes: [u8; 4] = chunk
.try_into()
.map_err(|_| NdimageError::ProcessingError("Invalid byte alignment".into()))?;
let value = f32::from_le_bytes(bytes);
let converted = T::from_f32(value).ok_or_else(|| {
NdimageError::ProcessingError("Failed to convert f32 to target type".into())
})?;
data.push(converted);
}
} else {
return Err(NdimageError::NotImplementedError(
"Only f32 and f64 types are currently supported for regular array loading".into(),
));
}
let raw_dim = D::from_dimension(&scirs2_core::ndarray::IxDyn(shape))
.ok_or_else(|| NdimageError::DimensionError("Invalid shape for dimension type".into()))?;
let array = Array::from_shape_vec(raw_dim, data)
.map_err(|e| NdimageError::ProcessingError(format!("Failed to create array: {}", e)))?;
Ok(array)
}
#[allow(dead_code)]
pub fn smart_loadimage<T, D, P>(
path: P,
shape: &[usize],
config: Option<MmapConfig>,
) -> NdimageResult<ImageData<T, D>>
where
T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
D: Dimension + 'static,
P: AsRef<Path>,
{
let config = config.unwrap_or_default();
let total_elements: usize = shape.iter().product();
let total_bytes = total_elements * std::mem::size_of::<T>();
if total_bytes > config.auto_mmap_threshold {
let mmap = loadimage_mmap::<T, D, P>(path, shape, 0, AccessMode::ReadOnly)?;
Ok(ImageData::MemoryMapped(mmap))
} else {
let array = load_regular_array::<T, D, P>(path, shape)?;
Ok(ImageData::Regular(array))
}
}
pub enum ImageData<T, D>
where
T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
D: Dimension,
{
Regular(Array<T, D>),
MemoryMapped(MemoryMappedArray<T>),
}
impl<T, D> ImageData<T, D>
where
T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
D: Dimension + 'static,
{
pub fn view(&self) -> NdimageResult<ArrayView<T, D>> {
match self {
ImageData::Regular(array) => Ok(array.view()),
ImageData::MemoryMapped(_mmap) => Err(NdimageError::NotImplementedError(
"View access for memory-mapped arrays not yet implemented".to_string(),
)),
}
}
pub fn is_mmap(&self) -> bool {
matches!(self, ImageData::MemoryMapped(_))
}
pub fn shape(&self) -> Vec<usize> {
match self {
ImageData::Regular(array) => array.shape().to_vec(),
ImageData::MemoryMapped(_mmap) => {
vec![]
}
}
}
}
#[allow(dead_code)]
pub fn process_largeimage_example<P: AsRef<Path>>(
input_path: P,
output_path: P,
shape: &[usize],
) -> NdimageResult<()> {
let input_mmap = loadimage_mmap::<f64, Ix2, _>(input_path, shape, 0, AccessMode::ReadOnly)?;
let output_mmap = saveimage_mmap(
&Array::<f64, IxDyn>::zeros(IxDyn(shape)).view(),
output_path,
0,
)?;
let chunk_results = input_mmap.process_chunks(
ChunkingStrategy::FixedBytes(10 * 1024 * 1024), |chunk_data, chunk_idx| {
let processed: Vec<f64> = chunk_data.iter().map(|&x| x * 2.0 + 1.0).collect();
(chunk_idx, processed)
},
);
println!("Processed {} chunks", chunk_results.len());
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::Array2;
use tempfile::tempdir;
#[test]
fn test_create_temp_mmap() {
let shape = vec![100, 100];
let (mmap, _temp_path) = create_temp_mmap::<f64>(&shape).expect("Operation failed");
assert!(!_temp_path.is_dir());
}
#[test]
fn test_save_and_load_mmap() {
let temp_dir = tempdir().expect("Operation failed");
let file_path = temp_dir.path().join("testimage.bin");
let data = Array2::<f64>::from_elem((50, 50), std::f64::consts::PI);
let _saved_mmap = saveimage_mmap(&data.view(), &file_path, 0).expect("Operation failed");
let loaded_mmap =
loadimage_mmap::<f64, Ix2, _>(&file_path, &[50, 50], 0, AccessMode::ReadOnly)
.expect("Operation failed");
assert!(file_path.exists());
}
#[test]
fn test_mmap_chunk_iterator() {
let shape = vec![1000];
let (mmap, _temp_path) = create_temp_mmap::<f64>(&shape).expect("Operation failed");
let iterator = MmapChunkIterator::new(&mmap, ChunkingStrategy::Fixed(100));
let chunks: Vec<_> = iterator.iter().collect();
assert_eq!(chunks.len(), 10);
assert_eq!(chunks[0].len(), 100);
}
}