AI Inference Server offers two options for recording data from the IE Vision Connector.
via Databus (low data throughput)
via ZMQ (high data throughput)
The second method has higher performance and is therefore recommended for images with higher resolution.
A detailed configuration description can be found in the IE Vision Connector user manual.
IE Vision Connector with low data throughput (Databus-based, string)
Only an earlier version of the IE Vision Connector (p 0.9.5) supports sending images to the Databus. This payload format in the AI Inference Server is only retained for reasons of backward compatibility. Newer versions of IE Vision Connector (≥ v 1.0.0) only support data transfer with a high data throughput.
Pipeline configuration
The type String should be chosen for the relevant variable.
Like with the other connectors, this connector should be selected/created in the Data connection section of the configuration page.
The preset port is the same as for the Databus: 1883
The Topic-Variable-Mapping is the same as in the case of the Databus.
The payload structure with use of the IE Vision Connector via Databus looks like this:
{ |
"timestamp":"2021-12-14T10:37:21.315513", |
"sensor_id":"a204dba4-274e-43ce-9a71-55de9e715e72", |
"image":"data:image/png;base64,iVBORw0KGgoAAAANSU…", |
"status":{"genicam_signal":{"code":3}} |
} |
The Python script should take care of handling this payload format.
Example script, assuming the vision_payload parameter contains the above payload JSON object, get_image_from_vision_payload returns the image object as PIL image:
from urllib.request import urlopen |
from PIL import Image |
import io |
from keras.preprocessing import image as imglib |
import numpy as np |
from tensorflow import keras |
from log_module import LogModule |
logger = LogModule() |
model = keras.models.load_model('models/classification_mobilnet.h5') |
IMAGE_WIDTH = 224 |
IMAGE_HEIGHT = 224 |
IMAGE_SIZE = (IMAGE_WIDTH, IMAGE_HEIGHT) |
SCALE = 255 |
def get_image_from_vision_payload(vision_payload): |
""" |
Takes a Vision Connector JSON payload, decodes the image and returns it as PIL image object. Returns None if decoding fails. |
""" |
global IMAGE_SIZE |
try: |
image_string = vision_payload['image'] |
with urlopen(image_string) as response: |
assert response.headers["Content-type"] in ["image/png", "image/jpeg"] |
logger.debug("Verified image type is PNG or JPEG") |
image_bytes = response.read() |
pil_image = Image.open(io.BytesIO(image_bytes)).resize(IMAGE_SIZE) |
logger.debug(f"Image info: {pil_image}") |
return pil_image |
except BaseException: |
logger.debug("Error decoding image from vision payload") |
return None |
IE Vision Connector with high data throughput (ZMQ-based, multi-part)
For performance reasons, the Vision Connector can publish the images in binary format via the ZMQ protocol
Pipeline configuration
The AI expert should select the type (ImageSet or Object (legacy type)) for the variable of the model. ImageSet is preferred.
The connector configuration is shown in the following screenshot. The default port is 5555
Example of a topic-variable mapping:
The Python script receives this variable as dict, which is structured differently for the ImageSet and Object input types.
The data flow from the camera to the Python script looks like this:
For the ImageSet type, the dict variable is structured as follows:
from log_module import LogModule |
logger = LogModule() |
def process_input(data: dict): |
# Note: the input dict of the process_input always contains a timestamp field which is a string and it is conform to the ISO 8601 |
# In this case it will be equal to the data["image"]["timestamp"] |
logger.info("Input timestamp: {0}".format(data["timestamp"])) |
logger.info("Data:") |
logger.info("Imageset version: {0}".format(data["image"]["version"])) |
logger.info("Imageset timestamp: {0}".format(data["image"]["timestamp"])) |
logger.info("Imageset cameraid: {0}".format(data["image"]["cameraid"])) |
images = data["image"]["detail"] |
logger.info("Timestamp of the input: {0}".format(str(images[0]["id"]))) |
logger.info("Image id: {0}".format(str(images[0]["id"]))) |
logger.info("Image seq: {0}".format(str(images[0]["seq"]))) |
# the timestamp in the detail is optional and it is a string allowing other timestamp formats beside the ISO 8601 |
if "timestamp" in images[0]: |
logger.info("Image timestamp: {0}".format(images[0]["timestamp"])) |
logger.info("Image format: {0}".format(images[0]["format"])) |
# The ‘Genicam’ defined formats like BGR8 or Mono8 always provide the width and height, but other formats may not provide it. For example the "PNG" od "JPG"formats will not provide it. |
if "width" in images[0]: |
logger.info("Image width: {0}".format(str(images[0]["width"]))) |
logger.info("Image height: {0}".format(str(images[0]["height"]))) |
# Line padding is optional, its existence depends on the ‘format’ |
if "linepadding" in images[0]: |
logger.info("Image linepadding: {0}".format(images[0]["linepadding"])) |
logger.info("Image byte size: {0}".format(str(len(images[0]["image"])))) |
# call the model |
... |
# prepare and return the prediction result |
output = ... |
|
return output |
The "ImageSet" input type in the pipeline configuration is only supported with the newly introduced "process_input" interface.
The data always contains the "timestamp" field as string in ISO 8601 format. For the ImageSet type, this is identical to the "timestamp" within the "data" input.
The data contains the variable from the pipeline package if it was set to "image" in this example.
The types of the various elements in the image are as follows:
version – String – Version of the message format. Latest: "1"
cameraid – String – ID of the camera device
timestamp – String - in ISO 8601 format with millisecond accuracy, e.g. "2022-01-31T20:09:28.987Z"
customfields – String - JSON containing additional information added to the image message
detail – array<Image Detail> - Array field consisting of N elements. The content includes necessary information for the image processing
Image Detail
– String – Image format specified in the GenICam standard, see: https://www.emva.org/wp-content/uploads/GenICam_SFNC_v2_7.pdf, e.g. RGB8, BGR8, Mono8. The most likely values might be: "Genicam", "Compressed".
width – Integer – Image width Mandatory for formats that require a width (e.g. RGB8 and all Genicam formats).
heigth – Integer – Image height Mandatory for formats that require a height (e.g. RGB8 and all Genicam formats).
metadata – String – Any JSON data about the image
linepadding – optional integer – is provided for some Genicam formats. Although optional, it is set to 0 by the VCA.
image – Binary – Binary representation of the image
The so-called multi-part ZMQ binary format looks like this in Python syntax:
[ |
b'"6ec59ac7-9288-402f-9008-4a2aaa414f8e" |
b'{"version":"1","count":1,"detail":[{"id":"6ec59ac7-9288-402f-9008-4a2aaa414f8e","seq":987, "height":123,"width":345, "format":"RGB", "linepadding":0, "metadata":{"custom":"field"}}],"timestamp":"2022-01-31T20:09:28.987Z","customfields":{"custom":"field"}} |
b'\x18\x18\x1b\x18\xbe\xbb\xb7\xf9\xf9\xf3\xeb\xe8\xe3\xec\xec\xe6\xeb\xec\xea\xe1\xef\xed\xc3\xe7\xe7\xbc... |
] |
For the legacy type Object, the dict variable is structured as follows:
from log_module import LogModule |
logger = LogModule() |
def process_input(data: dict): |
logger.info("Data:") |
logger.info("Image format: {0}".format(data["image"]["mimeType"])) |
logger.info("Image width: {0}".format(str(data["image"]["resolutionWidth"]))) |
logger.info("Image height: {0}".format(str(data["image"]["resolutionHeight"]))) |
logger.info("Image data type: {0}".format(data["image"]["dataType"])) |
logger.info("Image color channels: {0}".format(str(data["image"]["channelsPerPixel"]))) |
logger.info("Image byte size: {0}".format(str(len(data["image"]["image"])))) |
# call the model |
... |
# prepare and return the prediction result |
output = ... |
|
return output |
The Object and ImageSet input types in the pipeline configuration are only supported with the newly introduced interface process_input.
The data contains the variable from the pipeline package if it was set to image in this example.
The data also includes the time stamp of the image
timestamp: String in ISO8601 format with millisecond accuracy, e.g. "2022-01-31T20:09:28.987Z"
The types of the various elements in the image are as follows:
mimeType: String – always "image/raw"
resolutionWidth, resolutionHeight: Integer – example: 640 or 480
dataType: String – always "uint8" – 1 byte per color channel
channelsPerPixel – Always 3, and the color order is always BGR
image – bytes() – contains the image, each pixel is defined by 3 bytes
The so-called multi-part ZMQ binary format looks like this in Python syntax:
[ |
b'{"timestamp": "2022-01-31-T20:09:28.987251""sensor_id": "bd41278f-7a40-4c42-9e96-c6d5be2695fa", "Content-Type": ["image/raw"]}', |
b'[{"dtype": "uint8", "shape": [360, 640, 3]}]', |
b' \x18\x18 \x1b\x18\xbe\xbb\xb7\xf9\xf9\xf3\xeb\xe8\xe3\xec\xec\xe6\xeb\xec\xea\xe1\xef\xed\xc3\xe7\xe7\xbc...' |
] |
The mapping between the multi-part binary file and elements of the dict for the above image variable is as follows in the input:
ZMQ multi-part |
Variable |
Comment |
|---|---|---|
The first string in the input |
- |
The topic that is used during mapping on the user interface of the AI Inference Server. Does not exist on the Python script level. |
timestamp |
timestamp |
ISO 6801 |
sensor_id |
- |
Part of the topic. Does not exist on the Python script level. |
Content-Type |
image["mimeType"] |
Always "image/raw" |
shape[0] |
image["resolutionWidth"] |
|
shape[1] |
image["resolutionHeight"] |
|
shape[2] |
image["channelsPerPixel"] |
Always 3 and means BGR order |
dtype |
dataType |
Always "uint8" |
The next binary part |
image["image"] |
Notes on the multi-part structure
Multiple resolutions, data types and image files are allowed in a single message. The AI Inference Server splits them and the Python script receives them individually.
The AI model should validate the input as best practice. The AI Inference Server converts the ZMQ format into the "Python" format, but cannot and does not check the contents. For example, the fixed content of the mimeType is a contract that is provided by the VCA provider and not by the AI Inference Server.
See alsoGeniCam_SFNC