-1

The google vision API requires a bitmap sent as an argument. I am trying to convert a png from a URL to a bitmap to pass to the google api:

require "google/cloud/vision"
PROJECT_ID = Rails.application.secrets["project_id"]
KEY_FILE = "#{Rails.root}/#{Rails.application.secrets["key_file"]}"
google_vision = Google::Cloud::Vision.new project: PROJECT_ID, keyfile: KEY_FILE
img = open("https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png").read
image = google_vision.image img
ArgumentError: string contains null byte

This is the source code processing of the gem:

    def self.from_source source, vision = nil
      if source.respond_to?(:read) && source.respond_to?(:rewind)
        return from_io(source, vision)
      end
      # Convert Storage::File objects to the URL
      source = source.to_gs_url if source.respond_to? :to_gs_url
      # Everything should be a string from now on
      source = String source
      # Create an Image from a HTTP/HTTPS URL or Google Storage URL.
      return from_url(source, vision) if url? source
      # Create an image from a file on the filesystem
      if File.file? source
        unless File.readable? source
          fail ArgumentError, "Cannot read #{source}"
        end
        return from_io(File.open(source, "rb"), vision)
      end
      fail ArgumentError, "Unable to convert #{source} to an Image"
    end

https://github.com/GoogleCloudPlatform/google-cloud-ruby

Why is it telling me string contains null byte? How can I get a bitmap in ruby?

Daniel Viglione
  • 8,014
  • 9
  • 67
  • 101

3 Answers3

1

According to the documentation (which, to be fair, is not exactly easy to find without digging into the source code), Google::Cloud::Vision#image doesn't want the raw image bytes, it wants a path or URL of some sort:

Use Vision::Project#image to create images for the Cloud Vision service.

You can provide a file path:
[...]
Or any publicly-accessible image HTTP/HTTPS URL:
[...]
Or, you can initialize the image with a Google Cloud Storage URI:

So you'd want to say something like:

image = google_vision.image "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"

instead of reading the image data yourself.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • That is the first thing I tried to do and it gave a different error. So now I am just trying to send an image rather than a url. I guess I am going to have to create a file first like this: File.open('test.png', 'wb') do |fo| fo.write open("https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png").read end – Daniel Viglione Dec 15 '17 at 20:02
1

Instead of using write you want to use IO.copy_stream as it streams the download straight to the file system instead of reading the whole file into memory and then writing it:

require 'open-uri'
require 'tempfile' 
uri = URI("https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png")
tmp_img = Tempfile.new(uri.path.split('/').last)
IO.copy_stream(open(uri), tmp_img)

Note that you don't need to set the 'r:BINARY' flag as the bytes are just streamed without actually reading the file.

You can then use the file by:

require "google/cloud/vision"
# Use fetch as it raises an error if the key is not present
PROJECT_ID = Rails.application.secrets.fetch("project_id")
# Rails.root is a Pathname object so use `.join` to construct paths
KEY_FILE = Rails.root.join(Rails.application.secrets.fetch("key_file"))

google_vision = Google::Cloud::Vision.new(
  project: PROJECT_ID, 
  keyfile: KEY_FILE
)
image = google_vision.image(File.absolute_path(tmp_img))

When you are done you clean up by calling tmp_img.unlink.

max
  • 96,212
  • 14
  • 104
  • 165
0

Remember to read things in binary format:

open("https://www.google.com/..._272x92dp.png",'r:BINARY').read

If you forget this it might try and open it as UTF-8 textual data which would cause lots of problems.

tadman
  • 208,517
  • 23
  • 234
  • 262