Examples - 2DScanner setup ------------------------------------ This chapter contains complete ``2DScanner.ini`` files for the setups that come up most often, with notes on why each setting is what it is. Copy the example closest to your system into the profile's *Hardware* folder, then change the camera driver name, the ports and the calibration file names. .. note:: Start simple. Get one camera scanning in pixel mode first, confirm the image looks right, then add calibration, then add the second camera. Debugging a four camera calibrated stitch from a blank page is much harder than growing into it one step at a time. Example 10 - the simplest scanner: stack images from one camera ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ One camera, no calibration, no filter. Every ten grabs are stacked into one tall image. This is the right place to start on any new system. How it works: * ``CalibFile`` is empty, so the scanner runs in **pixel mode** - the camera image is used as it is and *Size* / *TopLeft* are ignored * ``Scans=10`` stacks ten camera images on top of each other. If the camera delivers 1024 x 2048, the scanner delivers 10240 x 2048 * ``ActiveGrabTimeout=0`` means the scanner waits indefinitely for all ten images - nothing is delivered until the scan is complete * ``Verbose=1`` prints status messages while you get it working :: [Config] CameraDriver = HVGrab_1_0_4_53.dll FilterDLL = SVLImageFilter.dll ResampleDLL = SVLResample.dll ResampleMode = 1 Verbose = 1 [Scanner0001] Cameras = 1 Scans = 10 [Scanner0001.Camera1] Port = 0 CalibFile = Trigger it from a script, one grab per scan line:: cam = GetCamera('Scanner0001') cam.executeCmd('reset','fill=0') # start from a clean image for i in range(10): cam.grab() # the image arrives after the 10th grab Example 11 - two cameras side by side, pixel mode ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Two cameras widen the field of view. In pixel mode the images are simply placed next to each other - camera 1 on the left, camera 2 on the right - and the result is as wide as both images together. How it works: * ``Cameras=2`` and one ``[Scanner0001.Camera]`` section per camera * ``Port`` picks which physical camera feeds which column. Port 0 is the first camera in Scorpion's camera list * ``Scans=1`` means one row - a single wide image per grab, not a scan * ``ActiveGrabTimeout=300`` with ``MinScans=0`` requires **both** cameras to deliver within 300 ms of each other. If only one arrives, the pair is discarded - see Example 14 for the reasoning * pixel mode tolerates cameras of different size; the shorter image is padded black down to the height of the taller :: [Config] CameraDriver = HVGrab_1_0_4_53.dll FilterDLL = SVLImageFilter.dll ResampleDLL = SVLResample.dll ResampleMode = 1 Verbose = 1 [Scanner0001] Cameras = 2 Scans = 1 ActiveGrabTimeout = 300 MinScans = 0 [Scanner0001.Camera1] Port = 0 CalibFile = [Scanner0001.Camera2] Port = 1 CalibFile = Example 12 - Scorpion 3D Venom, splitting a mirror image ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A Venom camera sees the scene through two mirrors, so one physical image holds two views. This configuration turns each view into its own resampled camera image by running **two scanners on the same physical camera port**. How it works: * both scanners use ``Port=0`` - the same raw image feeds both * each scanner has its own calibration file, and each calibration puts its origin (0,0) in the centre of its own mirror * ``TopLeft_x=-12``, ``TopLeft_y=-10``, ``Size_x=24``, ``Size_y=20`` describe a 24 x 20 mm area centred on that origin - remember *_x* is down, *_y* is across * ``Pitch_x`` / ``Pitch_y`` of 0.02 mm gives 24/0.02 = 1200 rows by 20/0.02 = 1000 columns out of each scanner * in the Scorpion image setup the **physical camera is active** and the two scanner images are inactive, because the raw image is the one being grabbed .. image:: _images/venomsetup.png :: [Config] CameraDriver = PylonAreaCamera_pylon5_1_5_2_40.dll FilterDLL = ScorpionOpenCV.dll ResampleDLL = SVLResample.dll ResampleMode = 1 Verbose = 0 [Scanner0001] Cameras = 1 Scans = 1 TopLeft_x = -12 TopLeft_y = -10 Size_x = 24 Size_y = 20 Pitch_x = 0.02 Pitch_y = 0.02 Threaded = 1 [Scanner0001.Camera1] Port = 0 CalibFile = ..\Calibration\2D\CalibrationCam1.ini [Scanner0002] Cameras = 1 Scans = 1 TopLeft_x = -12 TopLeft_y = -10 Size_x = 24 Size_y = 20 Pitch_x = 0.02 Pitch_y = 0.02 [Scanner0002.Camera1] Port = 0 CalibFile = ..\Calibration\2D\CalibrationCam2.ini .. note:: Two cameras **within one scanner** may also share a port. Use two scanners when the two views should be separate Scorpion images, and one scanner with two cameras when they should be stitched into a single wider image. Example 13 - two cameras scanning a conveyor ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Two cameras over a conveyor, each building a long image from 50 narrow strips. This is the classic linescan replacement. How it works: * ``Scans=50`` - fifty triggers make one image. With ``Size_x=100`` and ``Pitch_x=0.5`` each strip is 200 rows tall, so the finished image is 10000 rows tall * ``Size_y=329`` at ``Pitch_y=0.5`` gives 658 columns across * each camera has **three calibration files** for three working heights - the parent section is calibration 0, ``.Calib1`` and ``.Calib2`` are 25 mm and 50 mm. Switch with ``setProperty('calib', n)`` * ``Threaded=0`` turns off threaded resampling. For strips this narrow the thread costs more than it saves - worth testing both ways on your system * in the Scorpion image setup the **scanner images are active** and the physical camera images inactive .. image:: _images/fishscannersetup.png :: [Config] CameraDriver = PylonAreaCamera_pylon5_1_5_2_40.dll FilterDLL = ResampleDLL = SVLResample.dll ResampleMode = 1 Verbose = 0 [Scanner0001] Cameras = 1 Scans = 50 Overlap = 0 ActiveGrabTimeout = 0 MinScans = 0 TopLeft_x = 100 TopLeft_y = 0 Size_x = 100 Size_y = 329 Pitch_x = 0.5 Pitch_y = 0.5 Threaded = 0 [Scanner0001.Camera1] Port = 0 CalibFile = ..\Calibration\2D\ExtRefCam1_0mm.ini [Scanner0001.Camera1.Calib1] CalibFile = ..\Calibration\2D\ExtRefCam1_25mm.ini [Scanner0001.Camera1.Calib2] CalibFile = ..\Calibration\2D\ExtRefCam1_50mm.ini [Scanner0002] Cameras = 1 Scans = 50 TopLeft_x = 100 TopLeft_y = 0 Size_x = 100 Size_y = 329 Pitch_x = 0.5 Pitch_y = 0.5 Threaded = 0 [Scanner0002.Camera1] Port = 1 CalibFile = ..\Calibration\2D\ExtRefCam2_0mm.ini [Scanner0002.Camera1.Calib1] CalibFile = ..\Calibration\2D\ExtRefCam2_25mm.ini [Scanner0002.Camera1.Calib2] CalibFile = ..\Calibration\2D\ExtRefCam2_50mm.ini Switching working height at run time:: for scanner in ('Scanner0001','Scanner0002'): GetCamera(scanner).setProperty('calib', 1) # 25 mm .. note:: **Overlap** is worth adding when individual objects are scanned on a moving conveyor. ``Overlap=5`` copies the last five strips of each image to the top of the next one, so an object crossing the image boundary appears whole in at least one of them. Example 14 - stereo vision, two images captured together ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Here the 2DScanner is not used to build a long image at all - it is used to guarantee that two stereo images belong to the same moment. How it works: * ``Scans=1`` - one row, so the finished image is just the two views side by side * ``ActiveGrabTimeout=300`` - the second image must arrive within 300 ms of the first, in either order * ``MinScans=0`` - if it does not, the pair is discarded silently. The inspection never sees a mismatched stereo pair * each camera is calibrated with its origin at the image centre and a 440 x 600 area, giving a 440 x 1200 pixel result at ``Pitch=1`` :: [Config] CameraDriver = HVGrab_1_0_4_53.dll FilterDLL = SVLImageFilter.dll ResampleDLL = SVLResample.dll ResampleMode = 1 Verbose = 0 [Scanner0001] Cameras = 2 Scans = 1 Overlap = 0 ActiveGrabTimeout = 300 MinScans = 0 Pitch_x = 1 Pitch_y = 1 Threaded = 1 [Scanner0001.Camera1] Port = 0 TopLeft_x = -220 TopLeft_y = -300 Size_x = 440 Size_y = 600 CalibFile = ..\Calibration\2D\CalibrationCam1.txt [Scanner0001.Camera2] Port = 1 TopLeft_x = -220 TopLeft_y = -300 Size_x = 440 Size_y = 600 CalibFile = ..\Calibration\2D\CalibrationCam2.txt .. note:: **Why MinScans matters here.** When a network delay holds the second image back, the timeout fires on the first image alone. The delayed image then arrives on its own and causes a second, false timeout while it waits for a partner that will never come. With ``MinScans=0`` both are discarded and the inspection simply misses that cycle - which is what you want for stereo. With ``MinScans=1`` the scanner delivers **two** images instead, each with the missing half filled black. Use it only if your inspection can recognise and reject a half-black image. Example 15 - shading correction at two working heights ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ One camera with flat-field shading correction, configured for two heights. Because the illumination profile changes with height, each calibration gets its own correction image. How it works: * ``ImageFilterType = f`` selects the flat-field filter * ``ImageFilterArgs`` is the shading correction image, captured on a uniform white target with the same lens, lighting, gain and exposure as production * ``ImageFilterOptions = -t92`` sets the white-point target to 92 counts. Without ``-t`` the brightest pixel in the correction image is used instead, which makes the result depend on a single pixel * the parent camera section is calibration 0 and ``.Calib1`` is calibration 1. Both repeat the filter keys, because filter settings do not inherit into a ``Calib`` sub-section * ``CalibFile`` is empty here - shading correction works fine in pixel mode :: [Config] CameraDriver = PylonAreaCamera2_pylon5_1_5_3_52.dll FilterDLL = SVLImageFilter.dll ResampleDLL = SVLResample.dll ResampleMode = 1 Verbose = 1 [Scanner0001] Cameras = 1 Scans = 1 Overlap = 0 ActiveGrabTimeout = 0 MinScans = 0 Threaded = 1 [Scanner0001.Camera1] Port = 0 ImageFilterType = f ImageFilterArgs = ..\Calibration\2D\color\Calib_0mm_Gain17_Expo1200.bmp ImageFilterOptions = -t92 CalibFile = [Scanner0001.Camera1.Calib1] ImageFilterType = f ImageFilterArgs = ..\Calibration\2D\color\Calib_50mm_Gain17_Expo1200.bmp ImageFilterOptions = -t92 CalibFile = Switching between the two at run time:: cam = GetCamera('Scanner0001') cam.setProperty('calib', 0) # 0 mm correction image cam.setProperty('calib', 1) # 50 mm correction image Checking that the correction image was actually loaded:: print cam.executeCmd('get','camera1.filter.calibstatus') # ok / missing / error text print cam.executeCmd('get','camera1.filter.calibmax') # brightest pixel found .. note:: ``calib`` switches **every** camera in the scanner at the same time, so all cameras must have the same number of ``Calib`` sections. Example 16 - two resampled areas from one camera ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Two cameras in a scanner may share a physical port. Each gets its own calibration and its own area of the raw image, and the two resampled areas are stitched side by side into one image. This is a neat way to cover a wide object with fewer cameras when the optics allow it. How it works: * cameras 1 and 2 both use ``Port=0``, cameras 3 and 4 both use ``Port=1`` - two physical cameras produce four columns * each of the four has its **own calibration file**, so its ``TopLeft_y`` and ``Size_y`` are relative to **that calibration's** origin. They are all negative here simply because each calibration puts its origin near the middle of the area it covers - they are not positions on a shared axis * ``TopLeft_x``, ``Size_x`` and both ``Pitch`` values come from the scanner section, since all four areas share the same vertical extent and resolution * with ``Pitch_y=0.6``, camera 1 contributes 860/0.6 = 1433 columns and camera 2 contributes 800/0.6 = 1333; the finished image is as wide as all four together * ``Scans=6`` stacks six of these four-wide rows into the finished image :: [Config] CameraDriver = HVGrab_1_0_4_53.dll FilterDLL = SVLImageFilter.dll ResampleDLL = SVLResample.dll ResampleMode = 1 Verbose = 1 [Scanner0001] Cameras = 4 Scans = 6 TopLeft_x = -18 Size_x = 36 Pitch_x = 0.6 Pitch_y = 0.6 [Scanner0001.Camera1] Port = 0 TopLeft_y = -460 Size_y = 860 CalibFile = ..\Calibration\2D\CenterCam1-1.txt [Scanner0001.Camera2] Port = 0 TopLeft_y = -400 Size_y = 800 CalibFile = ..\Calibration\2D\CenterCam1-2.txt [Scanner0001.Camera3] Port = 1 TopLeft_y = -400 Size_y = 800 CalibFile = ..\Calibration\2D\CenterCam2-1.txt [Scanner0001.Camera4] Port = 1 TopLeft_y = -400 Size_y = 860 CalibFile = ..\Calibration\2D\CenterCam2-2.txt .. note:: Always set ``TopLeft_y`` and ``Size_y`` explicitly in every camera section when several calibrated cameras are stitched. The scanner does derive a default position for the next camera, but making it explicit is the only way to keep the layout obvious and predictable. Which column a camera ends up in is decided by the **order of the camera sections**, not by the coordinates - ``Camera1`` is always leftmost. Example 17 - high frame rate scanning ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Settings for two cameras running at around 50 fps, where the cameras must not be allowed to fall behind while a finished scan is handed to Scorpion. Requires version **1.1.0.32** or later. How it works: * ``RingSize=64`` doubles the capture buffer. The default of 32 covers roughly 0.6 s at 50 fps; raise it if the log reports frame loss during bursts * ``WorkerRtPriority=8`` runs the capture threads at real-time priority so they are not held up by other work on the machine * ``Threaded=1`` keeps resampling off the capture thread * ``PassThru`` is left empty - no raw images are passed alongside the stitched one, so nothing competes with the cameras for time * ``ResampleMode=1`` - nearest neighbour is the fastest resampler. Use 0 for bilinear if image quality matters more than rate * ``Verbose=2`` gives one diagnostic line per finished scan with the per-camera frame rate and its stability, without the per-image cost of level 3 :: [Config] CameraDriver = HVGrab_1_0_4_53.dll FilterDLL = SVLImageFilter.dll ResampleDLL = SVLResample.dll ResampleMode = 1 Verbose = 2 [Scanner0001] Cameras = 2 Scans = 100 Threaded = 1 RingSize = 64 WorkerRtPriority = 8 PassThru = [Scanner0001.Camera1] Port = 0 CalibFile = [Scanner0001.Camera2] Port = 1 CalibFile = Confirming the cameras keep up:: cam = GetCamera('Scanner0001') print cam.executeCmd('get','meta') # framerate and std per camera print cam.executeCmd('get','timing') # collect | resample | filter | queue Read the numbers like this: * **framerate** should match the camera setting; **std** should stay small. A rising *std* means the cameras are not being serviced evenly * a large **queue** time means processing cannot keep up - lower the rate, switch off filtering, or use nearest neighbour resampling * **lostpacket** counting up points at network bandwidth, not at the scanner .. note:: On SMARTedge, real-time priority needs the real-time privilege. Without it the driver logs a warning and carries on at normal priority - scanning still works, but timing is less even. Example 18 - continuous scanning ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From version **1.1.0.32** the scanner can drive itself, with no trigger script and no external signal. It is the fastest way to get a live image while aiming cameras and setting exposure. Two timing modes: * **delay** - wait this many ms after each finished scan, then start the next. The rate follows whatever the system can manage * **period** - start a scan every this many ms, measured from the previous scan start. Use it when the rate must be constant :: 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} The same thing with properties, which is convenient from a DataInput page:: cam.setProperty('continuousDelay', 100) # preset the timing cam.setProperty('continuous', 1) # start cam.setProperty('continuous', 0) # stop .. note:: *period* wins over *delay* - set ``continuousPeriod`` to 0 to use delay mode. Continuous mode keeps going until it is stopped or the scanner is closed. Remember to stop it before switching calibration or reloading a configuration. Example 19 - a real five camera SMARTedge scanner ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This is the working configuration of a **Pepsico arc detection scanner** (SP0177-08, hardware SN8235). Five colour cameras look down at a conveyor and are stitched into one image just over a metre wide. It is a good example to study because everything is calibrated, everything is shading corrected, and every number in the file has a reason. The system at a glance: * 5 x HikRobot **MV-CU013-A0GC** colour GigE cameras, 1280 x 1024 sensor, cropped to a 1280 x 500 band * each camera 3D calibrated by a separate Scorpion alignment profile * each camera flat-field corrected with its own shading image * running on **SMARTedge** (Linux ARM64), so the plugins are ``.so`` files * result: one **5250 x 450** pixel image covering **1050 x 90 mm** at 0.2 mm per pixel :: [Config] #CameraDriver=libsamplegrab.so CameraDriver=libhvgrab.so FilterDLL=libsvlimagefilter.so ResampleDLL=libsvlresample.so ResampleMode=1 Verbose=3 [Scanner0001] Cameras=5 Scans=1 Overlap=0 ActiveGrabTimeout=0 ImageDelay=0 MinScans=0 Scale_x=1 Scale_y=1 TopLeft_x=0 TopLeft_y=0 Size_x=90 Size_y=1050 Pitch_x=0.2 Pitch_y=0.2 PassThru= Active=0 DiscardLostPacket=0 DiscardSeqError=0 DiscardSyncError=0 Threaded=1 WorkerRtPriority=8 RingSize=32 [Scanner0001.Camera1] Port=0 TopLeft_x=-45 TopLeft_y=-105 CalibFile=../calibration/2D/SP0177-07-SN8235-Pepsico Arc Detection Scanner Alignment_Cam1_20260429_085449_Bin1x1_Offs245x0_ROI500x1280_UpLeft45x105.txt ImageFilterType=f ImageFilterArgs=../calibration/2D/SP0177-07-SN8235-Pepsico Arc Detection Scanner Alignment_shade_Cam1_ScanCam1_20260428_171658_size450x1050.bmp ImageFilterOptions=-t192-g1.0-o0-fbgr [Scanner0001.Camera2] Port=1 TopLeft_x=-45 TopLeft_y=-105 CalibFile=../calibration/2D/SP0177-07-SN8235-Pepsico Arc Detection Scanner Alignment_Cam2_20260430_130832_Bin1x1_Offs245x0_ROI500x1280_UpLeft45x105.txt ImageFilterType=f ImageFilterArgs=../calibration/2D/SP0177-07-SN8235-Pepsico Arc Detection Scanner Alignment_shade_Cam2_ScanCam2_20260428_171711_size450x1050.bmp ImageFilterOptions=-t192-g1.0-o0-fbgr [Scanner0001.Camera3] Port=2 TopLeft_x=-45 TopLeft_y=-105 CalibFile=../calibration/2D/SP0177-07-SN8235-Pepsico Arc Detection Scanner Alignment_Cam3_20260429_090000_Bin1x1_Offs245x0_ROI500x1280_UpLeft45x105.txt ImageFilterType=f ImageFilterArgs=../calibration/2D/SP0177-07-SN8235-Pepsico Arc Detection Scanner Alignment_shade_Cam3_ScanCam3_20260430_122116_size450x1050.bmp ImageFilterOptions=-t192-g1.0-o0-fbgr [Scanner0001.Camera4] Port=3 TopLeft_x=-45 TopLeft_y=-105 CalibFile=../calibration/2D/SP0177-07-SN8235-Pepsico Arc Detection Scanner Alignment_Cam4_20260429_090130_Bin1x1_Offs245x0_ROI500x1280_UpLeft45x105.txt ImageFilterType=f ImageFilterArgs=../calibration/2D/SP0177-07-SN8235-Pepsico Arc Detection Scanner Alignment_shade_Cam4_ScanCam4_20260428_171727_size450x1050.bmp ImageFilterOptions=-t192-g1.0-o0-fbgr [Scanner0001.Camera5] Port=4 TopLeft_x=-45 TopLeft_y=-105 CalibFile=../calibration/2D/SP0177-07-SN8235-Pepsico Arc Detection Scanner Alignment_Cam5_20260429_090315_Bin1x1_Offs245x0_ROI500x1280_UpLeft45x105.txt ImageFilterType=f ImageFilterArgs=../calibration/2D/SP0177-07-SN8235-Pepsico Arc Detection Scanner Alignment_shade_Cam5_ScanCam5_20260428_171735_size450x1050.bmp ImageFilterOptions=-t192-g1.0-o0-fbgr Working out the geometry """""""""""""""""""""""""" The scanner section describes the **whole** stitched area in millimetres, and the driver divides it between the cameras. Remember that *_x* is down the image and *_y* is across it. ============================ ======================== ================================================== Step Value Where it comes from ============================ ======================== ================================================== Total area 90 x 1050 mm ``Size_x`` x ``Size_y`` Cameras 5 ``Cameras`` Area per camera 90 x 210 mm ``Size_y`` / ``Cameras`` = 1050/5 Resolution 0.2 mm per pixel ``Pitch_x``, ``Pitch_y`` Output per camera 450 x 1050 pixels 90/0.2 rows by 210/0.2 columns Stitched image 5250 x 450 pixels 5 cameras across, ``Scans`` = 1 row down ============================ ======================== ================================================== .. warning:: Do not confuse the two 1050s. ``Size_y=1050`` is **millimetres across the whole scanner**; the 1050 in the output is **pixels from one camera**. They happen to be equal here because 1050 mm / 5 cameras / 0.2 mm = 1050 pixels. Change the pitch and they part company immediately. Each camera section sets ``TopLeft_x=-45`` and ``TopLeft_y=-105`` - half of 90 and half of 210. That places the resampled area exactly **centred on that camera's own calibration origin**: * centre x = -45 + 90/2 = 0 * centre y = -105 + 210/2 = 0 All five cameras use the same numbers, which surprises people the first time they see it. It works because each camera has its **own** calibration file with its **own** origin - the coordinates are relative to that camera, not to a shared axis running across the machine. .. note:: **Position in the stitched image comes from the order of the sections, not from the coordinates.** ``Camera1`` is leftmost, ``Camera5`` rightmost, whatever their ``TopLeft_y`` says. To reorder the strips, change the ``Port`` numbers. Note also that the camera sections do **not** set ``Size_x`` or ``Size_y`` - they inherit 90 and the automatic 1050/5 split from the scanner section. Only ``TopLeft`` is set per camera. This keeps the file short and means the whole field of view can be re-scaled from two numbers. The calibration files """"""""""""""""""""""" Each camera has its own calibration, produced by a **separate Scorpion alignment profile** - ``SP0177-07-SN8235-Pepsico Arc Detection Scanner Alignment`` - using the same cameras, lenses and mounting as the running system. That profile is kept alongside the production profile and re-run whenever the mechanics are disturbed. Opening one of the files shows what it holds:: [system] Profile=SP0177-07-SN8235-Pepsico Arc Detection Scanner Alignment Tool=Align_Cam1.3DResampleRef [calib] model=3 order=9 unit=mm polynomial=... revpolynomial=... * ``Tool=Align_Cam1.3DResampleRef`` - the calibration came from a **3D** resample reference tool. This is what makes five cameras line up at the seams; with 2D calibration only, the strips would not meet cleanly * ``unit=mm`` - the object coordinates in ``2DScanner.ini`` are millimetres * ``model=3``, ``order=9`` - a 9th order polynomial lens model, forward and reverse, which is what removes the distortion The file **name** is worth reading too, because it records the camera setup that was in force when the calibration was made: ================ =============================================================== Name fragment Meaning ================ =============================================================== ``Cam1`` which camera ``20260429`` the date it was made - check this after any mechanical work ``Bin1x1`` no binning ``Offs245x0`` sensor offset, 245 rows down, 0 across ``ROI500x1280`` the camera ROI, 500 rows by 1280 columns ``UpLeft45x105`` the ``TopLeft`` it was calibrated for - matches -45 / -105 ================ =============================================================== .. warning:: **A calibration is only valid for the camera setup it was made with.** If binning, ROI or sensor offset change, the calibration is wrong and must be redone. Encoding them in the file name, as here, makes a mismatch easy to spot during service - compare the name against the camera settings file. Because these cameras are cropped in the sensor rather than in software, the crop is part of the camera setup and ``CropOffset`` / ``CropSize`` are not needed in ``2DScanner.ini``. The shading correction """""""""""""""""""""""" Every camera runs the flat-field filter, ``ImageFilterType=f``, against its own correction image. The important detail is the size. The images on disk are **1050 x 450 pixels** - and the file name says ``size450x1050``, rows first, following the same *x is down* convention as the ini. That is exactly the **resampled output size** worked out above, not the 1280 x 500 raw camera image. .. warning:: **On a calibrated camera the shading image must match the resampled output, not the camera.** The filter runs after the resampler, on the image the resampler produced. In pixel mode there is no resampler, so the shading image must match the camera image instead. Get this wrong and the filter reports a size mismatch and stays inactive - the images keep coming, just uncorrected. Check it with:: print GetCamera('Scanner0001').executeCmd('get','camera1.filter.calibstatus') Now the options, ``-t192-g1.0-o0-fbgr``. They are run together without spaces, which is equivalent to ``-t192 -g1.0 -o0 -fbgr``: =============== ================================================================ Option Effect here =============== ================================================================ ``-t192`` white-point target 192. The correction is normalised so 192 counts means "leave this pixel alone". Deliberately below 255, which keeps headroom so corrected bright areas do not clip ``-g1.0`` no brightness change - the filter only evens the image out ``-o0`` no black-level shift ``-fbgr`` load the correction image as 3-channel BGR colour =============== ================================================================ .. note:: **Why -fbgr and not a Bayer pattern.** These are Bayer sensors, but ``smartedge.conf`` has ``RawBayer = 0``, so the camera driver demosaics before the 2DScanner ever sees the image. From the filter's point of view the input is ordinary BGR colour, so the correction image is loaded as BGR. Set ``RawBayer = 1`` and the driver would deliver raw Bayer instead, and ``-f`` would have to name the sensor pattern - ``gr``, ``rg``, ``gb`` or ``bg``. .. note:: A target of ``0`` would tell the filter to use the brightest pixel it finds in the correction image, which makes the whole result depend on one pixel - and on any dust or specular highlight that happened to be there. Setting ``-t`` explicitly, as here, is the more repeatable choice. Running it on SMARTedge """"""""""""""""""""""""" A few things in this file are there because it runs on SMARTedge rather than Windows: * the plugins are named as Linux shared objects - ``libhvgrab.so``, ``libsvlimagefilter.so``, ``libsvlresample.so``. The same file would work on Windows with the ``.dll`` names, and the driver translates in that direction automatically * paths use forward slashes and are relative to the folder holding ``2DScanner.ini`` * ``WorkerRtPriority=8`` needs the real-time privilege on the device. Without it the driver logs a warning and carries on at normal priority - scanning still works, it is just less even * ``PassThru=`` is empty, and on SMARTedge pass-through is ignored in any case * ``#CameraDriver=libsamplegrab.so`` on the first line is the **simulation driver**, commented out. Swap the comment over and the scanner replays stored images instead of talking to hardware, which is how the configuration is tested off the machine The cameras themselves are configured outside this file, in the ``hardware`` folder next to it - one ``.mfs`` settings file per camera, named after its serial number. That is where exposure, gain, ROI and trigger mode live. .. note:: ``Verbose=3`` prints a timing line for **every image** - five lines per scan here. That is right while commissioning, but it costs time on a running system. Drop it to 1, or to 2 if you want the per-scan frame rate summary:: GetCamera('Scanner0001').setProperty('verbose', 1) Both ``;`` and ``#`` start a comment line on SMARTedge. Only ``;`` is a real comment on Windows, so prefer ``;`` in files that move between the two. Commissioning checks """""""""""""""""""""" With a scanner this size it pays to confirm each stage separately:: cam = GetCamera('Scanner0001') # 1. did every camera get its calibration and its shading image? for i in range(1,6): print i, cam.executeCmd('get','camera%d.resampler.calibfile' % i) print i, cam.executeCmd('get','camera%d.filter.calibstatus' % i) # 2. is the stitched image the size we expect - 5250 x 450? print cam.getProperty('width'), cam.getProperty('height') # 3. are the five cameras running at the same rate and staying in step? print cam.executeCmd('get','meta') # 4. where is the time going? print cam.executeCmd('get','timing') * ``calibstatus`` must be ``ok``. ``missing`` means the file was not found; anything else is the reason it could not be read * ``width`` x ``height`` confirms the geometry maths above. A wrong width usually means a camera failed to open and was left out * in ``meta``, the five ``framerate`` values should agree and ``std`` should be small. ``lostpacket`` counting up points at network bandwidth * to see one camera on its own while aiming or focusing, bypass its filter temporarily with ``cam.setProperty('camera3.filter.active', 0)``