Scanner Integration

The SDK integrates fully with the Hyperscan system, making it possible to automate scanning operations.

Warning

The Buteo platform is also supported, although it requires a few wiring changes on the machine itself and some configuration on the camera. Please contact us at support@qtec.com for instructions on how to set it up.

Note

The scanner API is currently only available in Python.

Creating a scanner object

The base type is hs.control.StageController. This base type handles the conveyor, the lights and the camera. It does this by utilizing hs.control.Camera and an API client for the scanner. The idea of this class is to abstract away the complexities of controlling the scanner and streaming, and instead provide a simple interface for controlling both devices in a synchronized manner.

The scanner and the camera share one address. The scanner type is detected from the service running there, or can be given explicitly:

from qtec_hv_sdk import control

stage = control.StageController("<camera_ip>", scanner="auto")
Scanner types

scanner can be "auto", "labscan" for Hyperscan, or "buteo". With "auto", the type is read from the scanner service on port 5001 of the address. If the scanner or the camera cannot be reached, the constructor raises ValueError.

Without the hardware, hs.control.SimulatedStageController replays an image through a simulated camera. It supports the same settings, and accepts motion and light commands without doing anything:

import qtec_hv_sdk as hs
from qtec_hv_sdk import control

stage = control.SimulatedStageController(hs.open("<input-path>"))

Configuring the scan

Let us look at how to configure a scan and read back the resulting frame rate:

# Set conveyor belt velocity in mm/s
stage.velocity = 20.0

# Set conveyor move distance in mm.
stage.distance = 100.0

# Set oversampling (lines per pixel field of view)
stage.oversampling = 4.0

print(stage.velocity, stage.get_fps())

Note

Framerate and velocity cannot be set simultaneously. Setting one automatically calculates the other, and the last value set will take precedence. The framerate is set with set_fps().

Important

The distance and either the framerate or the velocity must be set before scanning.

The framerate, velocity and distance can also be given to the constructor. If both fps and velocity are given, fps is used:

stage = control.StageController("<camera_ip>", fps=30.0, distance=150.0)
Allowed values
  • The velocity must be between 1 and 100 mm/s.

  • The framerate must be within the range supported by the camera.

  • The distance must be at least pixel_fov, the length covered by a single line.

A value outside these limits raises an error and leaves the scanner object unchanged.

The conversion between velocity and framerate depends on the optics. Set them to match your setup:

stage.object_distance = 400.0  # mm
print(stage.focal_length, stage.pixel_pitch, stage.slit_width)
How the framerate is calculated

Each line covers pixel_fov mm of the conveyor:

\[\mathrm{pixel\_fov} = \frac{\mathrm{object\_distance}}{\mathrm{focal\_length}} \cdot \mathrm{pixel\_pitch}\]

The framerate and velocity are converted using the oversampling factor, which defaults to 1:

\[\mathrm{fps} = \frac{\mathrm{velocity}}{\mathrm{pixel\_fov}} \cdot \mathrm{oversampling}\]

The scan has \(\lceil \mathrm{distance} / \mathrm{velocity} \cdot \mathrm{fps} \rceil\) lines.

The default optics are a focal length of 12 mm, an object distance of 258 mm, a pixel pitch of 5 µm and a slit width of 20 µm. If they do not match your setup, the scanned objects will appear stretched or compressed along the conveyor.

Scanning

The scan is an image, so it is captured the same way as any other pipeline:

import qtec_hv_sdk as hs

# Convert to Image object
image = stage.to_hs_image()

# Start streaming and scanning
hs.write(image, "<output-path>.hdr")

As hs.write() gets executed the whole process starts. Light switches on, the conveyor belt starts moving and images are captured and written to file. Once the streaming process is complete, the lights switch off and the conveyor returns to its initial position.

Caution

The conveyor starts moving as soon as the image is consumed. Keep the conveyor path clear, and use stop() to halt it.

Scan sequence details
  1. The camera framerate is set, and the lights are switched on.

  2. After 1.5 s for the lights to warm up, the conveyor starts moving and the camera captures lines.

  3. Once the distance has been covered, the conveyor returns to its initial position at 100 mm/s.

The lights are switched off by a timer covering the warm-up and the travel time.

To process the lines as they arrive instead, stream the image:

with stage.to_hs_image().stream() as stream:
    for meta, line in stream:
        print(meta.seq, line.shape)

Caution

Using the hs.control.StageController.to_hs_image() method, it is important to state that it is a snapshot of the current state of the scanner object, meaning that if a change is made to any of the parameters after calling this method, it will not affect the image object created prior. The camera is shared, so camera settings such as the exposure apply to all images.

Lights, motion and camera

The lights and the conveyor can also be controlled directly, and the camera is available for settings such as the exposure, bands and cropping:

# Switch the lights on, and off again after 5 s
stage.set_lights_with_timer(5000)

# Move backwards at 10 mm/s, then stop
stage.move_direction(-10.0)
stage.stop()

stage.hs_camera.set_exposure(3000)

Switch the lights off with toggle_lights(), passing False. The light timer can be at most 60 s. See Camera Integration for the camera settings.