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 |
|---|---|
|
down the image - height, the scanning direction |
|
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 imageCLAHE (
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.iniand shows you where the file isup 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
|
Verbose |
0 |
how much the driver writes to the console
|
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 threeempty 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 |
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 |
|
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, |
Angles |
<empty> |
dynamic ROI rotation, |
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
.Calib1is calibration 1,.Calib2is 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 |
|---|---|
|
Selects the filter. |
|
Filter argument. For the flat-field filter this is the path to the calibration image. For CLAHE leave empty. |
|
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
gray8-bit greyscale (1 channel)
bgr8-bit BGR colour (3 channels)
rgb8-bit RGB colour (3 channels)
bgra8-bit BGRA colour with alpha (4 channels)
rgba8-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:
92for a sensor with a white-point near 92 counts;255for an 8-bit sensor;4095for 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 calibration0.5- halves output brightness, darkens the image2.0- doubles output brightness, brightens the image
-o<value>Additive offset applied after gain. Type: decimal number. Default:
0.0. Range:-256to255. 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 |
|---|---|---|
|
(file) |
Camera pixel format / Bayer pattern:
|
|
0 |
White-point target; 0 = use calib image max |
|
1.0 |
Brightness gain (must be > 0) |
|
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 noiseLower values, for example
1.0, are more conservative0disables 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 brightnessLarger tiles, for example
16,16, produce smoother, more global resultsSetting either value to
0disables 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
vHSV - CLAHE on the Value channel only. Enhances brightness while preserving colour. (default)
lLAB - CLAHE on the Lightness channel only. Similar to HSV but with better perceptual uniformity.
rRGB - 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. Use4095for 12-bit cameras - most industrial cameras. Use65535for full 16-bit sensors.Three LUT shapes are available:
g<max>,<gamma>- Gamma curveMaps the input range to 8-bit using a power function.
gamma=1.0is 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-curveCreates a classic S-shaped tone curve that compresses extreme highlights and shadows while stretching mid-tones.
contrastcontrols the steepness of the S - default10.0.midpointshifts the centre of the curve - 0 to 1, default0.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 enhancerA reverse S-curve that lifts shadows and compresses highlights at the same time, revealing detail at both ends of the tonal range.
detailcontrols the strength - 0 to 1, default0.6.Examples:
-lr4095,0.6 moderate detail enhancement -lr4095,0.8 strong detail enhancement
Default if
-lis 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 |
|---|---|---|
|
2.0 |
CLAHE clip limit; |
|
8,8 |
Tile grid size H,W; |
|
v |
Colour mode: |
|
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 |
|
|
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
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 |
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
|
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.