4

I have ndarray

let mut a = Array3::<u8>::from_elem((50, 40, 3), 3);

and I use image library

 let mut imgbuf = image::ImageBuffer::new(50, 40);

How could I save my ndarray as image ? If there is better image library then image for this I could use it.

user3048747
  • 179
  • 1
  • 9

1 Answers1

6

The easiest way is to ensure that the array follows is in standard layout (C-contiguous) with the image dimensions in the order (height, width, channel) order (HWC), or in an equivalent memory layout. This is necessary because image expects rows to be contiguous in memory.

Then, build a RgbImage using the type's from_raw function.

use image::RgbImage;
use ndarray::Array3;

fn array_to_image(arr: Array3<u8>) -> RgbImage {
    assert!(arr.is_standard_layout());

    let (height, width, _) = arr.dim();
    let raw = arr.into_raw_vec();

    RgbImage::from_raw(width as u32, height as u32, raw)
        .expect("container should have the right size for the image dimensions")
}

Example of use:

let mut array: Array3<u8> = Array3::zeros((200, 250, 3)); // 250x200 RGB

for ((x, y, z), v) in array.indexed_iter_mut() {
    *v = match z {
        0 => y as u8,
        1 => x as u8,
        2 => 0,
        _ => unreachable!(),
    };
}

let image = array_to_image(array);
image.save("out.png")?;

The output image:

the output image: a gradient over the red and green channels


Below are a few related helper functions, in case they are necessary.

Ndarrays can be converted to standard layout by calling the method as_standard_layout, available since version 0.13.0. Before this version, you would need to collect each array element into a vector and rebuild the array, like so:

fn to_standard_layout<A, D>(arr: Array<A, D>) -> Array<A, D>
where
    A: Clone,
    D: Dimension,
{
    let v: Vec<_> = arr.iter().cloned().collect();
    let dim = arr.dim();
    Array::from_shape_vec(dim, v).unwrap()
}

Moreover, converting an ndarray in the layout (width, height, channel) to (height, width, channel) is also possible by swapping the first two axes and making the array C-contiguous afterwards:

fn wh_to_hw(mut arr: Array3<u8>) -> Array3<u8> {
    arr.swap_axes(0, 1);

    arr.as_standard_layout().to_owned()
}
E_net4
  • 27,810
  • 13
  • 101
  • 139
  • Is this fast? Would it be faster than manipulating with pixels using for cycle without using ndarray? Like this: for (x, y, pixel) in imgbuf.enumerate_pixels_mut() { let (r, g, b) = some_math(x,y) } *pixel = image::Rgb([r as u8, g as u8, b as u8]); imgbuf.save(format!("image.png" )).unwrap(); – user3048747 Jun 26 '19 at 19:54
  • 1
    @user3048747 Right. Please see my edit. I have replaced the input with an owned array, so that the raw vector can be reused for the `RgbImage`. As long as the array is already in standard layout and the 3rd axis matches the expected RGB channels, this is a zero-copy operation and is just about as fast as it can be. – E_net4 Jun 26 '19 at 20:08
  • I've tried but received `borrow of moved value: arr` error and some others. – user3048747 Jun 27 '19 at 18:36
  • The last question: I have `cannot use the ? operator in a function that returns ()`. When I remove it everything compails, but with warning that `this Result may be an Err variant, which should be handled` Why is this happening? – user3048747 Jun 27 '19 at 18:51
  • 1
    Please see [this question](https://stackoverflow.com/q/30555477/1233251). When code examples use `?`, it is implicit that they sit in a function returning a `Result`. – E_net4 Jun 27 '19 at 19:19
  • I've found that this code isn't quite well. If using none square array (f.e. 300 x 200 px) it gives artifacts in image. As I'v understood the problem is in converting from ndarray to raw vec and then to image. But I didn't manage to find solution for this – user3048747 Jul 17 '19 at 17:37
  • 1
    Thanks for the heads up, the answer has been updated again. It happens that image libraries usually expect rows to be contiguous in memory, but an ndarray with dimensions `(width, height, channels)` is actually column contiguous (pixel-wise). The image needs to be in standard layout with the image dimensions in the order `(height, width, channels)`. – E_net4 Jul 17 '19 at 19:15
  • How would this be adapted to support RGBA? – Kot Jul 12 '23 at 17:16