Skip to main content

oxigdal_core/buffer/
mod.rs

1// Allow unsafe blocks for low-level buffer operations that require
2// direct memory access for performance-critical typed slice conversions
3#![allow(unsafe_code)]
4
5//! Buffer types for raster and vector data
6//!
7//! This module provides efficient buffer types for storing and manipulating
8//! geospatial data. When the `arrow` feature is enabled, buffers are backed
9//! by Apache Arrow arrays for zero-copy interoperability.
10//!
11//! # Overview
12//!
13//! The [`RasterBuffer`] type is the core buffer abstraction in `OxiGDAL`, providing
14//! type-safe storage for raster pixel data with automatic memory management.
15//!
16//! # Examples
17//!
18//! ## Creating buffers
19//!
20//! ```
21//! use oxigdal_core::buffer::RasterBuffer;
22//! use oxigdal_core::types::{RasterDataType, NoDataValue};
23//!
24//! // Create a zero-filled buffer
25//! let buffer = RasterBuffer::zeros(1000, 1000, RasterDataType::Float32);
26//!
27//! // Create a buffer with nodata value
28//! let nodata = NoDataValue::Float(-9999.0);
29//! let buffer = RasterBuffer::nodata_filled(1000, 1000, RasterDataType::Float32, nodata);
30//! ```
31//!
32//! ## Working with pixel data
33//!
34//! ```
35//! use oxigdal_core::buffer::RasterBuffer;
36//! use oxigdal_core::types::RasterDataType;
37//!
38//! let mut buffer = RasterBuffer::zeros(100, 100, RasterDataType::UInt8);
39//!
40//! // Set pixel value
41//! buffer.set_pixel(50, 50, 255.0)?;
42//!
43//! // Get pixel value
44//! let value = buffer.get_pixel(50, 50)?;
45//! assert_eq!(value, 255.0);
46//! # Ok::<(), oxigdal_core::error::OxiGdalError>(())
47//! ```
48//!
49//! ## Computing statistics
50//!
51//! ```
52//! use oxigdal_core::buffer::RasterBuffer;
53//! use oxigdal_core::types::RasterDataType;
54//!
55//! let buffer = RasterBuffer::zeros(1000, 1000, RasterDataType::Float32);
56//! let stats = buffer.compute_statistics()?;
57//!
58//! println!("Min: {}, Max: {}", stats.min, stats.max);
59//! println!("Mean: {}, StdDev: {}", stats.mean, stats.std_dev);
60//! println!("Valid pixels: {}", stats.valid_count);
61//! # Ok::<(), oxigdal_core::error::OxiGdalError>(())
62//! ```
63//!
64//! # See Also
65//!
66//! - [`RasterDataType`] - Supported pixel data types
67//! - [`NoDataValue`] - Representation of missing data
68//! - [`RasterStatistics`] - Pixel statistics
69//!
70//! [`RasterDataType`]: crate::types::RasterDataType
71//! [`NoDataValue`]: crate::types::NoDataValue
72//! [`RasterStatistics`]: crate::types::RasterStatistics
73
74use core::fmt;
75
76use crate::error::{OxiGdalError, Result};
77use crate::types::{NoDataValue, RasterDataType};
78
79/// A typed buffer for raster data
80#[derive(Clone)]
81pub struct RasterBuffer {
82    /// The underlying bytes
83    data: Vec<u8>,
84    /// Width in pixels
85    width: u64,
86    /// Height in pixels
87    height: u64,
88    /// Data type
89    data_type: RasterDataType,
90    /// `NoData` value
91    nodata: NoDataValue,
92}
93
94impl RasterBuffer {
95    /// Creates a new raster buffer
96    ///
97    /// # Errors
98    /// Returns an error if the data size doesn't match the dimensions and type
99    pub fn new(
100        data: Vec<u8>,
101        width: u64,
102        height: u64,
103        data_type: RasterDataType,
104        nodata: NoDataValue,
105    ) -> Result<Self> {
106        let expected_size = width * height * data_type.size_bytes() as u64;
107        if data.len() as u64 != expected_size {
108            return Err(OxiGdalError::InvalidParameter {
109                parameter: "data",
110                message: format!(
111                    "Data size mismatch: expected {} bytes for {}x{} {:?}, got {}",
112                    expected_size,
113                    width,
114                    height,
115                    data_type,
116                    data.len()
117                ),
118            });
119        }
120
121        Ok(Self {
122            data,
123            width,
124            height,
125            data_type,
126            nodata,
127        })
128    }
129
130    /// Creates a zero-filled buffer
131    #[must_use]
132    pub fn zeros(width: u64, height: u64, data_type: RasterDataType) -> Self {
133        let size = (width * height * data_type.size_bytes() as u64) as usize;
134        Self {
135            data: vec![0u8; size],
136            width,
137            height,
138            data_type,
139            nodata: NoDataValue::None,
140        }
141    }
142
143    /// Creates a buffer filled with the nodata value
144    #[must_use]
145    pub fn nodata_filled(
146        width: u64,
147        height: u64,
148        data_type: RasterDataType,
149        nodata: NoDataValue,
150    ) -> Self {
151        let mut buffer = Self::zeros(width, height, data_type);
152        buffer.nodata = nodata;
153
154        // Fill with nodata value if defined
155        if let Some(value) = nodata.as_f64() {
156            buffer.fill_value(value);
157        }
158
159        buffer
160    }
161
162    /// Fills the buffer with a constant value
163    pub fn fill_value(&mut self, value: f64) {
164        match self.data_type {
165            RasterDataType::UInt8 => {
166                let v = value as u8;
167                self.data.fill(v);
168            }
169            RasterDataType::Int8 => {
170                let v = value as i8;
171                self.data.fill(v as u8);
172            }
173            RasterDataType::UInt16 => {
174                let v = (value as u16).to_ne_bytes();
175                for chunk in self.data.chunks_exact_mut(2) {
176                    chunk.copy_from_slice(&v);
177                }
178            }
179            RasterDataType::Int16 => {
180                let v = (value as i16).to_ne_bytes();
181                for chunk in self.data.chunks_exact_mut(2) {
182                    chunk.copy_from_slice(&v);
183                }
184            }
185            RasterDataType::UInt32 => {
186                let v = (value as u32).to_ne_bytes();
187                for chunk in self.data.chunks_exact_mut(4) {
188                    chunk.copy_from_slice(&v);
189                }
190            }
191            RasterDataType::Int32 => {
192                let v = (value as i32).to_ne_bytes();
193                for chunk in self.data.chunks_exact_mut(4) {
194                    chunk.copy_from_slice(&v);
195                }
196            }
197            RasterDataType::Float32 => {
198                let v = (value as f32).to_ne_bytes();
199                for chunk in self.data.chunks_exact_mut(4) {
200                    chunk.copy_from_slice(&v);
201                }
202            }
203            RasterDataType::Float64 => {
204                let v = value.to_ne_bytes();
205                for chunk in self.data.chunks_exact_mut(8) {
206                    chunk.copy_from_slice(&v);
207                }
208            }
209            RasterDataType::UInt64 => {
210                let v = (value as u64).to_ne_bytes();
211                for chunk in self.data.chunks_exact_mut(8) {
212                    chunk.copy_from_slice(&v);
213                }
214            }
215            RasterDataType::Int64 => {
216                let v = (value as i64).to_ne_bytes();
217                for chunk in self.data.chunks_exact_mut(8) {
218                    chunk.copy_from_slice(&v);
219                }
220            }
221            RasterDataType::CFloat32 | RasterDataType::CFloat64 => {
222                // Complex types: fill with (value, 0)
223                // This is a simplified implementation
224            }
225        }
226    }
227
228    /// Returns the width in pixels
229    #[must_use]
230    pub const fn width(&self) -> u64 {
231        self.width
232    }
233
234    /// Returns the height in pixels
235    #[must_use]
236    pub const fn height(&self) -> u64 {
237        self.height
238    }
239
240    /// Returns the data type
241    #[must_use]
242    pub const fn data_type(&self) -> RasterDataType {
243        self.data_type
244    }
245
246    /// Returns the nodata value
247    #[must_use]
248    pub const fn nodata(&self) -> NoDataValue {
249        self.nodata
250    }
251
252    /// Returns the total number of pixels
253    #[must_use]
254    pub const fn pixel_count(&self) -> u64 {
255        self.width * self.height
256    }
257
258    /// Returns the raw bytes
259    #[must_use]
260    pub fn as_bytes(&self) -> &[u8] {
261        &self.data
262    }
263
264    /// Returns mutable raw bytes
265    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
266        &mut self.data
267    }
268
269    /// Consumes the buffer and returns the raw bytes
270    #[must_use]
271    pub fn into_bytes(self) -> Vec<u8> {
272        self.data
273    }
274
275    /// Creates a buffer from typed vector data
276    ///
277    /// # Arguments
278    /// * `width` - Width in pixels
279    /// * `height` - Height in pixels
280    /// * `data` - Typed data (e.g., `Vec<f32>`, `Vec<u8>`)
281    /// * `data_type` - The raster data type
282    ///
283    /// # Errors
284    /// Returns an error if the data size doesn't match dimensions and type
285    pub fn from_typed_vec<T: Copy + 'static>(
286        width: usize,
287        height: usize,
288        data: Vec<T>,
289        data_type: RasterDataType,
290    ) -> Result<Self> {
291        let expected_pixels = width * height;
292        if data.len() != expected_pixels {
293            return Err(OxiGdalError::InvalidParameter {
294                parameter: "data",
295                message: format!(
296                    "Data length mismatch: expected {} pixels for {}x{}, got {}",
297                    expected_pixels,
298                    width,
299                    height,
300                    data.len()
301                ),
302            });
303        }
304
305        // Convert typed data to bytes
306        let type_size = core::mem::size_of::<T>();
307        let expected_type_size = data_type.size_bytes();
308        if type_size != expected_type_size {
309            return Err(OxiGdalError::InvalidParameter {
310                parameter: "data_type",
311                message: format!(
312                    "Type size mismatch: provided type has {} bytes, {:?} expects {} bytes",
313                    type_size, data_type, expected_type_size
314                ),
315            });
316        }
317
318        let byte_data: Vec<u8> = data
319            .iter()
320            .flat_map(|v| {
321                // SAFETY: We're reading the bytes of a Copy type
322                let ptr = v as *const T as *const u8;
323                unsafe { core::slice::from_raw_parts(ptr, type_size) }.to_vec()
324            })
325            .collect();
326
327        Self::new(
328            byte_data,
329            width as u64,
330            height as u64,
331            data_type,
332            NoDataValue::None,
333        )
334    }
335
336    /// Returns the buffer data as a typed slice
337    ///
338    /// # Type Parameters
339    /// * `T` - The target type (must match the buffer's data type size)
340    ///
341    /// # Errors
342    /// Returns an error if the type size doesn't match the data type
343    pub fn as_slice<T: Copy + 'static>(&self) -> Result<&[T]> {
344        let type_size = core::mem::size_of::<T>();
345        let expected_size = self.data_type.size_bytes();
346
347        if type_size != expected_size {
348            return Err(OxiGdalError::InvalidParameter {
349                parameter: "T",
350                message: format!(
351                    "Type size mismatch: requested type has {} bytes, buffer contains {:?} ({} bytes)",
352                    type_size, self.data_type, expected_size
353                ),
354            });
355        }
356
357        let pixel_count = (self.width * self.height) as usize;
358        // SAFETY: We've verified the type size matches, and the data is properly aligned
359        // for the original type it was created with
360        let slice =
361            unsafe { core::slice::from_raw_parts(self.data.as_ptr() as *const T, pixel_count) };
362        Ok(slice)
363    }
364
365    /// Returns the buffer data as a mutable typed slice
366    ///
367    /// # Type Parameters
368    /// * `T` - The target type (must match the buffer's data type size)
369    ///
370    /// # Errors
371    /// Returns an error if the type size doesn't match the data type
372    pub fn as_slice_mut<T: Copy + 'static>(&mut self) -> Result<&mut [T]> {
373        let type_size = core::mem::size_of::<T>();
374        let expected_size = self.data_type.size_bytes();
375
376        if type_size != expected_size {
377            return Err(OxiGdalError::InvalidParameter {
378                parameter: "T",
379                message: format!(
380                    "Type size mismatch: requested type has {} bytes, buffer contains {:?} ({} bytes)",
381                    type_size, self.data_type, expected_size
382                ),
383            });
384        }
385
386        let pixel_count = (self.width * self.height) as usize;
387        // SAFETY: We've verified the type size matches, and the data is properly aligned
388        // for the original type it was created with
389        let slice = unsafe {
390            core::slice::from_raw_parts_mut(self.data.as_mut_ptr() as *mut T, pixel_count)
391        };
392        Ok(slice)
393    }
394
395    /// Gets a pixel value as f64
396    ///
397    /// # Errors
398    /// Returns an error if coordinates are out of bounds
399    pub fn get_pixel(&self, x: u64, y: u64) -> Result<f64> {
400        if x >= self.width || y >= self.height {
401            return Err(OxiGdalError::OutOfBounds {
402                message: format!(
403                    "Pixel ({}, {}) out of bounds for {}x{} buffer",
404                    x, y, self.width, self.height
405                ),
406            });
407        }
408
409        let pixel_size = self.data_type.size_bytes();
410        let offset = (y * self.width + x) as usize * pixel_size;
411
412        let value = match self.data_type {
413            RasterDataType::UInt8 => f64::from(self.data[offset]),
414            RasterDataType::Int8 => f64::from(self.data[offset] as i8),
415            RasterDataType::UInt16 => {
416                let bytes: [u8; 2] = self.data[offset..offset + 2].try_into().map_err(|_| {
417                    OxiGdalError::Internal {
418                        message: "Invalid slice length".to_string(),
419                    }
420                })?;
421                f64::from(u16::from_ne_bytes(bytes))
422            }
423            RasterDataType::Int16 => {
424                let bytes: [u8; 2] = self.data[offset..offset + 2].try_into().map_err(|_| {
425                    OxiGdalError::Internal {
426                        message: "Invalid slice length".to_string(),
427                    }
428                })?;
429                f64::from(i16::from_ne_bytes(bytes))
430            }
431            RasterDataType::UInt32 => {
432                let bytes: [u8; 4] = self.data[offset..offset + 4].try_into().map_err(|_| {
433                    OxiGdalError::Internal {
434                        message: "Invalid slice length".to_string(),
435                    }
436                })?;
437                f64::from(u32::from_ne_bytes(bytes))
438            }
439            RasterDataType::Int32 => {
440                let bytes: [u8; 4] = self.data[offset..offset + 4].try_into().map_err(|_| {
441                    OxiGdalError::Internal {
442                        message: "Invalid slice length".to_string(),
443                    }
444                })?;
445                f64::from(i32::from_ne_bytes(bytes))
446            }
447            RasterDataType::Float32 => {
448                let bytes: [u8; 4] = self.data[offset..offset + 4].try_into().map_err(|_| {
449                    OxiGdalError::Internal {
450                        message: "Invalid slice length".to_string(),
451                    }
452                })?;
453                f64::from(f32::from_ne_bytes(bytes))
454            }
455            RasterDataType::Float64 => {
456                let bytes: [u8; 8] = self.data[offset..offset + 8].try_into().map_err(|_| {
457                    OxiGdalError::Internal {
458                        message: "Invalid slice length".to_string(),
459                    }
460                })?;
461                f64::from_ne_bytes(bytes)
462            }
463            RasterDataType::UInt64 => {
464                let bytes: [u8; 8] = self.data[offset..offset + 8].try_into().map_err(|_| {
465                    OxiGdalError::Internal {
466                        message: "Invalid slice length".to_string(),
467                    }
468                })?;
469                u64::from_ne_bytes(bytes) as f64
470            }
471            RasterDataType::Int64 => {
472                let bytes: [u8; 8] = self.data[offset..offset + 8].try_into().map_err(|_| {
473                    OxiGdalError::Internal {
474                        message: "Invalid slice length".to_string(),
475                    }
476                })?;
477                i64::from_ne_bytes(bytes) as f64
478            }
479            RasterDataType::CFloat32 | RasterDataType::CFloat64 => {
480                // Return only the real part for complex types
481                let bytes: [u8; 4] = self.data[offset..offset + 4].try_into().map_err(|_| {
482                    OxiGdalError::Internal {
483                        message: "Invalid slice length".to_string(),
484                    }
485                })?;
486                f64::from(f32::from_ne_bytes(bytes))
487            }
488        };
489
490        Ok(value)
491    }
492
493    /// Sets a pixel value
494    ///
495    /// # Errors
496    /// Returns an error if coordinates are out of bounds
497    pub fn set_pixel(&mut self, x: u64, y: u64, value: f64) -> Result<()> {
498        if x >= self.width || y >= self.height {
499            return Err(OxiGdalError::OutOfBounds {
500                message: format!(
501                    "Pixel ({}, {}) out of bounds for {}x{} buffer",
502                    x, y, self.width, self.height
503                ),
504            });
505        }
506
507        let pixel_size = self.data_type.size_bytes();
508        let offset = (y * self.width + x) as usize * pixel_size;
509
510        match self.data_type {
511            RasterDataType::UInt8 => {
512                self.data[offset] = value as u8;
513            }
514            RasterDataType::Int8 => {
515                self.data[offset] = (value as i8) as u8;
516            }
517            RasterDataType::UInt16 => {
518                let bytes = (value as u16).to_ne_bytes();
519                self.data[offset..offset + 2].copy_from_slice(&bytes);
520            }
521            RasterDataType::Int16 => {
522                let bytes = (value as i16).to_ne_bytes();
523                self.data[offset..offset + 2].copy_from_slice(&bytes);
524            }
525            RasterDataType::UInt32 => {
526                let bytes = (value as u32).to_ne_bytes();
527                self.data[offset..offset + 4].copy_from_slice(&bytes);
528            }
529            RasterDataType::Int32 => {
530                let bytes = (value as i32).to_ne_bytes();
531                self.data[offset..offset + 4].copy_from_slice(&bytes);
532            }
533            RasterDataType::Float32 => {
534                let bytes = (value as f32).to_ne_bytes();
535                self.data[offset..offset + 4].copy_from_slice(&bytes);
536            }
537            RasterDataType::Float64 => {
538                let bytes = value.to_ne_bytes();
539                self.data[offset..offset + 8].copy_from_slice(&bytes);
540            }
541            RasterDataType::UInt64 => {
542                let bytes = (value as u64).to_ne_bytes();
543                self.data[offset..offset + 8].copy_from_slice(&bytes);
544            }
545            RasterDataType::Int64 => {
546                let bytes = (value as i64).to_ne_bytes();
547                self.data[offset..offset + 8].copy_from_slice(&bytes);
548            }
549            RasterDataType::CFloat32 => {
550                // Set only the real part
551                let bytes = (value as f32).to_ne_bytes();
552                self.data[offset..offset + 4].copy_from_slice(&bytes);
553            }
554            RasterDataType::CFloat64 => {
555                // Set only the real part
556                let bytes = value.to_ne_bytes();
557                self.data[offset..offset + 8].copy_from_slice(&bytes);
558            }
559        }
560
561        Ok(())
562    }
563
564    /// Returns true if the given value equals the nodata value
565    #[must_use]
566    pub fn is_nodata(&self, value: f64) -> bool {
567        match self.nodata.as_f64() {
568            Some(nd) => {
569                if nd.is_nan() && value.is_nan() {
570                    true
571                } else {
572                    (nd - value).abs() < f64::EPSILON
573                }
574            }
575            None => false,
576        }
577    }
578
579    /// Converts the buffer to a different data type
580    ///
581    /// # Errors
582    /// Returns an error if conversion fails
583    pub fn convert_to(&self, target_type: RasterDataType) -> Result<Self> {
584        if target_type == self.data_type {
585            return Ok(self.clone());
586        }
587
588        let mut result = Self::zeros(self.width, self.height, target_type);
589        result.nodata = self.nodata;
590
591        for y in 0..self.height {
592            for x in 0..self.width {
593                let value = self.get_pixel(x, y)?;
594                result.set_pixel(x, y, value)?;
595            }
596        }
597
598        Ok(result)
599    }
600
601    /// Computes basic statistics
602    pub fn compute_statistics(&self) -> Result<BufferStatistics> {
603        let mut min = f64::MAX;
604        let mut max = f64::MIN;
605        let mut sum = 0.0;
606        let mut sum_sq = 0.0;
607        let mut valid_count = 0u64;
608
609        for y in 0..self.height {
610            for x in 0..self.width {
611                let value = self.get_pixel(x, y)?;
612                if !self.is_nodata(value) && value.is_finite() {
613                    min = min.min(value);
614                    max = max.max(value);
615                    sum += value;
616                    sum_sq += value * value;
617                    valid_count += 1;
618                }
619            }
620        }
621
622        if valid_count == 0 {
623            return Ok(BufferStatistics {
624                min: f64::NAN,
625                max: f64::NAN,
626                mean: f64::NAN,
627                std_dev: f64::NAN,
628                valid_count: 0,
629            });
630        }
631
632        let mean = sum / valid_count as f64;
633        let variance = (sum_sq / valid_count as f64) - (mean * mean);
634        let std_dev = variance.sqrt();
635
636        Ok(BufferStatistics {
637            min,
638            max,
639            mean,
640            std_dev,
641            valid_count,
642        })
643    }
644}
645
646impl fmt::Debug for RasterBuffer {
647    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
648        f.debug_struct("RasterBuffer")
649            .field("width", &self.width)
650            .field("height", &self.height)
651            .field("data_type", &self.data_type)
652            .field("nodata", &self.nodata)
653            .field("bytes", &self.data.len())
654            .finish()
655    }
656}
657
658/// Statistics computed from a buffer
659#[derive(Debug, Clone, Copy, PartialEq)]
660pub struct BufferStatistics {
661    /// Minimum value
662    pub min: f64,
663    /// Maximum value
664    pub max: f64,
665    /// Mean value
666    pub mean: f64,
667    /// Standard deviation
668    pub std_dev: f64,
669    /// Number of valid (non-nodata) pixels
670    pub valid_count: u64,
671}
672
673#[cfg(feature = "arrow")]
674mod arrow_support {
675    //! Arrow integration for zero-copy interoperability
676
677    use arrow_array::{Array, Float64Array};
678
679    use super::{OxiGdalError, RasterBuffer, Result};
680
681    impl RasterBuffer {
682        /// Creates a `RasterBuffer` from an Arrow array
683        ///
684        /// # Errors
685        /// Returns an error if the array type doesn't match
686        pub fn from_arrow_array<A: Array>(_array: &A, _width: u64, _height: u64) -> Result<Self> {
687            // This is a simplified implementation
688            // A full implementation would handle all Arrow types
689            Err(OxiGdalError::NotSupported {
690                operation: "Arrow array conversion".to_string(),
691            })
692        }
693
694        /// Converts to an Arrow `Float64Array`
695        pub fn to_float64_array(&self) -> Result<Float64Array> {
696            let mut values = Vec::with_capacity(self.pixel_count() as usize);
697            for y in 0..self.height {
698                for x in 0..self.width {
699                    values.push(self.get_pixel(x, y)?);
700                }
701            }
702            Ok(Float64Array::from(values))
703        }
704    }
705}
706
707#[cfg(test)]
708mod tests {
709    #![allow(clippy::expect_used)]
710
711    use super::*;
712
713    #[test]
714    fn test_buffer_creation() {
715        let buffer = RasterBuffer::zeros(100, 100, RasterDataType::UInt8);
716        assert_eq!(buffer.width(), 100);
717        assert_eq!(buffer.height(), 100);
718        assert_eq!(buffer.pixel_count(), 10_000);
719        assert_eq!(buffer.as_bytes().len(), 10_000);
720    }
721
722    #[test]
723    fn test_pixel_access() {
724        let mut buffer = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
725
726        buffer.set_pixel(5, 5, 42.0).expect("set should work");
727        let value = buffer.get_pixel(5, 5).expect("get should work");
728        assert!((value - 42.0).abs() < f64::EPSILON);
729
730        // Out of bounds
731        assert!(buffer.get_pixel(100, 0).is_err());
732        assert!(buffer.set_pixel(0, 100, 0.0).is_err());
733    }
734
735    #[test]
736    fn test_nodata() {
737        let buffer = RasterBuffer::nodata_filled(
738            10,
739            10,
740            RasterDataType::Float32,
741            NoDataValue::Float(-9999.0),
742        );
743
744        assert!(buffer.is_nodata(-9999.0));
745        assert!(!buffer.is_nodata(0.0));
746    }
747
748    #[test]
749    fn test_statistics() {
750        let mut buffer = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
751
752        // Fill with values 0-99
753        for y in 0..10 {
754            for x in 0..10 {
755                let value = (y * 10 + x) as f64;
756                buffer.set_pixel(x, y, value).expect("set should work");
757            }
758        }
759
760        let stats = buffer.compute_statistics().expect("stats should work");
761        assert!((stats.min - 0.0).abs() < f64::EPSILON);
762        assert!((stats.max - 99.0).abs() < f64::EPSILON);
763        assert!((stats.mean - 49.5).abs() < 0.01);
764        assert_eq!(stats.valid_count, 100);
765    }
766
767    #[test]
768    fn test_data_validation() {
769        // Wrong size should fail
770        let result = RasterBuffer::new(
771            vec![0u8; 100],
772            10,
773            10,
774            RasterDataType::UInt16, // Needs 200 bytes
775            NoDataValue::None,
776        );
777        assert!(result.is_err());
778
779        // Correct size should succeed
780        let result = RasterBuffer::new(
781            vec![0u8; 200],
782            10,
783            10,
784            RasterDataType::UInt16,
785            NoDataValue::None,
786        );
787        assert!(result.is_ok());
788    }
789}