Converting NArray to Magick::Image in Ruby

316 views Asked by At

Is there an efficient way to create an RMagick image from the data in a 2D NArray (a Ruby class that's supposed to be more efficient than regular arrays), or are the two libraries just incompatible in their data types?

The following code works, but it does it the hard way: converting data types pixel by pixel with nested do-loops. As far as I can tell, this gives me a lot of extra work and none of the advantages of NArray. It runs slower than molasses in January:

  def LineaProcessor.createImage(dataArray, width, height, filename)

    image = Magick::Image.new width, height

    # scale pixel values from 0..256*255 (the RMagick pixel uses 16 bits)
    scaling = 65280/dataArray.max

    # set each pixel...I couldn't find any easy way to convert array types
    width.times do |x|
        height.times do |y|
            g = dataArray[x+y*width]*scaling
            pixel = Magick::Pixel.new(g, g, g,0)
            image.pixel_color x, y, pixel 
      end
    end

    image
  end  
2

There are 2 answers

0
wedesoft On

You can convert a greyscale 8-bit NArray to an RMagick image like this

require 'narray'
require 'RMagick'
class NArray
  def to_magick
    retval = Magick::Image.new(*shape) { self.depth = 8 }
    retval.import_pixels 0, 0, *shape, 'I', to_s, Magick::CharPixel
    retval
  end
end
sample = NArray.byte(8, 32).indgen.to_magick
2
Neil Slater On

Here is some code I used to do this for greyscale only, and seems relatively quick:

module Convert
  PX_SCALE = ( 2 ** Magick::QuantumDepth  ).to_f

  # Converts 2D NArray of floats 0.0->1.0 to Magick::Image greyscale (16-bit depth)
  def self.narray_to_image na
    raise( ArgumentError, "Input should be NArray, but it is a #{na.class}") unless na.is_a?(NArray)
    raise( ArgumentError, "Input should have 2 dimensions, but it has #{na.dim}" ) unless na.dim == 2
    width, height = na.shape
    img = Magick::Image.new( width, height ) { self.depth = 16; self.colorspace = Magick::GRAYColorspace }
    img.import_pixels(0, 0, width, height, 'I', na.flatten, Magick::DoublePixel )
    img
  end

  # Converts Magick::Image greyscale to 2D NArray of floats 0.0 -> 1.0
  def self.image_to_narray img
    width = img.columns
    height = img.rows
    pixels = NArray.cast( img.export_pixels( 0, 0, width, height, 'I' ).map { |x| x/PX_SCALE } )
    pixels.reshape( width, height )
  end
end

The key methods to read up on are Magick::Image#import_pixels, Magick::Image#export_pixels and NArray.cast

It should be possible to do something similar channel-by-channel to deal with colour images. There is no fundamental reason why you have to use floats, I just wanted the format for my purpose (input to neural net)