Skip to content

v0.29.0

Choose a tag to compare

@github-actions github-actions released this 26 Sep 03:40
· 47 commits to main since this release
4906512

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, MinArea 50, 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 writes UseOtsu, so a recipe saved before 0.29.0 keeps the value written in it (usually true). Presence and area recipes must be edited: turn Otsu off
    and set Threshold.
  • What the default does reach: options built in code with no saved file (for example a tool not taught yet), and files
    that lack the UseOtsu key (read as false).
  • 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 with RatePct = 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: AcquisitionStart is now sent inside the try whose finally sends AcquisitionStop. A GVCP command is on
    the wire before its acknowledgement is awaited, so a caller that cancelled while AcquisitionStart was 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 next StartContinuous could then stall after one frame. Hardware-dependent; there is no
    suite test for it.
  • GevCam.GrabFrameAsync(Timeout.InfiniteTimeSpan) now uses GrabTimeoutMs (default 5000 ms), as the ICamGrabAsync
    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. Regression 10-G-9.
  • The generic path (CamGrabExt, for cameras without their own async grab) with InfiniteTimeSpan: if GrabOne returns
    without a frame, a late frame now gets a one-second grace and then the call returns null. It used to wait forever,
    for example after a VideoCaptureCam read failure. VirtualCam publishes before returning, so it was never affected.
    Out-of-range timeouts are normalised before the grab starts. Before, Task.Delay threw
    ArgumentOutOfRangeException after GrabOne was already running. The waits no longer leave a registration on the
    caller's token. Regressions 10-G-10 and 10-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.
    Regression 10-G-12 covers the window rule, and the hardware wiring is outside the suite. The TimeoutException text
    now names both places to look: trigger mode on the camera-state line, and a separate chunk-mode warning.
  • Documentation of ICamGrabAsync corrected to match what GevCam has always done: a timeout is null on the
    generic path but a TimeoutException from GevCam, whose message points at what it logged at open. null can 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 treats null and TimeoutException alike
    as "no frame".
  • Regression 10-G-8 pins that a grab cancelled before it starts leaves no "already waiting" mark. GevCam sets 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. IncompleteFrames goes up by one and MissingPackets by 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 in TimeoutException with a frame N dropped: Incomplete warning. 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 exposure GevCam reads 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.
  • ReceiveAsync lifetime is documentation only. GevCam already 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:UseOtsuDesc is removed. It is split into cv:BlobUseOtsuDesc and cv:RingUseOtsuDesc.
  • cv:BlobThresholdDesc and cv:RingThresholdDesc state the boundary rule. The blob text also says 128 is a placeholder.
  • A host that ships loose Assets/lang copies 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-incremental reports 0 warnings
  • main is an ancestor of this branch (the release-pr-guard job verifies it)
  • the tag will be created on the merge commit, main merged back into dev afterwards, and dev bumped to the next -dev version