Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lift ndarray compatibility into Buffer<T> #494

Merged
merged 18 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@

## Unreleased

- Defers the gdal_i.lib missing message until after the pkg-config check and outputs pkg-config metadata in case of a static build.
- Added ability to convert between `Buffer<T>` and `ndarray::Array2<T>`.
- Implemented `IntoIterator`, `Index` and `IndexMut` for `Buffer<T>`.
- **Breaking**: `Buffer<T>::size` is now private and accessed via `Buffer<T>::shape().
- **Breaking**: `Buffer<T>::data` is now private and accessed via `Buffer<T>::data().
- **Breaking**: Removed `Rasterband::read_as_array`, changed signature of `Rasterband::read_block` to return a `Buffer<T>`.
- **Breaking**: `Rasterband::write` and `Rasterband::write_block` now require a `&mut Buffer<T>` to handle possible case of drivers temporarily mutating input buffer.

- <https://github.com/georust/gdal/pull/494>

- Defers the `gdal_i.lib` missing message until after the `pkg-config` check and outputs `pkg-config` metadata in case of a static build.

- <https://github.com/georust/gdal/pull/492>

Expand Down
2 changes: 1 addition & 1 deletion examples/rasterband.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ fn main() {
println!("rasterband scale: {:?}", rasterband.scale());
println!("rasterband offset: {:?}", rasterband.offset());
if let Ok(rv) = rasterband.read_as::<u8>((20, 30), (2, 3), (2, 3), None) {
println!("{:?}", rv.data);
println!("{:?}", rv.data());
}
}
222 changes: 222 additions & 0 deletions src/raster/buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
use crate::raster::GdalType;
use std::ops::{Index, IndexMut};
use std::slice::Iter;

#[cfg(feature = "ndarray")]
use ndarray::Array2;

/// A 2-D array backed by it's `size` (cols, rows) and a row-major `Vec<T>` and it's dimensions.
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct Buffer<T> {
metasim marked this conversation as resolved.
Show resolved Hide resolved
shape: (usize, usize),
data: Vec<T>,
}

impl<T: GdalType> Buffer<T> {
/// Construct a new buffer from `size` (`(cols, rows)`) and `Vec<T>`.
///
/// # Notes
/// The elements of `shape` are in reverse order from what is used in `ndarray`.
///
/// # Panics
/// Will panic if `size.0 * size.1 != data.len()`.
pub fn new(shape: (usize, usize), data: Vec<T>) -> Self {
assert_eq!(
shape.0 * shape.1,
data.len(),
"size {:?} does not match length {}",
shape,
data.len()
);
Buffer { shape, data }
}

/// De-structures `self` into constituent parts.
metasim marked this conversation as resolved.
Show resolved Hide resolved
pub fn into_shape_and_vec(self) -> ((usize, usize), Vec<T>) {
(self.shape, self.data)
}

/// Gets the 2-d shape of the buffer.
//
/// Returns `(cols, rows)`
///
/// # Notes
/// The elements of `shape` are in reverse order from what is used in `ndarray`.
pub fn shape(&self) -> (usize, usize) {
self.shape
}

/// Get a slice over the buffer contents.
pub fn data(&self) -> &[T] {
self.data.as_slice()
}

/// Get a mutable slice over the buffer contents.
pub fn data_mut(&mut self) -> &mut [T] {
self.data.as_mut_slice()
}

/// Get the number of elements in the buffer
pub fn len(&self) -> usize {
self.data.len()
}

/// Determine if the buffer has no elements.
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}

#[cfg(feature = "ndarray")]
/// Convert `self` into an [`ndarray::Array2`].
pub fn to_array(self) -> crate::errors::Result<Array2<T>> {
// Array2 shape is (rows, cols) and Buffer shape is (cols in x-axis, rows in y-axis)
Ok(Array2::from_shape_vec(
(self.shape.1, self.shape.0),
self.data,
)?)
}

#[inline]
pub(self) fn vec_index_for(&self, coord: (usize, usize)) -> usize {
metasim marked this conversation as resolved.
Show resolved Hide resolved
if coord.0 >= self.shape.0 {
panic!(
"index out of bounds: buffer has {} columns but row {} was requested",
self.shape.0, coord.0
);
}
if coord.1 >= self.shape.1 {
panic!(
"index out of bounds: buffer has {} rows but row {} was requested",
self.shape.1, coord.1
);
}
coord.0 * self.shape.0 + coord.1
metasim marked this conversation as resolved.
Show resolved Hide resolved
}
}

impl<T: GdalType> Index<(usize, usize)> for Buffer<T> {
type Output = T;
fn index(&self, index: (usize, usize)) -> &Self::Output {
&self.data[self.vec_index_for(index)]
}
}

impl<T: GdalType> IndexMut<(usize, usize)> for Buffer<T> {
fn index_mut(&mut self, index: (usize, usize)) -> &mut Self::Output {
let idx = self.vec_index_for(index);
&mut self.data[idx]
}
}

impl<'a, T: GdalType> IntoIterator for &'a Buffer<T> {
metasim marked this conversation as resolved.
Show resolved Hide resolved
type Item = &'a T;
type IntoIter = Iter<'a, T>;

fn into_iter(self) -> Self::IntoIter {
self.data.iter()
}
}
metasim marked this conversation as resolved.
Show resolved Hide resolved

pub type ByteBuffer = Buffer<u8>;
metasim marked this conversation as resolved.
Show resolved Hide resolved

#[cfg(feature = "ndarray")]
impl<T: GdalType> TryFrom<Buffer<T>> for Array2<T> {
type Error = crate::errors::GdalError;

fn try_from(value: Buffer<T>) -> Result<Self, Self::Error> {
value.to_array()
}
}

#[cfg(feature = "ndarray")]
impl<T: GdalType + Copy> From<Array2<T>> for Buffer<T> {
fn from(value: Array2<T>) -> Self {
// Array2 shape is (rows, cols) and Buffer shape is (cols in x-axis, rows in y-axis)
let shape = value.shape();
let (rows, cols) = (shape[0], shape[1]);
metasim marked this conversation as resolved.
Show resolved Hide resolved
let data: Vec<T> = if value.is_standard_layout() {
value.into_raw_vec()
} else {
value.iter().copied().collect()
};

Buffer::new((cols, rows), data)
}
}

#[cfg(feature = "ndarray")]
#[cfg(test)]
mod tests {
use crate::raster::Buffer;
use ndarray::{Array2, ShapeBuilder};

#[test]
fn convert_to() {
let b = Buffer::new((5, 10), (0..5 * 10).collect());
let a = b.clone().to_array().unwrap();
let expected = Array2::from_shape_fn((10, 5), |(row, col)| row as i32 * 5 + col as i32);
assert_eq!(a, expected);
let b2: Buffer<_> = a.into();
assert_eq!(b, b2);
}

#[test]
fn convert_from() {
let a = Array2::from_shape_fn((10, 5), |(row, col)| row as i32 * 5 + col as i32);
metasim marked this conversation as resolved.
Show resolved Hide resolved
let b: Buffer<_> = a.clone().into();
let expected = Buffer::new((5, 10), (0..5 * 10).collect());
assert_eq!(b, expected);

let a2 = b.to_array().unwrap();
assert_eq!(a, a2);
}

#[test]
fn shapes() {
let s1 = (10, 5).into_shape().set_f(true);
let s2 = (10, 5).into_shape().set_f(false);

let a1 = Array2::from_shape_fn(s1, |(y, x)| y as i32 * 5 + x as i32);
let a2 = Array2::from_shape_fn(s2, |(y, x)| y as i32 * 5 + x as i32);

let expected = Buffer::new((5, 10), (0..5 * 10).collect());

assert_eq!(a1, expected.clone().to_array().unwrap());

let b1: Buffer<_> = a1.into();
let b2: Buffer<_> = a2.into();

assert_eq!(b1, expected);
assert_eq!(b2, expected);
}

#[test]
fn index() {
let b = Buffer::new((5, 7), (0..5 * 7).collect());
assert_eq!(b[(0, 0)], 0);
assert_eq!(b[(1, 1)], 5 + 1);
assert_eq!(b[(4, 5)], 4 * 5 + 5);

let mut b = b;
b[(2, 2)] = 99;
assert_eq!(b[(2, 1)], 2 * 5 + 1);
assert_eq!(b[(2, 2)], 99);
assert_eq!(b[(2, 3)], 2 * 5 + 3);
}

#[test]
fn iter() {
let b = Buffer::new((5, 7), (0..5 * 7).collect());
let mut iter = b.into_iter();
let _ = iter.next().unwrap();
let v = iter.next().unwrap();
assert_eq!(*v, b[(0, 1)]);
}

#[test]
#[should_panic]
fn index_bounds_panic() {
let b = Buffer::new((5, 7), (0..5 * 7).collect());
let _ = b[(5, 0)];
}
}
2 changes: 1 addition & 1 deletion src/raster/mdarray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ impl<'a> MDArray<'a> {

#[cfg(feature = "ndarray")]
#[cfg_attr(docsrs, doc(cfg(feature = "array")))]
/// Read a 'Array2<T>' from this band. T implements 'GdalType'.
/// Read a [`ArrayD<T>`] from this band. T implements [`GdalType`].
metasim marked this conversation as resolved.
Show resolved Hide resolved
///
/// # Arguments
/// * `window` - the window position from top left
Expand Down
32 changes: 16 additions & 16 deletions src/raster/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
//! let rv = band.read_as::<u8>(window, window_size, size, resample_alg)?;
//! // `Rasterband::read_as` returns a `Buffer` struct, which contains the shape of the output
//! // `(cols, rows)` and a `Vec<_>` containing the pixel values.
//! println!(" Data size: {:?}", rv.size);
//! println!(" Data values: {:?}", rv.data);
//! println!(" Data size: {:?}", rv.shape());
//! println!(" Data values: {:?}", rv.data());
//! }
//! # Ok(())
//! # }
Expand All @@ -74,27 +74,30 @@
//! ...
//! ```

#[cfg(all(major_ge_3, minor_ge_1))]
mod mdarray;
pub mod processing;
mod rasterband;
mod rasterize;
mod types;
mod warp;

pub use buffer::{Buffer, ByteBuffer};
#[cfg(all(major_ge_3, minor_ge_1))]
pub use mdarray::{
Attribute, Dimension, ExtendedDataType, ExtendedDataTypeClass, Group, MDArray, MdStatisticsAll,
};
pub use rasterband::{
Buffer, ByteBuffer, CmykEntry, ColorEntry, ColorInterpretation, ColorTable, GrayEntry,
Histogram, HlsEntry, PaletteInterpretation, RasterBand, ResampleAlg, RgbaEntry, StatisticsAll,
StatisticsMinMax,
CmykEntry, ColorEntry, ColorInterpretation, ColorTable, GrayEntry, Histogram, HlsEntry,
PaletteInterpretation, RasterBand, ResampleAlg, RgbaEntry, StatisticsAll, StatisticsMinMax,
};
pub use rasterize::{rasterize, BurnSource, MergeAlgorithm, OptimizeMode, RasterizeOptions};
pub use types::{AdjustedValue, GdalDataType, GdalType};
pub use warp::reproject;

mod buffer;
#[cfg(all(major_ge_3, minor_ge_1))]
mod mdarray;
pub mod processing;
mod rasterband;
mod rasterize;
#[cfg(test)]
mod tests;
mod types;
mod warp;

/// Key/value pair for passing driver-specific creation options to
/// [`Driver::create_with_band_type_wth_options`](crate::Driver::create_with_band_type_with_options`).
///
Expand All @@ -104,6 +107,3 @@ pub struct RasterCreationOption<'a> {
pub key: &'a str,
pub value: &'a str,
}

#[cfg(test)]
mod tests;
Loading