1use scirs2_core::ndarray::{Array, ArrayView, Dimension, Ix1, Ix2, IxDyn};
7use scirs2_core::numeric::{Float, FromPrimitive, NumCast};
8use std::fs;
9use std::path::Path;
10
11use scirs2_core::memory_efficient::{
12 create_mmap, AccessMode, ChunkingStrategy, MemoryMappedArray, MemoryMappedChunkIter,
13 MemoryMappedChunks,
14};
15
16use crate::error::{NdimageError, NdimageResult};
17
18#[allow(dead_code)]
34pub fn loadimage_mmap<T, D, P>(
35 path: P,
36 shape: &[usize],
37 offset: usize,
38 access: AccessMode,
39) -> NdimageResult<MemoryMappedArray<T>>
40where
41 T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
42 D: Dimension,
43 P: AsRef<Path>,
44{
45 let total_elements: usize = shape.iter().product();
47 let element_size = std::mem::size_of::<T>();
48 let total_bytes = total_elements * element_size;
49
50 let file_size = std::fs::metadata(path.as_ref())
52 .map_err(NdimageError::IoError)?
53 .len() as usize;
54
55 if file_size < offset + total_bytes {
56 return Err(NdimageError::InvalidInput(format!(
57 "File too small: expected at least {} bytes, got {}",
58 offset + total_bytes,
59 file_size
60 )));
61 }
62
63 let dummy_array = Array::<T, IxDyn>::zeros(IxDyn(shape));
65
66 let mmap = create_mmap(&dummy_array.view(), path.as_ref(), access, offset)
68 .map_err(NdimageError::CoreError)?;
69
70 Ok(mmap)
71}
72
73#[allow(dead_code)]
87pub fn saveimage_mmap<T, D, P>(
88 array: &ArrayView<T, D>,
89 path: P,
90 offset: usize,
91) -> NdimageResult<MemoryMappedArray<T>>
92where
93 T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
94 D: Dimension,
95 P: AsRef<Path>,
96{
97 let mmap = create_mmap(array, path.as_ref(), AccessMode::Write, offset)
99 .map_err(NdimageError::CoreError)?;
100
101 Ok(mmap)
102}
103
104#[allow(dead_code)]
116pub fn create_temp_mmap<T>(
117 shape: &[usize],
118) -> NdimageResult<(MemoryMappedArray<T>, tempfile::TempPath)>
119where
120 T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
121{
122 use tempfile::NamedTempFile;
123
124 let temp_file = NamedTempFile::new().map_err(NdimageError::IoError)?;
126
127 let temp_path = temp_file.into_temp_path();
128
129 let dummy_array = Array::<T, IxDyn>::zeros(IxDyn(shape));
131
132 let mmap = create_mmap(&dummy_array.view(), &temp_path, AccessMode::Write, 0)
134 .map_err(NdimageError::CoreError)?;
135
136 Ok((mmap, temp_path))
137}
138
139#[allow(dead_code)]
154pub fn process_mmap_chunks<T, R, F>(
155 mmap: &MemoryMappedArray<T>,
156 strategy: ChunkingStrategy,
157 processor: F,
158) -> NdimageResult<Vec<R>>
159where
160 T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
161 F: Fn(&[T], usize) -> R,
162 R: Send,
163{
164 let results = mmap.process_chunks(strategy, processor);
165 Ok(results)
166}
167
168pub struct MmapChunkIterator<'a, T>
172where
173 T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
174{
175 mmap: &'a MemoryMappedArray<T>,
176 strategy: ChunkingStrategy,
177}
178
179impl<'a, T> MmapChunkIterator<'a, T>
180where
181 T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
182{
183 pub fn new(mmap: &'a MemoryMappedArray<T>, strategy: ChunkingStrategy) -> Self {
184 Self { mmap, strategy }
185 }
186
187 pub fn iter(&self) -> impl Iterator<Item = Array<T, Ix1>> + '_ {
189 self.mmap.chunks(self.strategy.clone())
190 }
191}
192
193#[derive(Debug, Clone)]
195pub struct MmapConfig {
196 pub auto_mmap_threshold: usize,
198 pub default_chunk_strategy: ChunkingStrategy,
200 pub parallel: bool,
202 pub prefetch: bool,
204}
205
206impl Default for MmapConfig {
207 fn default() -> Self {
208 Self {
209 auto_mmap_threshold: 100 * 1024 * 1024, default_chunk_strategy: ChunkingStrategy::Auto,
211 parallel: true,
212 prefetch: true,
213 }
214 }
215}
216
217#[allow(dead_code)]
231pub fn load_regular_array<T, D, P>(path: P, shape: &[usize]) -> NdimageResult<Array<T, D>>
232where
233 T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
234 D: Dimension + 'static,
235 P: AsRef<Path>,
236{
237 use std::fs::File;
238 use std::io::Read;
239
240 let total_elements: usize = shape.iter().product();
241 let element_size = std::mem::size_of::<T>();
242 let expected_bytes = total_elements * element_size;
243
244 let mut file = File::open(path.as_ref()).map_err(NdimageError::IoError)?;
246
247 let file_size = file.metadata().map_err(NdimageError::IoError)?.len() as usize;
249
250 if file_size < expected_bytes {
251 return Err(NdimageError::InvalidInput(format!(
252 "File too small: expected {} bytes, got {}",
253 expected_bytes, file_size
254 )));
255 }
256
257 let mut buffer = vec![0u8; expected_bytes];
259 file.read_exact(&mut buffer)
260 .map_err(NdimageError::IoError)?;
261
262 let mut data = Vec::with_capacity(total_elements);
264
265 if std::mem::size_of::<T>() == std::mem::size_of::<f64>() {
266 for chunk in buffer.chunks_exact(8) {
268 let bytes: [u8; 8] = chunk
269 .try_into()
270 .map_err(|_| NdimageError::ProcessingError("Invalid byte alignment".into()))?;
271 let value = f64::from_le_bytes(bytes);
272 let converted = T::from_f64(value).ok_or_else(|| {
273 NdimageError::ProcessingError("Failed to convert f64 to target type".into())
274 })?;
275 data.push(converted);
276 }
277 } else if std::mem::size_of::<T>() == std::mem::size_of::<f32>() {
278 for chunk in buffer.chunks_exact(4) {
280 let bytes: [u8; 4] = chunk
281 .try_into()
282 .map_err(|_| NdimageError::ProcessingError("Invalid byte alignment".into()))?;
283 let value = f32::from_le_bytes(bytes);
284 let converted = T::from_f32(value).ok_or_else(|| {
285 NdimageError::ProcessingError("Failed to convert f32 to target type".into())
286 })?;
287 data.push(converted);
288 }
289 } else {
290 return Err(NdimageError::NotImplementedError(
291 "Only f32 and f64 types are currently supported for regular array loading".into(),
292 ));
293 }
294
295 let raw_dim = D::from_dimension(&scirs2_core::ndarray::IxDyn(shape))
297 .ok_or_else(|| NdimageError::DimensionError("Invalid shape for dimension type".into()))?;
298
299 let array = Array::from_shape_vec(raw_dim, data)
300 .map_err(|e| NdimageError::ProcessingError(format!("Failed to create array: {}", e)))?;
301
302 Ok(array)
303}
304
305#[allow(dead_code)]
307pub fn smart_loadimage<T, D, P>(
308 path: P,
309 shape: &[usize],
310 config: Option<MmapConfig>,
311) -> NdimageResult<ImageData<T, D>>
312where
313 T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
314 D: Dimension + 'static,
315 P: AsRef<Path>,
316{
317 let config = config.unwrap_or_default();
318
319 let total_elements: usize = shape.iter().product();
321 let total_bytes = total_elements * std::mem::size_of::<T>();
322
323 if total_bytes > config.auto_mmap_threshold {
324 let mmap = loadimage_mmap::<T, D, P>(path, shape, 0, AccessMode::ReadOnly)?;
326 Ok(ImageData::MemoryMapped(mmap))
327 } else {
328 let array = load_regular_array::<T, D, P>(path, shape)?;
330 Ok(ImageData::Regular(array))
331 }
332}
333
334pub enum ImageData<T, D>
336where
337 T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
338 D: Dimension,
339{
340 Regular(Array<T, D>),
341 MemoryMapped(MemoryMappedArray<T>),
342}
343
344impl<T, D> ImageData<T, D>
345where
346 T: Float + FromPrimitive + NumCast + Send + Sync + 'static,
347 D: Dimension + 'static,
348{
349 pub fn view(&self) -> NdimageResult<ArrayView<T, D>> {
355 match self {
356 ImageData::Regular(array) => Ok(array.view()),
357 ImageData::MemoryMapped(_mmap) => Err(NdimageError::NotImplementedError(
358 "Cannot return a borrowed view over a memory-mapped array: \
359 call to_array() to materialise a copy first, then use .view() on the Array."
360 .to_string(),
361 )),
362 }
363 }
364
365 pub fn to_array(&self) -> NdimageResult<Array<T, D>> {
370 match self {
371 ImageData::Regular(array) => Ok(array.clone()),
372 ImageData::MemoryMapped(mmap) => mmap.as_array::<D>().map_err(|e| {
373 NdimageError::ProcessingError(format!(
374 "Failed to materialise memory-mapped array: {e}"
375 ))
376 }),
377 }
378 }
379
380 pub fn is_mmap(&self) -> bool {
382 matches!(self, ImageData::MemoryMapped(_))
383 }
384
385 pub fn shape(&self) -> Vec<usize> {
387 match self {
388 ImageData::Regular(array) => array.shape().to_vec(),
389 ImageData::MemoryMapped(mmap) => mmap.shape.clone(),
390 }
391 }
392}
393
394#[allow(dead_code)]
396pub fn process_largeimage_example<P: AsRef<Path>>(
397 input_path: P,
398 output_path: P,
399 shape: &[usize],
400) -> NdimageResult<()> {
401 let input_mmap = loadimage_mmap::<f64, Ix2, _>(input_path, shape, 0, AccessMode::ReadOnly)?;
403
404 let output_mmap = saveimage_mmap(
406 &Array::<f64, IxDyn>::zeros(IxDyn(shape)).view(),
407 output_path,
408 0,
409 )?;
410
411 let chunk_results = input_mmap.process_chunks(
413 ChunkingStrategy::FixedBytes(10 * 1024 * 1024), |chunk_data, chunk_idx| {
415 let processed: Vec<f64> = chunk_data.iter().map(|&x| x * 2.0 + 1.0).collect();
417 (chunk_idx, processed)
418 },
419 );
420
421 println!("Processed {} chunks", chunk_results.len());
423
424 Ok(())
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430 use scirs2_core::ndarray::Array2;
431 use tempfile::tempdir;
432
433 #[test]
434 fn test_create_temp_mmap() {
435 let shape = vec![100, 100];
436 let (mmap, _temp_path) = create_temp_mmap::<f64>(&shape).expect("Operation failed");
437
438 assert!(!_temp_path.is_dir());
442 }
443
444 #[test]
445 fn test_save_and_load_mmap() {
446 let temp_dir = tempdir().expect("Operation failed");
447 let file_path = temp_dir.path().join("testimage.bin");
448
449 let data = Array2::<f64>::from_elem((50, 50), std::f64::consts::PI);
451
452 let _saved_mmap = saveimage_mmap(&data.view(), &file_path, 0).expect("Operation failed");
454 let loaded_mmap =
459 loadimage_mmap::<f64, Ix2, _>(&file_path, &[50, 50], 0, AccessMode::ReadOnly)
460 .expect("Operation failed");
461
462 assert!(file_path.exists());
469 }
470
471 #[test]
472 fn test_mmap_chunk_iterator() {
473 let shape = vec![1000];
474 let (mmap, _temp_path) = create_temp_mmap::<f64>(&shape).expect("Operation failed");
475
476 let iterator = MmapChunkIterator::new(&mmap, ChunkingStrategy::Fixed(100));
477 let chunks: Vec<_> = iterator.iter().collect();
478
479 assert_eq!(chunks.len(), 10);
480 assert_eq!(chunks[0].len(), 100);
481 }
482}