Repository navigation
v0.24.0
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.Matchreturning non-null does not mean the pattern was found, and
MatchAll(...).Count > 0is not a detection count.nullmeans 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 theAcceptScorecheck
entirely — raisingAcceptScoreto 0.999999 still returnsCount == 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 onScore >= AcceptScore, or useCvInspGeom.MatchPattern, whosePresent
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
CvPoseis a struct, so it cannot be null, anddefault(CvPose)hasScale == 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), andInverse()on it yields an infinite scale, which this library's own notes
describe as taking the process down in native code.ApplyandInversenow throw with a reason
instead of returning folded coordinates, andCvPose.IsValidlets 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 atRmsPx0.000 with the angle off by 9.46°,
passing both the residual and angle gates; a 60° arc gives 3 points atRmsPx0.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.MinPointsandCvFindCircleOpt.MinPointsnow gate on
how many points survived. They default to 0 (off), so nothing changes for an existing recipe. CvRingFillcounted 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, andCvRingFillHit.OutsidePxtells 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.WrapPeriodXpersisted 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
CvLogsink that throws no longer propagates. This covers bothCvLog.Sink = ...(which
replays held lines) and everyCvLog.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 aCvLogwarning 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 forCvPose.Apply/Inverse/Compose. The finder puts the absolute found angle
inThetaDeg; onlyXformByPosesubtracted 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)
equalsXformByPose(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 aCvPosethat 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.0vs20.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).UseAngleSearchand
UseScaleSearchare 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, andCvPatternOpt.TemplateSize(), which
reads the PNG header without decoding so you can size a window before matching.CvUnwrapGeom.WrapPoseForLandmarkis removed. It existed only to feedVerifyLandmarkand 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
CvPose.ApplyandInversenow throw on a pose that cannot be used for a transform (scale 0 or
non-finite). Code that fed adefault(CvPose)through them used to get(0, 0)and carry on
inspecting; it now gets an exception naming the cause. CheckIsValid, or check the result list is
non-empty rather than usingFirstOrDefault().CvRingFillreports 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;OutsidePxnow tells you that is what happened.CvPatternOpt.WrapPeriodXno 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.- 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, watchTraceinstead. CvInspGeom.VerifyLandmarknow 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.- Migrating off
VerifyLandmarkchanges 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. - 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 publicIEnumerable
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 buildin the default configuration reports 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