User Guide

This guide explains how to set up and run the Scorpion 2DScanner.

To Scorpion the 2DScanner looks like an ordinary camera. You pick it from the camera list, it delivers one image per grab, and every tool in the toolbox works on that image as usual. What makes it a scanner is what happens between the trigger and the finished image:

  • several area scan cameras can feed the same scanner

  • every camera can be cropped and resampled to a calibrated, distortion free image

  • every camera can be shading corrected or contrast enhanced

  • every camera can be delayed in an image FIFO so images taken at different times line up

  • the results are stitched into one image - cameras side by side, scans stacked

A 2DScanner built on 3D calibrated cameras is in effect a 3D scanning device. 3D calibration is also what makes several cameras line up cleanly when they are stitched - with 2D calibration only, seams are hard to avoid.

Each camera is grabbed and processed in its own thread. Doing the resampling inside the driver rather than in the toolbox is faster, keeps the profile simpler and hides the camera geometry from the inspection.

  • the price is slightly higher latency and, in extreme cases, marginally lower frame rate

Note

The 2DScanner runs on both Scorpion (Windows) and SMARTedge (Linux ARM64). The same 2DScanner.ini can be used on both. Windows .dll names for the camera driver, filter and resampler are translated to their Linux .so equivalents when the file is loaded - for example HVGrab_1_0_4_53.dll becomes libhvgrab.so.

Windows supports up to six scanners - Scanner0001 .. Scanner0006. SMARTedge supports one - Scanner0001.

How a scan is built

Every image passes through the same four steps on its way from the camera to the finished scanner image:

  • acquisition - the image is fetched from the camera

  • resampling - the image is cropped and/or resampled into calibrated, real-world coordinates

  • filtering - shading correction or contrast enhancement is applied

  • stitching - the finished part-image is placed in the scanner image

Where each part-image lands

The scanner image is a grid. The column is given by the camera - camera 1 leftmost, camera 2 to the right of it, and so on. The row is given by the scan number - scan 1 on top, scan 2 below it, down to Scans.

So the finished image is:

  • width = the widths of all cameras added together

  • height = the tallest camera image, multiplied by Scans

Note

Cameras of different width and height may be combined. The images are placed side by side and padded down to the height of the tallest camera; the padding is black.

Cameras with different pixel depth cannot be combined. The scanner refuses to open and writes a clear error to the console rather than delivering a corrupted image.

If a scan times out before every camera has delivered, the missing part-images are filled with a fixed pixel value - see ActiveGrabTimeout and MinScans.

x is down, y is across

Throughout 2DScanner.ini the suffix _x is the vertical direction and _y is the horizontal direction. This follows the Scorpion image convention and it is the most common source of confusion when setting up a scanner:

Suffix

Meaning

_x

down the image - height, the scanning direction

_y

across the image - width, the stitching direction

So Size_x is the height of one scan and Size_y its width, Pitch_x is the pixel height and Pitch_y the pixel width. TopLeft_y is what you change to move a camera sideways in the stitched image.

Resampling

Resampling converts the raw camera image into an image with real-world coordinates - millimetres rather than pixels - using a calibration file made in Scorpion. It removes lens distortion and perspective, so the object measures the same wherever it appears in the image. The work is done by SVLResample.dll, which is part of the standard Scorpion installation.

The calibration file is usually produced by a separate Scorpion calibration profile using the same cameras, lenses and mounting as the running system. It defines an origin (0,0); TopLeft and Size are given relative to that origin, in object coordinates.

Set CalibFile to the calibration file, either as an absolute path or relative to the folder holding 2DScanner.ini.

Pixel mode

Leave CalibFile empty - or set it to pixels - and the scanner runs in pixel mode: no calibration, no resampling, the camera image is used as it is.

From version 1.1.0.31 pixel mode is much simpler:

  • the output size is taken straight from the camera image

  • no resampler plugin is loaded

  • Size and TopLeft are ignored

Pixel mode is the right starting point when you just want to stack or stitch raw images, or while the calibration is still being built.

Cropping

If the calibration was made on a part of the raw image rather than on the whole frame, tell the scanner about it with CropOffset and CropSize. These must match the sub image used in the calibration profile, otherwise the resampled image is offset.

Dynamic ROI

Centers and Angles let the resampled area move and rotate from image to image, which is how you follow an object that shifts position between scans. Both are round-robin lists: the scanner steps to the next entry for every image and wraps around at the end. The position in the list is reset by the reset command.

  • Centers overrides TopLeft_x / TopLeft_y

  • Angles overrides Angle

Note

Dynamic ROI adds resampling time on calibrated systems. In pixel mode with angle 0 a moving centre costs nothing extra.

Filtering

Each camera can have its own image filter, applied to every frame before it is stitched. The filter is created once, when the camera opens.

Two filters are available:

  • flat-field (f) - shading correction from a calibration image

  • CLAHE (h) - local contrast enhancement

Each camera section - and each calibration sub-section - can have its own filter settings. Filters work both in calibrated mode and in pixel mode. They are described in detail under Image filters.

Example 19 is a complete five camera system with calibration and shading correction on every camera, worked through step by step.

Installation

The 2DScanner is a virtual camera driver - it produces images, but it does not talk to hardware itself. It grabs from ordinary Scorpion camera drivers and delivers a stitched result.

  • installed by the Scorpion Vision Installer

  • used like any other camera driver

  • configured entirely from 2DScanner.ini - there is no configuration dialog

    • pressing Configure on the scanner camera in Scorpion writes the current settings back to 2DScanner.ini and shows you where the file is

  • up to six scanners in one profile on Windows, one on SMARTedge

  • the camera driver used for the real cameras is named in the configuration file

Note

Open the real cameras before the scanner. A scanner can only attach to a camera port that is already open, so the physical cameras must come before Scanner0001 in Scorpion’s camera list. If a port is not open when the scanner starts, the console reports invalid port or invalid driver and that camera is left out of the scan.

Set the physical cameras inactive and the scanner active in the image setup, unless you deliberately want the raw images as well.

Configuration file

The configuration file 2DScanner.ini lives in the Scorpion profile’s Hardware folder. If it is missing, create it by copying an example from this guide and editing it. The full path of the file actually in use can always be read back:

print GetCamera('Scanner0001').executeCmd('get','inifile')

The file has three kinds of section:

  • [Config] - settings shared by every scanner in the file

  • [Scanner<nnnn>] - one per scanner, settings shared by its cameras

  • [Scanner<nnnn>.Camera<n>] - one per camera in that scanner

Settings cascade downwards. A value in [Config] is the default for every scanner, a value in [Scanner0001] is the default for all of its cameras, and a value in [Scanner0001.Camera2] applies to that camera alone.

Note

Complete, commented examples are in the Examples chapter.

Config section

Settings shared by all scanners in the file.

Key

Default

Description

CameraDriver

<driver.dll>

Scorpion Camera Driver used for the real cameras - the SCD

FilterDLL

SVLImageFilter.dll

Scorpion standard image filter plugin

ResampleDLL

SVLResample.dll

Scorpion standard resampling plugin

ResampleMode

0

resampling quality / speed trade-off

  • 0 - bilinear

  • 1 - nearest neighbour

  • 2 - wide

  • 3 - fast wide

Verbose

0

how much the driver writes to the console

  • 0 - errors and warnings only

  • 1 - and status messages

  • 2 - and scan-complete diagnostics

  • 3 - and per-image timing

Note

CameraDriver must be the same for every camera attached to a 2DScanner.

Example: HVGrab_1_0_4_53.dll - the SDK for that camera must be installed.

Note

FilterDLL, ResampleDLL and ResampleMode may be overridden per scanner and per camera by repeating the key in the [Scanner<nnnn>] or [Scanner<nnnn>.Camera<n>] section. This is rarely needed.

ResampleMode defaults to bilinear, which gives the best image quality. Nearest neighbour is the fastest and is a good choice when the scan rate matters more than the last bit of accuracy.

SVLResample.dll ships with Scorpion - changing ResampleDLL is not advised.

Note

Raise Verbose while commissioning a system and lower it again for production. Level 3 prints one line per image and will itself slow down a fast scanner.

Scanner section

[Scanner<nnnn>] holds the settings shared by all cameras in one scanner. Every value here is the default for that scanner’s cameras and can be overridden in an individual camera section.

Key

Default

Description

Cameras

1

number of cameras stitched side by side

Scans

1

number of scans stacked to form the finished image

ActiveGrabTimeout

0

ms allowed between images before the scan is declared timed out. 0 disables the timeout - the scanner waits indefinitely

ImageDelay

0

ms to wait after a scan completes before handing it to Scorpion. A scan that completes while the delay is running is dropped and counted

MinScans

0

how many scans may be missing and still deliver the image on timeout. See the note below

Overlap

0

number of scans copied from the end of one image to the start of the next

Scale_x

1.0

reserved - written to the file but currently ignored

Scale_y

1.0

reserved - written to the file but currently ignored

TopLeft_x

0

top left corner of the resampled area, in object coordinates relative to the calibration origin (0,0). Ignored in pixel mode

TopLeft_y

0

as TopLeft_x, across the image. Ignored in pixel mode

Size_x

100

height of one scan in object coordinates. Ignored in pixel mode, where the camera image height is used

Size_y

100

width of one scan in object coordinates, shared between Cameras cameras. Ignored in pixel mode, where the camera image width is used

Angle

0

rotation of the resampled area in degrees

Pitch_x

1.0

pixel height in the finished image - object units per pixel

Pitch_y

1.0

pixel width in the finished image - object units per pixel

PassThru

<empty>

comma separated list of scan numbers whose raw camera image is also passed to Scorpion. Empty or ‘-’ switches it off. Windows only - ignored on SMARTedge. Leave empty in runtime systems, see the note below

Active

0

if 1, the stitched image is delivered before the raw pass-through image, otherwise after. Only relevant when PassThru is in use

DiscardLostPacket

0

discard the scan if a camera reports a lost packet

DiscardSeqError

0

discard the scan if a camera FrameNumber sequence gap is detected

DiscardSyncError

0

discard the scan if the cameras FrameNumbers do not match each other

Threaded

1

resample in a separate thread per camera. May be turned off for small images where the thread costs more than the resampling saves

RingSize

32

per-camera capture buffer depth. The default absorbs short bursts so no frame is lost at high frame rates. Raised automatically to at least Fifo + 1

WorkerRtPriority

8

run the capture thread at real-time priority for smooth, jitter-free scanning. Set 0 for normal scheduling. See the note below

Note

Placing several cameras side by side. When cameras are calibrated to a shared origin, set TopLeft_x / TopLeft_y and Size_x / Size_y explicitly in each camera section. The scanner does derive a default position for the next camera from the previous one, but relying on it makes the configuration hard to read and easy to get wrong.

Scale, TopLeft, Size, Pitch, Angle, Threaded, RingSize and WorkerRtPriority may all be overridden per camera by repeating the key in the [Scanner<nnnn>.Camera<n>] section.

Note

Overlap copies the last Overlap scans of one image to the top of the next, so an object that straddles the boundary appears whole in at least one image.

  • useful when individual objects are scanned on a conveyor

  • the value is wrapped into the range 0 .. Scans-1

Note

MinScans only applies when ActiveGrabTimeout > 0. It is the number of scans that may be missing when the timeout strikes:

  • MinScans = 0 - the scan must be complete. Any partial scan is discarded. Use this when the images must be in sync, for example stereo vision

  • MinScans > 0 - up to that many scans may be missing. The image is delivered with the missing part filled black

Timed-out scans are also marked visibly: the unfinished part of the image is filled with a black and white striped pattern so an operator can see at a glance that the image is incomplete.

Note

Scan validation - DiscardLostPacket, DiscardSeqError and DiscardSyncError throw away a scan when a transport or synchronisation problem is detected, rather than passing on a torn or mismatched image:

  • DiscardLostPacket - a camera reported a lost packet (GigE transport layer)

  • DiscardSeqError - a camera FrameNumber sequence has a gap

  • DiscardSyncError - the cameras FrameNumbers are not in step with each other

A discarded scan is reported on the console and counted in the metadata.

Note

High frame rate scanning - RingSize and WorkerRtPriority keep the cameras running at full rate without falling behind (from version 1.1.0.32):

  • RingSize is the per-camera capture buffer depth. The default of 32 absorbs short bursts - about 0.6 s at 50 fps - so no frame is lost while a finished scan is handed to Scorpion. It is raised automatically to at least Fifo + 1

  • WorkerRtPriority runs the capture thread at real-time priority for smooth, jitter-free scanning. Set 0 to use normal scheduling

On Linux/SMARTedge real-time priority requires the real-time privilege. If it is not granted the driver logs a warning and continues at normal priority.

Note

PassThru passes selected raw camera images to Scorpion alongside the stitched image. It is a commissioning aid - it lets you see what a camera actually delivered next to what the scanner made of it.

The value is a comma separated list of scan numbers, in the style of Python indexing:

  • 1,-1 - the first and the last image of each scan

  • 0 - every image

  • 1,4,8 - those three

  • empty or - - pass-through off

Counting starts at 1; negative numbers count back from the end. A number outside the range 1 .. Scans switches pass-through off, which is how setProperty('passthru', n) disables it.

To see the raw images, the physical camera must also be active in the Scorpion image setup. Active on the scanner decides whether the stitched image arrives before or after the raw one.

Note

Pass-through is off unless you ask for it. From version 1.1.0.33 the default is empty, so a scanner with no PassThru key passes no raw images.

Leave it that way in runtime systems - every passed image is an extra image handed to Scorpion, which costs time and lowers the scan rate.

Pass-through is Windows only. On SMARTedge the setting is ignored - the extra copy disturbs scan timing - and the driver says so in the log.

Camera section

[Scanner<nnnn>.Camera<n>] holds the settings for one camera in the scanner. <n> counts from 1.

Key

Default

Description

Port

<n>-1

Scorpion camera port of the physical camera, 0-indexed. This is the position of the camera in Scorpion’s camera list, not a hardware port

CalibFile

<empty>

calibration file for this camera - absolute, or relative to the folder holding 2DScanner.ini. Empty or pixels selects pixel mode. TopLeft and Size must match the calibration

Fifo

0

number of images this camera is delayed before it is stitched. Use it to line up cameras that see the same object at different times. While the FIFO fills, this camera’s part of the image is black. See the note below

ImageFilterType

f

f flat-field / shading correction, h CLAHE contrast enhancement. No filter is created unless ImageFilterArgs or ImageFilterOptions is set

ImageFilterArgs

<empty>

flat-field: path to the shading correction image. CLAHE: leave empty

ImageFilterOptions

<empty>

filter options - see Image filters

CropOffset_x

0

top of the crop rectangle in the raw image, in pixels

CropOffset_y

0

left of the crop rectangle in the raw image, in pixels

CropSize_x

0

crop height in pixels. 0 means no cropping

CropSize_y

0

crop width in pixels. 0 means no cropping

Angle

0

rotation of the resampled area in degrees

Centers

<empty>

dynamic ROI centres, (x0,y0),...,(xn,yn). Stepped round-robin, one entry per image, reset by the reset command. Overrides TopLeft

Angles

<empty>

dynamic ROI rotation, a0,...,an. Stepped round-robin like Centers and reset by the same command. Overrides Angle

In addition, any of TopLeft_x, TopLeft_y, Size_x, Size_y, Pitch_x, Pitch_y, Threaded, RingSize, WorkerRtPriority, ResampleMode, ResampleDLL and FilterDLL may be repeated here to override the scanner-wide value for this camera.

Note

Fifo delays a camera by a whole number of images so that cameras looking at the same object from different positions can be stitched together.

  • it lines images up in time, not in space

  • it needs the cameras to be triggered by the object movement - typically a hardware trigger from an encoder

  • while the FIFO is filling, this camera contributes black pixels

  • the reset command empties the FIFO

Note

Recommended port numbering - Port=0 for camera 1, Port=1 for camera 2, and so on. Two cameras in a scanner may share a port when you want two different resampled areas out of one physical image, but duplicate ports otherwise cause confusion.

Note

Shading correction images and calibration files are resolved relative to the folder holding 2DScanner.ini. Calibration files normally live in the profile’s Calibration/2D folder.

Several calibrations - working at more than one height

A 2D calibration is only valid at the height it was made. When objects arrive at different heights, make one calibration per height and switch between them at run time.

Add extra sections named [Scanner<nnnn>.Camera<n>.Calib<c>], where <c> runs from 1 upwards. Repeat only the keys that differ from the parent camera section - normally CalibFile, and the filter keys if the shading correction also changes.

  • the parent [Scanner<nnnn>.Camera<n>] section is calibration 0

  • .Calib1 is calibration 1, .Calib2 is calibration 2, and so on

Switch with the calib property, which applies to every camera in the scanner at once:

cam = GetCamera('Scanner0001')   # get the scanner
cam.setProperty('calib', 1)      # switch every camera to calibration 1

Note

All cameras in a scanner switch together and must therefore have the same number of calibrations. Switching reloads the resampler and briefly interrupts scanning, so do it between scans, not during one.

Worked examples: Example 13 switches between three calibration heights, Example 15 pairs each height with its own shading correction image.

Image filters

A filter cleans up each camera image before it is stitched. It is configured in the camera section - or in a Calib<n> sub-section, so the filter can follow the calibration height.

Key

Description

ImageFilterType

Selects the filter. f = flat-field correction, h = CLAHE contrast enhancement.

ImageFilterArgs

Filter argument. For the flat-field filter this is the path to the calibration image. For CLAHE leave empty.

ImageFilterOptions

Option string controlling filter behaviour. See each filter section for the available options.

Note

A filter is created as soon as either ImageFilterArgs or ImageFilterOptions has a value. To switch filtering off, clear both keys - or leave ImageFilterType empty.

Omitting ImageFilterType is not enough on its own: it defaults to f, the flat-field filter.

Note

CalibFile in the same section is the geometric lens calibration used by the resampler. It is unrelated to the image filter.

Option tokens in ImageFilterOptions may be written with spaces between them or run together - both forms are equivalent:

ImageFilterOptions = -t92 -fgray -g1.0 -o0
ImageFilterOptions = -t92-fgray-g1.0-o0

Note

The filter options changed in version 1.0.0.27. The old PreFilter key is obsolete and is ignored - replace it with ImageFilterType, ImageFilterArgs and ImageFilterOptions.

Flat-field filter (f)

Purpose

Corrects uneven illumination across the image - vignetting, lens fall-off and sensor non-uniformity. The correction is based on a calibration image captured under uniform illumination with the same lens and lighting as production scanning. Every pixel in the acquired frame is divided by the corresponding pixel in the calibration image, normalised to a target white-point value, and then an optional gain and offset are applied.

The filter also handles Bayer-pattern cameras: if the source image is raw Bayer - single channel, 8- or 16-bit - and the output is colour (3 channels), demosaicing is performed automatically before the flat-field correction.

Calibration image

The calibration image should be a well-exposed flat-field image - typically a uniform white target or a diffuse light source - acquired with the same gain, exposure and gamma settings as production scanning. It is named by ImageFilterArgs.

The image can be any format supported by OpenCV (BMP, PNG, TIFF, …). It is loaded once at startup. If the file is missing, or exists but cannot be read, an error is written to the console and the filter stays inactive.

Options

-f<format>

Colour space to load the calibration image as. This must match the pixel format delivered by the camera:

Value

Camera output format

gray

8-bit greyscale (1 channel)

bgr

8-bit BGR colour (3 channels)

rgb

8-bit RGB colour (3 channels)

bgra

8-bit BGRA colour with alpha (4 channels)

rgba

8-bit RGBA colour with alpha (4 channels)

(omitted)

Load unchanged - format as stored in the file

-t<value>

White-point target value. The calibration image is normalised so that this value corresponds to an output of the original pixel value - no correction applied. Pixels brighter than the target are corrected downward; darker pixels are corrected upward.

  • Default 0 - the driver uses the maximum pixel value found in the calibration image as the target.

  • Typical values: 92 for a sensor with a white-point near 92 counts; 255 for an 8-bit sensor; 4095 for a 12-bit sensor.

-g<value>

Gain multiplier applied to the corrected image. Type: decimal number. Default: 1.0. Must be greater than zero.

  • 1.0 - no change in brightness. Use this when acquiring images for geometric calibration

  • 0.5 - halves output brightness, darkens the image

  • 2.0 - doubles output brightness, brightens the image

-o<value>

Additive offset applied after gain. Type: decimal number. Default: 0.0. Range: -256 to 255. Use it to shift the black level up or down.

Configuration examples

Greyscale camera - live acquisition vs. calibration context

Gain 0.5 during live scanning, 1.0 during calibration acquisition so the scanner receives unattenuated images for geometric calibration:

[Scanner0001.Camera1]
Port                = 0
ImageFilterType     = f
ImageFilterArgs     = ..\Calibration\2D\grayscale\RightTop2D-Calib_Gain17_Gamma750_Expo1200.bmp
ImageFilterOptions  = -t92 -fgray -g0.5 -o0

[Scanner0001.Camera1.Calib1]
ImageFilterType     = f
ImageFilterArgs     = ..\Calibration\2D\grayscale\RightTop2D-Calib_Gain17_Gamma750_Expo1200.bmp
ImageFilterOptions  = -t92 -fgray -g1.0 -o0

Colour BGR camera

No target override - the white-point is taken from the maximum pixel value in the calibration image:

[Scanner0001.Camera2]
Port                = 1
ImageFilterType     = f
ImageFilterArgs     = ..\Calibration\2D\color\calib_bgr.bmp
ImageFilterOptions  = -fbgr -g1.0 -o0

[Scanner0001.Camera2.Calib1]
ImageFilterType     = f
ImageFilterArgs     = ..\Calibration\2D\color\calib_bgr.bmp
ImageFilterOptions  = -fbgr -g1.0 -o0

12-bit Bayer camera to 8-bit BGR output

The camera delivers raw Bayer frames, which are demosaiced to BGR before the flat-field correction is applied. The -f option selects the Bayer pattern of the sensor - gr, rg, gb or bg. The target is set to 4000 counts, typical for a well-exposed 12-bit flat image:

[Scanner0001.Camera3]
Port                = 2
ImageFilterType     = f
ImageFilterArgs     = ..\Calibration\2D\bayer\calib_bgr.bmp
ImageFilterOptions  = -t4000 -fgb -g1.0 -o0

[Scanner0001.Camera3.Calib1]
ImageFilterType     = f
ImageFilterArgs     = ..\Calibration\2D\bayer\calib_bgr.bmp
ImageFilterOptions  = -t4000 -fgb -g1.0 -o0

Quick reference

Option

Default

Effect

-f<format>

(file)

Camera pixel format / Bayer pattern: gray bgr rgb bgra rgba gr rg gb bg

-t<int>

0

White-point target; 0 = use calib image max

-g<double>

1.0

Brightness gain (must be > 0)

-o<double>

0.0

Black-level offset (-256 … 255)

CLAHE filter (h)

Purpose

CLAHE - Contrast Limited Adaptive Histogram Equalisation - improves local contrast in images where the scene contains both bright and dark regions that cannot both be well exposed at the same time. Unlike a global brightness adjustment, CLAHE divides the image into small tiles and equalises the contrast within each tile independently, which prevents over-amplification of noise.

The result is then mapped from the 16-bit working range to 8-bit output through a configurable lookup table (LUT).

CLAHE does not use a calibration image. ImageFilterArgs must be left empty.

Options

-c<value>

CLAHE clip limit. Controls how aggressively local contrast is stretched. Type: decimal number. Default: 2.0.

  • Higher values, for example 4.0, produce stronger contrast enhancement but can amplify noise

  • Lower values, for example 1.0, are more conservative

  • 0 disables CLAHE entirely - only the LUT is applied

-s<height>,<width>

Tile grid size for local histogram equalisation. Both values are integers. Default: 8,8.

  • Smaller tiles, for example 4,4, adapt more locally - better for images with rapid spatial variation in brightness

  • Larger tiles, for example 16,16, produce smoother, more global results

  • Setting either value to 0 disables CLAHE - LUT only

-m<mode>

Colour mode - which colour-space channel CLAHE is applied to. Only relevant for colour (3-channel) input. Ignored for greyscale.

Value

Behaviour

v

HSV - CLAHE on the Value channel only. Enhances brightness while preserving colour. (default)

l

LAB - CLAHE on the Lightness channel only. Similar to HSV but with better perceptual uniformity.

r

RGB - CLAHE applied independently to each colour channel. Stronger effect but may shift colour balance.

-l<lut>

Lookup table mapping the 16-bit CLAHE result to 8-bit output. Format: <type><maxval>,<param>,...

<maxval> is the maximum expected pixel value of the source image. Use 4095 for 12-bit cameras - most industrial cameras. Use 65535 for full 16-bit sensors.

Three LUT shapes are available:

g<max>,<gamma> - Gamma curve

Maps the input range to 8-bit using a power function. gamma=1.0 is a straight linear mapping - no contrast change. Values below 1.0 lift shadows; values above 1.0 compress them.

Examples:

-lg4095,1.0    linear, 12-bit source          (default)
-lg4095,0.7    mild shadow lift
-lg65535,1.0   linear, 16-bit source
s<max>,<contrast>,<midpoint> - Sigmoid S-curve

Creates a classic S-shaped tone curve that compresses extreme highlights and shadows while stretching mid-tones. contrast controls the steepness of the S - default 10.0. midpoint shifts the centre of the curve - 0 to 1, default 0.5.

Examples:

-ls4095,8.0,0.5    moderate S-curve, centred
-ls4095,12.0,0.4   strong S-curve, shifted toward shadows
r<max>,<detail> - Reverse sigmoid, detail enhancer

A reverse S-curve that lifts shadows and compresses highlights at the same time, revealing detail at both ends of the tonal range. detail controls the strength - 0 to 1, default 0.6.

Examples:

-lr4095,0.6    moderate detail enhancement
-lr4095,0.8    strong detail enhancement

Default if -l is omitted: g4095,1.0 - linear gamma, 12-bit.

Configuration examples

12-bit mono camera - local contrast enhancement

Linear LUT with default CLAHE settings:

[Scanner0001.Camera4]
Port                = 3
ImageFilterType     = h
ImageFilterArgs     =
ImageFilterOptions  = -lg4095,1.0 -c2.0 -s8,8

[Scanner0001.Camera4.Calib1]
ImageFilterType     = h
ImageFilterArgs     =
ImageFilterOptions  = -lg4095,1.0 -c2.0 -s8,8

16-bit colour camera - HSV CLAHE with sigmoid LUT

Stronger CLAHE with finer tiles and a sigmoid LUT for better visual contrast in mid-tones:

[Scanner0001.Camera5]
Port                = 4
ImageFilterType     = h
ImageFilterArgs     =
ImageFilterOptions  = -ls4095,8.0,0.5 -c3.0 -s16,16 -mv

[Scanner0001.Camera5.Calib1]
ImageFilterType     = h
ImageFilterArgs     =
ImageFilterOptions  = -ls4095,8.0,0.5 -c3.0 -s16,16 -mv

LUT only - gamma compression without CLAHE

Disable CLAHE with -c0 and apply only a gamma LUT to compress a 12-bit range to 8-bit with a mild shadow lift:

[Scanner0001.Camera6]
Port                = 5
ImageFilterType     = h
ImageFilterArgs     =
ImageFilterOptions  = -lg4095,0.7 -c0

Quick reference

Option

Default

Effect

-c<double>

2.0

CLAHE clip limit; 0 = LUT only

-s<int>,<int>

8,8

Tile grid size H,W; 0 = LUT only

-m<char>

v

Colour mode: v HSV / l LAB / r RGB

-l<lut>

g4095,1.0

LUT shape and range - see above

Running the scanner

Everything the scanner does at run time is reached through the standard Scorpion camera interface - getProperty, setProperty and executeCmd on the camera object, or the equivalent Scorpion commands:

cam = GetCamera('Scanner0001')

Properties

Properties can be read and written on the scanner itself. A property prefixed with camera<1..n>. is passed to that attached camera instead - for example camera1.exposure.

Property

Access

Default

Description

scanCount

R/W

1

number of scans in the finished image - the Scans setting

calib

W

0

select calibration index for every camera at once. Write only - read the active file back with camera<n>.resampler.calibfile

count

R/W

scans collected so far. Reset to 0 when a scan completes. Setting count>1 copies that many scans from the previous image to the top, creating an overlap

overlap

R/W

0

the Overlap setting

minScanCount

R/W

0

the MinScans setting

activeGrabTimeout

R/W

0

the ActiveGrabTimeout setting, in ms

imageDelay

R/W

0

the ImageDelay setting, in ms

discardLostPacket

R/W

0

the DiscardLostPacket setting

discardSeqError

R/W

0

the DiscardSeqError setting

discardSyncError

R/W

0

the DiscardSyncError setting

continuous

R/W

0

1 starts continuous scanning, 0 stops it. See Continuous scanning

continuousDelay

R/W

0

ms to wait after a finished scan before starting the next

continuousPeriod

R/W

0

ms between the start of one scan and the start of the next. 0 selects delay mode

verbose

R/W

0

console output level, 0-3. See the note below

passthru

W

pass-through scan number - the property form takes one number, not a list. A value outside 1 .. scanCount switches pass-through off

pixelsize

R

8

bits per pixel, mirrored from the camera

width

R

width of the finished image in pixels

height

R

height of the finished image in pixels

cameras

R

number of cameras configured for this scanner

scanTime

R

time from the first image of the scan until the scan completed, in ms

trigTime

R

average trigger period of the last scan, in ms

activeGrabTimeoutCount

R

number of timeouts since the scanner was opened

camera<n>.<prop>

any property of attached camera n

Note

Setting a property on the scanner sets it on every attached camera. Any property the cameras understand - exposure, gain, trigger mode - can be set once on the scanner instead of camera by camera. This is both convenient and faster.

Reading works the other way: a camera property must be read from one specific camera.

cam = GetCamera('Scanner0001')       # the scanner
cam.setProperty('exposure', 1000)    # sets exposure on every attached camera
e = cam.getProperty('camera1.exposure', 1)   # reads it back from camera 1

Note

verbose controls how much the driver writes to the Scorpion console:

  • 0 - errors and warnings only

  • 1 - and status messages

  • 2 - and one diagnostic line per finished scan

  • 3 - and one timing line per image

Level 3 is the detailed image-flow view shown in Example 9 below. It costs time on a fast scanner - use it to investigate, not in production.

Resampler and filter properties

Each camera exposes its resampler and filter through the same camera<n>. prefix. Numeric values can be read with getProperty; strings must be read with executeCmd('get', ...).

Property

Access

Default

Description

camera<n>.resampler.active

R

1 when this camera is resampling, 0 in pixel mode

camera<n>.resampler.height

R

height of one scan from this camera, in pixels

camera<n>.resampler.width

R

width of one scan from this camera, in pixels

camera<n>.resampler.calibfile

R

path of the active calibration file - string, use executeCmd

camera<n>.filter.active

R/W

1 when the filter is on. Set 0 to bypass it

camera<n>.filter.targetvalue

R/W

0

white-point of the shading correction. 0 uses calibmax

camera<n>.filter.calibmin

R

lowest pixel value in the calibration image

camera<n>.filter.calibmax

R

highest pixel value in the calibration image

camera<n>.filter.calibstatus

R

ok, missing or an error text - string, use executeCmd

camera<n>.filter.args

R

the configured ImageFilterArgs - string, use executeCmd

camera<n>.filter.options

R

the configured ImageFilterOptions - string, use executeCmd

cam = GetCamera('Scanner0001')
print cam.executeCmd('get','camera1.resampler.calibfile')
print cam.executeCmd('get','camera1.filter.calibstatus')
cam.setProperty('camera1.filter.active', 0)      # bypass the filter on camera 1
_images/consoleoutput.png

Console window with verbose messages

Commands

Commands are sent with executeCmd(<command>, <parameters>).

Command

Parameters

Description

reset

fill=<value>

abandon the current scan and start over. The scan counters are cleared and the camera FIFOs are emptied. With fill the image buffer is set to that pixel value, without it the buffer is left as it is. flush is an alias for reset

trig

fill=<value>

deliver the image now, even if the scan is incomplete. The remaining scans are filled with fill, which defaults to 0 - black. Pass fill=-1 to leave the unfinished part of the buffer untouched

clear

fill=<value>

clear the image buffer of the incomplete scan. The pixel value is fill, or 0 if not given. Counters are not touched

continuous

start delay=<ms> start period=<ms> stop status

start or stop continuous scanning. See Continuous scanning

stat

print a status report for the scanner to the console

set

<prop>=<value>

write any property, including all of those listed above

get

<prop>

read any property as a string. Both get prop and get value=prop are accepted. In addition to the properties above:

  • inifile - full path of the active 2DScanner.ini

  • status or imageStatus - scanner counters as a Python dictionary string

  • meta - metadata for the finished scan, as JSON

  • timing - per-camera timing for the finished scan, as JSON

  • pitch - outgoing pixel size as (x,y)

save

<filename>

save the current configuration as <filename> in the Hardware folder

load

<filename>

load <filename> from the Hardware folder and restart the scanner with it

Note

get inifile must be written exactly as executeCmd('get','inifile') - the value= form is not accepted for this one value.

Note

save and load make it easy to keep several complete scanner setups in one profile and switch between them:

cam = GetCamera('Scanner0001')
cam.executeCmd('save','2DScanner_highres.ini')   # keep the current setup
cam.executeCmd('load','2DScanner_fast.ini')      # switch to another one

load closes and reopens the scanner, so the image size may change.

Continuous scanning

From version 1.1.0.32 the scanner can run by itself, without a trigger from a script or an external signal. It starts the next scan as soon as the previous one is finished, which is the simplest way to get a live scanning image while setting a system up.

Two timing modes are available:

  • delay - wait delay ms after a scan completes, then start the next one. The scan rate follows whatever the cameras and processing can manage

  • period - start a new scan every period ms, measured from the start of the previous scan. Use this when the scan rate must be constant. If a scan overruns the period, the next one starts immediately

Start and stop it with a command:

cam = GetCamera('Scanner0001')
cam.executeCmd('continuous','start delay=100')    # 100 ms between scans
cam.executeCmd('continuous','start period=500')   # a scan every 500 ms
cam.executeCmd('continuous','stop')
print cam.executeCmd('continuous','status')
# {'continuous': 1, 'delay': 100, 'period': 0}

or with properties:

cam.setProperty('continuousDelay', 100)   # preset the timing
cam.setProperty('continuous', 1)          # start
cam.setProperty('continuous', 0)          # stop

Note

period takes precedence over delay. Set continuousPeriod to 0 to use delay mode.

Continuous mode keeps running until it is stopped or the scanner is closed. Stop it before switching calibration or loading another configuration.

Warning

The scanner accepts both spellings, continuous and continous, for this property - and both are handled by the scanner, not passed on to the cameras.

Many camera drivers use continous for their own continuous grab mode. From 1.1.0.32, setting it on the scanner starts continuous scanning instead. To put a physical camera into continuous grab, address it directly:

cam.setProperty('camera1.continous', 1)   # the camera, not the scanner

Metadata and timing

Every finished scan carries metadata describing how it was acquired. It is delivered to Scorpion with the image - available on CameraImage() instances and through Central.CameraCommand - and can also be read back on demand.

cam = GetCamera('Scanner0001')
print cam.executeCmd('get','meta')

Field

Description

span

duration of the scan in ms, first image to last

imagenumber

running count of good scans since the scanner was opened

timeout

running count of timed-out scans

drops

running count of scans dropped because ImageDelay was still running

lostpacket

total lost packets over all cameras and scans in this image

framenumber

the camera FrameNumber of every image in the scan, one list per camera

frameperiod

time between consecutive images in ms, one list per camera

framerate

average frame rate per camera, in frames per second

std

standard deviation of the frame rate per camera - a stability measure

Use framerate and std to confirm the cameras are running at the expected rate and staying in step. A std that grows, or lostpacket counting up, points at a transport or bandwidth problem rather than an optical one.

timing breaks the same scan down by processing step, all values in ms:

print cam.executeCmd('get','timing')

{"scan":1657,"meta":0.4,"callback":2.1,
 "cameras":[{"collect":2.7,"resample":5.1,"filter":0.0,"queue":0.2,"process":8.0}]}

Field

Description

scan

total scan duration

meta

time spent delivering the metadata to Scorpion

callback

time Scorpion spent taking the finished image

collect

fetching the image from the camera

resample

resampling

filter

filtering - shading correction or CLAHE

queue

waiting between collection and the start of processing

process

total processing time for that camera

A large queue time means the processing cannot keep up with the cameras - lower the frame rate, turn off filtering, or switch ResampleMode to nearest neighbour. A large callback time means Scorpion itself is the bottleneck.

Examples - properties

Example 1 - set properties for all cameras at once

cam = GetCamera('Scanner0001')     # the scanner
cam.setProperty('exposure', 1000)  # exposure on every attached camera
cam.setProperty('gain', 4)         # gain on every attached camera

Example 2 - read a property from one camera

exposure = cam.getProperty('camera1.exposure', 1)   # via the scanner
exposure = GetCamera('0').getProperty('exposure')   # directly, same result

Example 3 - change the scan setup at run time

cam = GetCamera('Scanner0001')
cam.setProperty('scanCount', 6)          # 6 scans per image
cam.setProperty('activeGrabTimeout', 500)  # give up after 500 ms

Example 4 - switch calibration height

Use this when the configuration holds several calibrations, typically one per working height. The switch applies to every camera in the scanner.

cam = GetCamera('Scanner0001')   # the scanner
cam.setProperty('calib', 1)      # switch to calibration 1

Examples - scripts

Example 5 - scanner status for several scanners

def imagestatus():
  cameras = ('Scanner0001','Scanner0002')

  for cam in cameras:
    print cam, GetCamera(cam).executeCmd('get','imageStatus')

# output

[10:41:13:950] Scanner0001 {'imageCount': 2061, 'activeGrabTimeoutCount': 0,'activeGrabTime': 1577, 'trigTime': 65, 'camera0': {'imageCount': 49464, 'activeGrabTimeoutCount': 0}}
[10:41:13:950] Scanner0002 {'imageCount': 2061, 'activeGrabTimeoutCount': 0,'activeGrabTime': 1577, 'trigTime': 65, 'camera0': {'imageCount': 49464, 'activeGrabTimeoutCount': 0}}

Example 6 - pick the status apart

The status is returned as a string, so eval turns it into a dictionary. Note that the per-camera keys are camera0, camera1, … - 0-indexed - while the property prefix camera<n>. counts from 1.

def imagestatus(): # several cameras on one scanner
  print 50*'-'
  status = eval(GetCamera('Scanner0001').executeCmd('get','imageStatus'))
  print 'imcnt:',status['imageCount'], 'aGrabTimeoutCnt:',status['activeGrabTimeoutCount'],
  print 'activeGrabTime:',status['activeGrabTime'], 'trigTime:',status['trigTime']
  print 'cam1:',status['camera0']
  print 'cam2:',status['camera1']
  print 'cam3:',status['camera2']

# output

imcnt: 1855 aGrabTimeoutCnt: 0 activeGrabTime: 1891 trigTime: 210
cam1: {'activeGrabTimeoutCount': 2, 'imageCount': 16707}
cam2: {'activeGrabTimeoutCount': 2, 'imageCount': 16707}
cam3: {'activeGrabTimeoutCount': 2, 'imageCount': 16707}

Example 7 - print a status report

GetCamera('Scanner0001').executeCmd('stat')   # writes to the console

# console output

---------------------------------------------------
Scanner0001: last scan 1657 ms, avg trig period 184 ms
 Image count 690 timeout count 0 drop count 0
 Cam[1] image count 0 total 6218 timeouts 1
 Cam[2] image count 0 total 6218 timeouts 1
 Cam[3] image count 0 total 6218 timeouts 1

Example 8 - start a scan from scratch

Clears the counters, empties the camera FIFOs and blanks the image, so the next scan starts from a known state.

cam = GetCamera('Scanner0001')
cam.executeCmd('reset','fill=0')

Example 9 - watch the image flow

Verbose level 3 prints one line per image with a breakdown of where the time goes. It is the quickest way to see which step is holding a scanner back.

def verbose():
  cam = GetCamera('Scanner0001')
  if cam.getProperty('verbose') < 3:
    cam.setProperty('verbose',3)
  else:
    cam.setProperty('verbose',0)

# sample output

[09:36:55:311] Scanner0001.0 [1/8] 2.7 | 5.1 | 0.0 | 0.2 | 8.0 ms
[09:36:55:311] Scanner0001.1 [1/8] 0.3 | 5.2 | 0.0 | 0.2 | 5.7 ms
[09:36:55:312] Scanner0001.0 [2/8] 0.2 | 4.6 | 0.0 | 0.6 | 5.4 ms
[09:36:55:312] Scanner0001.1 [2/8] 0.2 | 5.1 | 0.0 | 0.2 | 5.5 ms
                           *   *    *     *     *     *     *
                           1   2    3     4     5     6     7

 * 1 - source, scanner name and camera port
 * 2 - progress, scan number of scanCount
 * 3 - collect time - fetching the image from the camera
 * 4 - resample time
 * 5 - filter time
 * 6 - queue time - waiting between collection and start of processing
 * 7 - total processing time

At verbose=2 the scanner prints one line per finished scan instead, with the per-camera frame rate and its standard deviation - enough to confirm the cameras are in step without the per-image detail.