I would recommended not using parse_example
to start with. There are several options for sending image data, each with tradeoffs in complexity and payload size:
- Raw Tensor Encoded as JSON
- Tensors Packed as Byte Strings
- Compressed Image Data
In each case, it is important to note that the input placeholders must have 'None' as the outer-dimension of their shape. This is the "batch_size" dimension (required, even if you intend to send images one-by-one to the service).
Raw Tensor Encoded as JSON
# Dimensions represent [batch size, height width, channels]
input_images = tf.placeholder(dtype=tf.float32, shape=[None,320,240,3], name='source')
output_tensor = foo(input_images)
# Export the SavedModel
inputs = {'image': input_images}
outputs = {'output': output_tensor}
# ....
The JSON you send to the service will look like as documented (see "Instances JSON string"). For example, (I recommend removing as much white space as possible; pretty printed here for readability):
{
"instances": [
{
"image": [
[
[1,1,1], [1,1,1], ... 240 total ... [1,1,1]
],
... 320 total ...
[
[1,1,1], [1,1,1], ... 240 total ... [1,1,1]
]
]
},
{
"image": [ ... repeat if you have more than one image in the request ... ]
]
}
Please note that gcloud
builds that request body from an input file format where each input is on a separate line (and most be packed on a single line), i.e.:
{"image": [[[1,1,1], [1,1,1], <240 of these>] ... <320 of these>]}
{"image": [[[2,2,2], [2,2,2], <240 of these>] ... <320 of these>]}
Tensors Packed as Byte Strings
If you're doing resizing, etc. on the client, my recommendation is to send a byte string. JSON can be a fairly inefficient way to send floats over the wire; even sending integer data causes bloat. Instead, you can encode the bytes on the client and decode them in TensorFlow. My recommendation is to use uint8
data.
This is the TensorFlow Model code to decode bytes strings:
raw_byte_strings = tf.placeholder(dtype=tf.string, shape=[None], name='source')
# Decode the images. The shape of raw_byte_strings is [batch size]
# (were batch size is determined by how many images are sent), and
# the shape of `input_images` is [batch size, 320, 240, 3]. It's
# important that all of the images sent have the same dimensions
# or errors will result.
#
# We have to use a map_fn because decode_raw only works on a single
# image, and we need to decode a batch of images.
decode = lambda raw_byte_str: tf.decode_raw(raw_byte_str, tf.uint8)
input_images = tf.map_fn(decode, raw_byte_strings, dtype=tf.uint8)
output_tensor = foo(input_images)
# Export the SavedModel
inputs = {'image_bytes': input_images}
outputs = {'output': output_tensor}
# ....
One special note here: as pointed out by Jeremy Lewi, the name of this input alias must end in _bytes
(image_bytes
). This is because JSON doesn't have a way of distinguish text form binary data.
Note that the same trick can be applied to float data, not just uint8 data.
Your client would be responsible for creating a bytes string of uint8s. Here's how you would do that in Python using numpy
.
import base64
import json
import numpy as np
images = []
# In real life, this is obtained via other means, e.g. scipy.misc.imread), for now, an array of all 1s
images.append(np.array([[[2]*3]*240]*320], dtype=np.uint8))
# If we want, we can send more than one image:
images.append(np.array([[[2]*3]*240]*320], dtype=np.uint8))
# Convert each image to byte strings
bytes_strings = (i.tostring() for i in images)
# Base64 encode the data
encoded = (base64.b64encode(b) for b in bytes_strings)
# Create a list of images suitable to send to the service as JSON:
instances = [{'image_bytes': {'b64': e}} for e in encoded]
# Create a JSON request
request = json.dumps({'instances': instances})
# Or if dumping a file for gcloud:
file_data = '\n'.join(json.dumps(instances))
Compressed Image Data
It is often most convenient to send the original images and do the resizing and decoding in TensorFlow. This is exemplified in this sample, which I won't repeat here. The client simply needs to send the raw JPEG bytes. Same note about _bytes
suffix applies here.
tf.saved_model.signature_def_utils.build_signature_def( inputs={'image_bytes': tensor_inputs_info}
what exactly do meaninput/output
aliases? do you have a short example? – Hammons