Skip to content

v0.24.0

Choose a tag to compare

@github-actions github-actions released this 16 Sep 07:29
· 175 commits to main since this release
0f6d1a1

What changed and why

Most of this release came from asking one question about one method — should the library decide
whether a landmark is there, or is that the host's call?
— and following the answer down. On the way
a defect class surfaced that runs through the measurement API: values that look plausible and are
wrong, with nothing to say so.
A pose that folds every taught coordinate onto (0,0). A fit whose
residual improves as the evidence disappears. A fill rate that reads 100% because most of the band
was never seen. Each one is fixed here so that it either reports honestly or refuses.

Every claim below was measured, not reasoned: each fix has a regression, and each regression was run
against a deliberately broken build to prove it fails there.

Judging "found"

  • CvPatternFinder.Match returning non-null does not mean the pattern was found, and
    MatchAll(...).Count > 0 is not a detection count.
    null means only a structural impossibility;
    a scene with no part still comes back with a pose carrying a low score (measured: 0.085 against
    1.000 for the same scene with the part). MatchAll's element 0 skips the AcceptScore check
    entirely — raising AcceptScore to 0.999999 still returns Count == 1. That contract lived in the
    class summary only; it is now on the methods themselves, which is what IntelliSense shows at the
    call site. Judge on Score >= AcceptScore, or use CvInspGeom.MatchPattern, whose Present
    does it for you.
  • A related claim in the docs was simply false and has been corrected. "Empty scenes return an
    empty list" holds for a circle-trained template and not for a rectangle-trained one, which returns
    its best candidate with a near-zero score. Judging on the count therefore flipped with a teaching
    choice the judging code never sees.

Measurements that were quietly wrong

  • CvPose is a struct, so it cannot be null, and default(CvPose) has Scale == 0 — the = 1.0
    in the declaration is a constructor default, not a struct default. FirstOrDefault() on an empty
    result list therefore hands you a plausible-looking pose that folds every transformed coordinate
    onto (0,0), and Inverse() on it yields an infinite scale, which this library's own notes
    describe as taking the process down in native code. Apply and Inverse now throw with a reason
    instead of returning folded coordinates, and CvPose.IsValid lets you check first.
  • A line or circle fit gets a better residual as evidence disappears, because the few surviving
    points are more nearly collinear or fit any circle well. Measured: with 12 calipers over a 360 px
    taught segment, edges over only 100 px give 3 points at RmsPx 0.000 with the angle off by 9.46°,
    passing both the residual and angle gates; a 60° arc gives 3 points at RmsPx 0.000 with 3.47 px of
    radius error, while the full circle gives 16 points at 0.136 and 0.21 px. The residual is not a
    coverage gate and never was.
    CvFindLineOpt.MinPoints and CvFindCircleOpt.MinPoints now gate on
    how many points survived. They default to 0 (off), so nothing changes for an existing recipe.
  • CvRingFill counted only the band pixels it could see, so a band running off the edge of the
    image reported a higher fill rate — 100% for a band 40% outside. That is the worst direction for
    an inspection to be wrong in. The denominator is now the whole band, unobserved area counts as
    unfilled, and CvRingFillHit.OutsidePx tells you how much was never seen so you can separate "badly
    configured" from "genuinely empty". A band fully inside the image gives exactly the same number as
    before
    — pinned by a regression.
  • CvPatternOpt.WrapPeriodX persisted into recipe JSON although its own documentation calls it a
    per-run value the host assigns every frame, and the README promises volatile runtime values carry
    [JsonIgnore]. One frame's value therefore baked into the recipe and fed back as the next run's
    parameter: measured, the same scene scores 1.0000 with it at 0 and 0.0893 with a stale 200 — a good
    part silently failing. It no longer serializes.

Nothing the library calls takes the caller down with it

  • A CvLog sink that throws no longer propagates. This covers both CvLog.Sink = ... (which
    replays held lines) and every CvLog.Publish, which runs on acquisition threads and teardown paths
    where an already-disposed host logger is a common way to throw. The undelivered line goes to the
    diagnostic trace instead — line not delivered: <level> <source> <message>, so its content survives
    — and lines a throwing sink did not receive during a replay are put back for the next sink.
  • A property that cannot be read no longer takes the property editor down. List rows read and
    enumerate getters nothing read before — a lazy getter talking to a device, a list another thread is
    filling. A throw now costs that one row, not the control, and a CvLog warning names the property.

Landmark verification moves to the host, and the geometry under it becomes public

CvInspGeom.VerifyLandmark is obsolete and will be removed in the next minor. What it did was one
Cv2.MatchTemplate call wrapped in geometry this toolkit already owns — and that wrapping was weaker
than the toolkit's own matcher: no mask, no angle or scale search, no gates. It read 8 of
CvPatternOpt's 38 members, so a landmark's AcceptScore, TrainedShape (the circular mask!) and
search settings did nothing at all. Deciding whether a landmark is present is the host's call; the
library's job is the geometry underneath. That geometry is now public:

  • CvInspGeom.FixturePose(pat, found, extraDeg) — the finder's pose with the trained angle taken
    out, ready for CvPose.Apply / Inverse / Compose. The finder puts the absolute found angle
    in ThetaDeg; only XformByPose subtracted the trained angle, so applying a finder pose directly
    rotated taught geometry by the trained angle too — 43 px off a part that had not moved, on a pattern
    taught at 25°, and invisible whenever the trained angle is 0. FixturePose(pat, p).Apply(x, y)
    equals XformByPose(pat, p, x, y), pinned by a regression.
  • CvInspGeom.NormalizedWindow(...) — a window in which the part stands at its taught pose and
    taught size, plus a CvPose that maps window coordinates straight back to the input image. It warps
    only the window, not the frame, so a caller sweeping candidate angles no longer pays for a
    full-image rotation per candidate. The coordinate space does not change with the outcome: a window
    falling outside the image keeps its size and its space.
  • The overload taking a target pattern sizes and places the window from that pattern and returns
    the opt to match with (MatchOpt). It exists because two settings are consequences of the
    normalization that a caller cannot be expected to remember: the trained angle is already removed
    (leave it in the opt and the finder evaluates a pose the window does not contain — measured
    ThetaDeg 0.0 vs 20.0), and the search region must be re-pinned to the margin you asked for
    (without it the finder treats the whole window as the allowed centre range). UseAngleSearch and
    UseScaleSearch are deliberately left alone — those are yours; the documentation explains what
    they now mean in a normalized window.
  • CvPatternOpt.Clone() for building a per-run copy, and CvPatternOpt.TemplateSize(), which
    reads the PNG header without decoding so you can size a window before matching.
  • CvUnwrapGeom.WrapPoseForLandmark is removed. It existed only to feed VerifyLandmark and had
    no callers anywhere.

Where each release's changes live

The four packages now carry a Release Notes link to their own version's release page. Until this
version there was no way to reach the change log from nuget.org at all, and the pointer cannot be
added retroactively — 0.17.0 through 0.23.0 will never have it. The three non-core package READMEs
gained a Versions section with the lockstep rule, the 0.x pinning warning and the releases link;
previously that guidance shipped only in the core package. The GigE README now says plainly that
0.20.0 is the floor if you use GigE — below it the device's 16-bit frame id was compared by size,
so acquisition stalled when the number wrapped, about every 78 minutes at 14 fps.

Upgrading

  1. CvPose.Apply and Inverse now throw on a pose that cannot be used for a transform (scale 0 or
    non-finite). Code that fed a default(CvPose) through them used to get (0, 0) and carry on
    inspecting; it now gets an exception naming the cause. Check IsValid, or check the result list is
    non-empty rather than using FirstOrDefault().
  2. CvRingFill reports a lower rate for a band that runs off the image. A fully visible band is
    unchanged to the pixel. If a recipe was passing on a clipped band, it was passing on unobserved
    area; OutsidePx now tells you that is what happened.
  3. CvPatternOpt.WrapPeriodX no longer round-trips through JSON. Recipes that stored it will load
    with it at 0; the host is expected to assign it per run, as its documentation always said.
  4. A host log sink that throws no longer surfaces that exception to you — it goes to the diagnostic
    trace. If you were learning about logger faults by catching them, watch Trace instead.
  5. CvInspGeom.VerifyLandmark now raises an obsolete warning. Nothing about its behaviour changed
    except two fixes: the expected position now scales with the found pose (with scale search enabled it
    was drifting by the taught offset times the scale error — measured 19 px at scale 1.15, reporting a
    present landmark at score 0.087), and the clipped-window path now returns the expected point in
    input-image space as documented rather than 248 px away in normalized space. If you tuned a
    threshold against the drifted behaviour with scale search on, re-check it.
  6. Migrating off VerifyLandmark changes scores, in the direction of correctness: a circle-trained
    landmark finally gets its inscribed mask, the window is resampled once instead of twice, and the
    comparison happens at taught scale. Establish the baseline after moving, not before.
  7. The property editor may show rows that were invisible before — this was already true in 0.23.0
    and is repeated here because it is the change most likely to surprise: any public IEnumerable
    property that is not [Browsable(false)], and any property carrying
    [TypeConverter(typeof(ExpandableObjectConverter))].

Version

0.24.0 — minor, because the public API grew: CvInspGeom.FixturePose, two NormalizedWindow
overloads, CvPatternOpt.Clone, CvPatternOpt.TemplateSize, CvPose.IsValid,
CvFindLineOpt.MinPoints, CvFindCircleOpt.MinPoints, CvRingFillHit.OutsidePx. One removal
(CvUnwrapGeom.WrapPoseForLandmark, which had no callers in this repository or in either consuming
repository) and one obsoletion (CvInspGeom.VerifyLandmark, behaviour unchanged, removed next minor).
The acquisition contract — ICam, CamFrame, CamOpt — is untouched.

Checks

  • dotnet build in the default configuration 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