C#
C# 3
Usage and Important Notes
Channel order (AccessChannel number) is environment-dependent.
First, grab just one frame and determine which is Range/Intensity based on the size and bit depth.
(For example, Range is 16-bit, Intensity is 8-bit, etc. This also depends on the Stream Setup settings.)
This code assumes 1 Grab = 1 line (height = 1px).
If your setup is for a 2D image containing multiple lines in 1 Grab, tile_images is unnecessary and the image will be used as a "height map" as is.
Bit Depth: If aiming for a few microns, Mono12/16 equivalent is strongly recommended (to ensure quantization fineness).
Match the PixelFormat to your camera settings and save as a 16-bit TIFF.
Converting Z to mm: Apply Z_mm = scale * DN + offset in a post-process (scale/offset are camera/stream setup values).
Removing the tilt of the reference plane using plane fitting will result in tiny steps (scratches).
Reason for choosing asynchronous:
At high line rates, acquisition and post-processing (linking/saving) can be separated, making it easier to avoid missing data.
This asynchronous version is fine to start with. If you need even higher throughput, you can gradually improve it by saving and processing in a separate thread.
To switch to software trigger:
Set TriggerMode="On", TriggerSource="Software", and execute
SetFramegrabberParam(acq,"TriggerSoftware","execute") → GrabDataAsync(...) for each line.
```csharp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111 | using System;
using HalconDotNet;
class GrabRangeIntensity_AsyncStack
{
static void Main()
{
// === Adjust to your environment ===
const string IFace = "GigEVision2"; // e.g., "GigEVision2"
const string Device = "RULER3000_01"; // DeviceUserID or "169.254.x.x"
const int NumLines = 100; // Number of lines to acquire (assumes 1 grab = 1 line)
const string SaveDir = @"C:\temp\ruler_async"; // Save destination
const string PixelFmt = "Mono16"; // 12/16bit is recommended for micron-level precision (must match the camera)
HTuple acq = null;
HObject[] rangeLines = new HObject[NumLines];
HObject[] intenLines = new HObject[NumLines];
HObject rangeConcat = null, intenConcat = null;
HObject rangeStack = null, intenStack = null;
try
{
System.IO.Directory.CreateDirectory(SaveDir);
// 1) Open the frame grabber
HOperatorSet.OpenFramegrabber(
IFace,
0,0,0,0,0,0,
"default",
-1, "default", -1, "false",
"default",
Device,
0, -1,
out acq);
// 2) Enable chunks & free run (can be changed to software trigger later if needed)
TrySet(acq, "ChunkModeActive", "true"); // Receive Range+Intensity as a simultaneous payload
TrySet(acq, "PixelFormat", PixelFmt); // Must match the camera settings
TrySet(acq, "TriggerMode", "Off"); // Start with free run
// 3) Start asynchronous acquisition
// grab_data_start -> grab_data_async in a loop
HOperatorSet.GrabDataStart(acq, -1);
// 4) Acquisition loop: just acquire data here. Concatenation and tiling will be done later.
for (int i = 0; i < NumLines; i++)
{
// -1 means "wait" for an indefinite period. Adjust to an appropriate value like milliseconds if needed.
HObject imagesTuple; // Contains (Range, Intensity, ...)
HTuple metaTuple; // Metadata like 'mark'. Can be ignored if not used.
HOperatorSet.GrabDataAsync(out imagesTuple, out metaTuple, acq, -1);
// Extract Range / Intensity from the image tuple
// * The channel order depends on the settings.
// Typically, [1]=Range and [2]=Intensity (1-based index), but verify if needed.
HObject rangeImg, intenImg;
HOperatorSet.AccessChannel(imagesTuple, out rangeImg, 1); // 1st channel -> Range (assumed)
HOperatorSet.AccessChannel(imagesTuple, out intenImg, 2); // 2nd channel -> Intensity (assumed)
// This assumes 1 Grab = 1 line (height = 1px)
rangeLines[i] = rangeImg;
intenLines[i] = intenImg;
imagesTuple.Dispose(); // Release the tuple itself when no longer needed
// metaTuple is HTuple, so no explicit release is needed
}
// 5) Concatenate -> tile in one go (vertical stack: Rows=NumLines, Cols=1)
HOperatorSet.GenEmptyObj(out rangeConcat);
HOperatorSet.GenEmptyObj(out intenConcat);
for (int i = 0; i < NumLines; i++)
{
HObject tmp;
HOperatorSet.ConcatObj(rangeConcat, rangeLines[i], out tmp);
rangeConcat.Dispose(); rangeConcat = tmp;
rangeLines[i].Dispose();
HOperatorSet.ConcatObj(intenConcat, intenLines[i], out tmp);
intenConcat.Dispose(); intenConcat = tmp;
intenLines[i].Dispose();
}
HOperatorSet.TileImages(rangeConcat, out rangeStack, NumLines, 1);
HOperatorSet.TileImages(intenConcat, out intenStack, NumLines, 1);
// 6) Save as 16bit TIFF (PNG is also possible, but TIFF is safer for preserving 16bit data)
string pathRange = System.IO.Path.Combine(SaveDir, "range_stack_async.tiff");
string pathInten = System.IO.Path.Combine(SaveDir, "intensity_stack_async.tiff");
HOperatorSet.WriteImage(rangeStack, "tiff", 0, pathRange);
HOperatorSet.WriteImage(intenStack, "tiff", 0, pathInten);
Console.WriteLine($"saved:\n {pathRange}\n {pathInten}");
}
finally
{
if (rangeStack != null) rangeStack.Dispose();
if (intenStack != null) intenStack.Dispose();
if (rangeConcat != null) rangeConcat.Dispose();
if (intenConcat != null) intenConcat.Dispose();
if (acq != null) HOperatorSet.CloseFramegrabber(acq);
}
}
// Utility to suppress errors if the interface doesn't support a parameter
static void TrySet(HTuple acq, string name, string val)
{
try { HOperatorSet.SetFramegrabberParam(acq, name, val); } catch {}
}
}
|