Hello!
I'm trying to create an iterator over an image (coming from Python) that is stored in a NumPy array. I need to go through every pixel and do some operations (basically transform every pixel into something else).
I think someone somewhere should have done something similar.
My attempt looks something like this:
fn transform_image<'py>(
py: Python<'py>,
x: PyReadonlyArray3<'py, f64>,
) -> PyResult<&'py PyArray3<f64>> {
let x = x.as_array();
let a: ndarray::Array3<f64> = x
.axis_iter(Axis(0))
.enumerate()
.map(|(x, row)| {
let c: ArrayVec<_, 2> = row
.axis_iter(Axis(0))
.enumerate()
.map(|(y, channel)| {
// channel is rgb as [r,g,b].
[x as f64, y as f64]
})
.collect();
c.into_inner().unwrap()
})
.collect_vec()
.into();
Ok(a.into_pyarray(py))
}
This complies but gets runtime error: pyo3_runtime.PanicException: ArrayVec: capacity exceeded in extend/from_iter
Does anyone who has a clue how to do this effectively?
Solution (non-iterator)
Maybe the problem is not suitable using iterators as the arrays are not dynamically allocated. There is probably a good way of doing it with iterators (and found in my search map_inplace()
that may or may not be useful).
Just a dirty solution, that first allocates the array with zeros and then mutates all of them:
#[pyfn(m)]
fn transform_image<'py>(
py: Python<'py>,
x: PyReadonlyArray3<'py, f64>,
) -> PyResult<&'py PyArray3<f64>> {
let x = x.as_array();
let shape = x.shape();
let h = shape[0];
let w = shape[1];
let new_channel_size = 2;
let mut transformed_image = ndarray::Array3::<f64>::zeros((h, w, new_channel_size));
for (x, row) in x.axis_iter(Axis(0)).enumerate() {
for (y, channel) in row.axis_iter(Axis(0)).enumerate() {
let mut c = transformed_image.slice_mut(s![x, y, ..]);
c[0] = x as f64;
c[1] = y as f64;
}
}
Ok(transformed_image.into_pyarray(py))
}
In terms of readability, I find this to be more understandable.