Repository navigation
v0.29.0
What changed and why
Two behaviour changes in the vision tools (a safer blob default, and a ring-fill counting rule), fixes to the single-grab
path, and GevSharp 0.4.1. Saved recipes are not rewritten by anything in this release — but a taught Bright ring-fill
recipe reads slightly differently (see below).
Take this release if you grab from GigE cameras: earlier versions could return a frame the camera had cut short, as
a normal grab result with its bottom rows from an older frame. This was measured on hardware; see "Imaging.Gev" below. With 0.29.0 such a
grab fails instead.
Library — blob: CvBlobOpt.UseOtsu now defaults to false (behaviour change)
Otsu always splits the histogram in two. When the search region holds only one population — the part is absent and
the region is plain background, or the part fills the whole region — it splits the noise or the lighting gradient,
and the half-lit pixels join up (8-connected) into one large blob that MinArea does not filter out. Measured with
this package on synthetic images (200×200 region, background 30 / part 220, uniform noise ±5 and ±10, gradient ±20):
- Part present (including a part covering 0.8 % of the region, and a part on a gradient): automatic and fixed
thresholds both right. - Part absent: the automatic threshold returned one blob of 42–54 % of the region. The fixed threshold returned none.
- Part filling the region: the automatic threshold reported about half the area (42–54 %).
- A band-coverage check shaped like a real one (40×628 band, dark polarity, median 3,
MinArea50, pass at ≥ 20 %)
read a band with its target missing as 49–66 % — a pass. A fixed threshold of 100 read 0 %.
A blob is mostly used to confirm that something is there, and the automatic threshold fails in the "something is there"
direction, so the default is now the fixed threshold.
- The default does not change saved recipes. A serializer that writes every property (System.Text.Json with default
settings, for one) always writesUseOtsu, so a recipe saved before 0.29.0 keeps the value written in it (usuallytrue). Presence and area recipes must be edited: turn Otsu off
and setThreshold. - What the default does reach: options built in code with no saved file (for example a tool not taught yet), and files
that lack theUseOtsukey (read asfalse). Threshold(128) is a placeholder, not a calibrated value. Set it between the part and background levels, and check that
an empty sample yields no blob. Bright means pixels above it; Dark means pixels at or below it.- For locating only (position, not presence), Otsu tolerates lighting drift — turn it on explicitly there.
Regression 9-E: a background-only region yields no blob with the default. In the same run, turning Otsu on for the
same image invents a blob of about half the region (the control), and a present part is still found with the default.
Library — ring fill: Bright counts pixels above the threshold (fix + behaviour change)
CvRingFill counted Bright as value >= threshold. The Otsu value OpenCV returns is the top of the lower class, and its
binarization treats > threshold as foreground, so >= counted one whole bin of the dark class as filled. On a
noise-free two-level image (30 / 220) Otsu returns 30, and a half-filled band read 100 %, as did an empty band
on a bright background. With ±5 noise it added about one bin in eleven. Dark (<=) was right all along.
- Bright now counts
value > threshold, for Otsu and for the fixed threshold. The fixed-threshold rule now matches the blob
tool (Cv2.Threshold). - What this costs: a taught Bright ring-fill recipe reads lower by the pixels whose value is exactly the threshold. A part
near the pass limit can flip. Dark recipes do not change. - Regression
9-D: a half-filled two-level band reads about half with Otsu, Dark on the same image is the control, and the
two polarities must add up to 100 %. A liveness check pins that Otsu returned the dark value, which is what makes>=
visible there. Putting>=back fails withRatePct = 100, ThresholdUsed = 30. - Both tools now document the one-population limit of Otsu where it is chosen (option descriptions and XML docs). For ring
fill it reads about half (54.5 % Bright, 45.5 % Dark) when an empty or full band is as bright as its surroundings.
Computing the threshold from the band pixels alone would make that worse.
Imaging — single grab
GevCam:AcquisitionStartis now sent inside thetrywhosefinallysendsAcquisitionStop. A GVCP command is on
the wire before its acknowledgement is awaited, so a caller that cancelled whileAcquisitionStartwas being acknowledged
left the device acquiring, with no Stop and no frame-number reset. On a camera that declares the acquisition-mode lock
through a register (Crevis), the nextStartContinuouscould then stall after one frame. Hardware-dependent; there is no
suite test for it.GevCam.GrabFrameAsync(Timeout.InfiniteTimeSpan)now usesGrabTimeoutMs(default 5000 ms), as theICamGrabAsync
contract says ("defer to the implementation's own deadline"). It used to mean no deadline at all, so a camera waiting for
its trigger could hold the call until the camera was closed or the token cancelled. Very large timeouts are clamped, and
zero or negative ones become 1 ms. Regression10-G-9.- The generic path (
CamGrabExt, for cameras without their own async grab) withInfiniteTimeSpan: ifGrabOnereturns
without a frame, a late frame now gets a one-second grace and then the call returnsnull. It used to wait forever,
for example after aVideoCaptureCamread failure.VirtualCampublishes before returning, so it was never affected.
Out-of-range timeouts are normalised before the grab starts. Before,Task.Delaythrew
ArgumentOutOfRangeExceptionafterGrabOnewas already running. The waits no longer leave a registration on the
caller's token. Regressions10-G-10and10-G-11. - A single grab that ends without a frame (timeout or cancellation, camera still connected) opens a short stop window, so a
block its stop cuts is logged as "in flight when acquisition stopped" (Info) rather than a dropped-frame warning. The next
grab closes that window when it starts acquisition, so a genuine loss in the next grab still warns — even in a loop that
retries or polls with a short timeout. A grab that received its frame, or ended because control was lost, opens no window.
Regression10-G-12covers the window rule, and the hardware wiring is outside the suite. TheTimeoutExceptiontext
now names both places to look: trigger mode on the camera-state line, and a separate chunk-mode warning. - Documentation of
ICamGrabAsynccorrected to match whatGevCamhas always done: a timeout isnullon the
generic path but aTimeoutExceptionfromGevCam, whose message points at what it logged at open.nullcan arrive before the
timeout (a frame in a pixel format that cannot be converted). A camera that is not open throws. Transport failures may
surface as the backend's own exceptions. Code that must work on any backend treatsnullandTimeoutExceptionalike
as "no frame". - Regression
10-G-8pins that a grab cancelled before it starts leaves no "already waiting" mark.GevCamsets the mark
inside the grab body, so it was never affected. The test guards the ordering.
Imaging.Gev — GevSharp 0.4.0 → 0.4.1
GevSharp fixed four items this package reported (its release notes: cmir79/GevSharp#2). What
consumers of this package will see:
- Frame completion: a block that ends short of what its leader announced is now incomplete, instead of being handed on
with the previous frame's bytes as its tail.IncompleteFramesgoes up by one andMissingPacketsby the part that never
came, and the GigE library warns once per open the first time it happens. Crevis and other models were not measured
with this rule. If every frame of one pixel format is dropped, with GevSharp's warning "the trailer ended the block …
the leader announced … bytes", the computed frame size does not match what the device sends for that format. - New symptom, and why it is the fix: a grab right after a cancelled grab can now time out. Measured on the Basler
below, by cancelling 20 grabs 0–3 ms after the call and then grabbing normally, eight times. In 2 of the 8 bursts the
camera also cut the next grab's own frame — trailer after 551–552 of 563 packets. It did so with GevSharp 0.4.0 and
0.4.1 alike. With 0.4.0 (CvInspect ≤ 0.28.0) that grab returned the cut frame as its answer: it arrived in 71–75 ms
instead of 78–85, and its bottom rows were left over from an earlier frame. With 0.4.1 the frame is incomplete and the
grab ends inTimeoutExceptionwith aframe N dropped: Incompletewarning. That warning is correct, because it is that
grab's own frame. A retry succeeds (the following grabs were normal in every case). Why the camera cuts the frame after
a cancelled grab has not been isolated. A grab-sequence change to avoid it is planned for a later release. Hosts that
cancel grabs in flight (a loop token) are the ones exposed; plain timeouts cut nothing in these runs. - Float features (SwissKnife/Converter formulas) are evaluated in floating point. On Crevis, gain, delay, temperature
and frame rate now read their correct values. The exposureGevCamreads and writes is unchanged on both known models. - Writes that fail after reaching the device now drop the cached value. The case this fixes: an
AcquisitionStop
whose acknowledgement was lost could leave a Crevis camera's acquisition mode locked in the cache. ReceiveAsynclifetime is documentation only.GevCamalready folds its pump on control loss.
Measured through this package on the shared Basler (acA2500-14gm), with the published 0.4.1 package: 10 single grabs, 1.5 s of live
(21 frames), 10 more grabs, five 1 ms-timeout grabs (all TimeoutException), 20 grabs cancelled 0–3 ms after the call,
then grabs and live again, and one InfiniteTimeSpan grab. In the first run everything was normal, with no warning
or error lines and "84 completed, 0 incomplete". The second run, on the final code, cut one block during the
cancellations and timed out the first grab after them, which led to the burst runs above. Whether a cancellation hit
the Start-acknowledgement window is not known. This Basler declares no acquisition-mode lock, so the stall that the
AcquisitionStart fix prevents cannot occur on it.
Translations (public through CvLoc.T)
cv:UseOtsuDescis removed. It is split intocv:BlobUseOtsuDescandcv:RingUseOtsuDesc.cv:BlobThresholdDescandcv:RingThresholdDescstate the boundary rule. The blob text also says 128 is a placeholder.- A host that ships loose
Assets/langcopies overriding the library text key by key must rename the key and pick up the new
texts.
Documentation
CvCropOpt.FromLegacy: discarding its result is not a recovery. The tools after the crop were taught in the cropped space, so
they judge at a shifted position and still look normal. Discarding is safe only together with re-teaching those tools.
Version
0.29.0 — minor. Changed: the default of CvBlobOpt.UseOtsu (true → false), the Bright counting rule of CvRingFill, and
GevCam/generic-path timeout handling (InfiniteTimeSpan, out-of-range values). Removed: translation key cv:UseOtsuDesc.
GevSharp dependency 0.4.0 → 0.4.1. No public type or member was added or removed. The four packages are released in lockstep.
Checks
-
dotnet build CvInspect.sln -c Release --no-incrementalreports 0 warnings -
mainis an ancestor of this branch (the release-pr-guard job verifies it) - the tag will be created on the merge commit,
mainmerged back intodevafterwards, anddevbumped to the next-devversion