Repository navigation
v0.29.2
What changed and why
Fixes to how live acquisition starts and stops, most of them in ReconnectingCam, plus clearer diagnostics when a camera's
automatic exposure is on. No public type or member was added or removed. Two ICam documentation remarks now state
what implementations and callers can rely on (see "Contract notes").
Take this release if you wrap cameras in ReconnectingCam (the recommended setup for GigE), or if one thread can call
StopContinuous while another is still in StartContinuous.
Imaging.Gev — a stop that arrives while a live start waits now wins
0.29.1 added a short wait before a live start that follows an aborted single grab, and ran that wait outside the camera's
lock. A StopContinuous from another thread during the wait found nothing running and returned, and the waking start then
began live acquisition: Stop was called, yet live kept running. 0.29.0 held the lock for the whole start, so the stop
queued behind it. A consumer found this by reading the 0.29.1 source; it was then reproduced on the shared Basler
(acA2500-14gm): 30 ms exposure, stop 20 ms after the start call, 40 tries. All 25 tries whose start waited were still live
after the stop; in the same run, none of the 15 tries without a wait were.
StopContinuousnow counts stop requests at the moment it is called, even when nothing is running.
StartContinuousrecords the count when called and stands down if a stop was called after it. It logs
continuous grab not started: …, throws nothing and raises noGrabbingChanged. A close and reopen in the meantime, or
a control loss during the wait, also makes the start stand down.- The stop is counted before it waits for the camera lock. A first version counted it once it held the lock. Then a stop
called before a start, but still waiting while another call held the lock (an exposure write, another stop), was
counted as the later command and cancelled the start. The pre-release review found this; regression10-G-16. - Same probe after the fix, with each try classified by when the start thread actually entered: 0 violations in 40 tries
directly onGevCam, and 0 in 40 throughReconnectingCam.
Imaging — ReconnectingCam
A consumer porting this class found four defects and reported them by reading our source. Each was first reproduced here
with a fake camera: the new tests failed on the code just before each fix (the defects themselves predate 0.29.1).
Reviewing the fixes turned up more of the same kind. What changes for a subscriber:
- Close and Dispose now announce the disconnect. Before, closing a connected, live camera raised no
GrabbingChanged(false)and noConnectionChanged(false)at all. A screen that mirrors state from events kept showing it
connected. The order is grabbing first, then connection, as the inner cameras raise them. - A wrapped
DeadCamis no longer reported connected. ItsOpenonly logs a warning. With default options the first
Opennow throws with the dead slot's reason; withRetryInitialOpenit keeps retrying without ever announcing a
connection. - A session lost right after it opened is retried. A loss that landed between the inner
Openand the swap was dropped
as a stale notification, so the wrapper kept a dead camera as connected and never reconnected. - An open that finishes after Close is discarded.
Closewaits for a reconnect in progress only up toShutdownWaitMs.
If the innerOpenfinished later, its session was attached and the closed camera stayed connected (and kept device
control until the next Close). It is now closed and disposed, and an Info line says so. The same applies to aCloseor
Disposethat wins over a slow firstOpen. - Connected and grabbing are announced only for the session that is still current. A Close landing in the short gap
after a reconnect could leaveIsConnected == trueafter Close, with no camera behind it. A session that died while live
was being started or resumed could be announced as grabbing after its disconnect. Both states are now decided under the
same lock that records them. - Live is announced only when the inner camera is really live. When the inner camera stood down (see above), or when two
threads called Stop and Start at almost the same moment, the wrapper raisedGrabbingChanged(true)from the return alone
and reportedIsGrabbing == truewith nothing running. It now checks the inner camera'sIsGrabbingafter the call. An
adapter that reports live later, from a callback, is still picked up from itsGrabbingChanged(true), unless a Stop has
come in first. - A reconnect no longer tears down a healthy session. If a reconnect attempt failed and, during the backoff, your code
calledOpen()to recover (asICam.Opensuggests), the loop's next attempt closed that working session and opened
another. During that, grabs failed with "Camera is reconnecting." and live dropped. Only a session whose loss was
reported is replaced now. Open()after a loss restores live, as a reconnect does. Before, only the reconnect loop brought back the live intent.
A session you opened yourself, or aStartContinuouscalled whileOpenwas still running, left live off with the
intent still set, and a later, unrelated reconnect then started live that nobody had asked for at that moment.- An exposure set during a reconnect is applied. A
SetExposureTimeUsthat landed after the new session read the
exposure, but before it was announced, was recorded but never reached the camera. The camera then ran the old exposure
with no error and no log line. - A start ended by Close returns quietly. Before, the caller got the inner camera's
ObjectDisposedException, which
names the inner camera even though only the wrapper had been closed. - Dispose no longer strands a queued
Open. AnOpenwaiting behind a slow reconnect could wait forever onceDispose
ran; it now wakes and throwsObjectDisposedException. A reconnect attempt that finishes after Dispose no longer logs
reconnect loop failed. - A cancelled reconnect loop whose task starts late no longer clears the next session's pending reconnect request. When
that happened, the dead session was never retried. - A start that fails because the connection was just lost no longer drops the live intent. The reconnect then brings live
back. (GevCamalso treats such a start as a loss, not a refusal.) - Known limitation: if two threads call
StopContinuousandStartContinuousat almost the same moment, within a window of
microseconds, the inner camera can drop the start. Live then stays off while the recorded intent says on. This is not a
false "live" report, and the nextOpenor reconnect restores it. - Events are decided under the wrapper's lock but raised outside it, so transitions made on different threads can reach a
subscriber out of order. AsICamalready recommends,IsConnectedandIsGrabbingare the values to trust.
Regressions 10-11, 10-R1…10-R14 and 10-G-16. The new ones were confirmed to fail on the code before their fix.
Each guard was also removed on its own to see whether its test fails; the result is in "Checks" below.
Imaging.Gev — automatic exposure
With ExposureAuto on, GevCam wrote the configured exposure anyway. On the Basler measured here the exposure node is
locked while automatic exposure runs, so the write was refused with a warning that said only "read-only". The camera then
set its own exposure: 29995 → 175910 µs, and mean brightness went from 22 to 127. Neither Open nor SetExposureTimeUs
threw. On Crevis MG-A500M-22 (firmware 3.7.3.0) the XML declares no such lock, so the write reaches the device. What that
camera then does with it was not measured.
- The camera-state line at open now includes
exposure=(the value after the write) andexposureAuto=. - When automatic exposure is on and the node is locked, the warning names
ExposureAutoand gives both ways out. For a fixed
exposure, turnExposureAutooff on the camera or in its user set. For automatic exposure, leaveCamOpt.ExposureTimeUs
at 0 and do not callSetExposureTimeUs. If the write is accepted but automatic exposure stays on, a warning says the
value will not hold. If the write turned automatic exposure off, an Info line says so. GevCamdoes not turn automatic exposure off by itself.CamOpt.ExposureTimeUsdefaults to 10000, so every open
writes an exposure. Switching auto exposure off would silently change sites that rely on it.- Diagnostic reads on the open path no longer throw on transport errors.
Contract notes (documentation, no API change)
ICam.StartContinuous: a normal return does not mean live started. AStopContinuouscalled between the call and the
start wins, and the call returns without starting. ReadIsGrabbingorGrabbingChangedto know. A wrapper must not
announce "started" from the return alone. If you implement it: when live did start,IsGrabbingmust already be true
on return.ICam.Open: an implementation that will be wrapped inReconnectingCammust return withIsConnectedalready true. The
wrapper checks it afterOpenand treats false as a failed open, so an adapter that setsIsConnectedlater from a
callback never attaches. Every camera in this repository already does this, andDeadCamdeliberately does not. The
Imaging README's "Vendor adapters" section now states both requirements.
What can change for you when you upgrade from 0.29.1
- A
ReconnectingCamwhose factory returns aDeadCam(for exampleCamFactory.Createwith an unregisteredComType)
used to report itself connected. Now, with default options, its firstOpen()throwsInvalidOperationException
with the dead slot's reason. WithRetryInitialOpen, it keeps retrying without announcing a connection; set
MaxAttemptsif the slot will never heal. ReconnectingCam.Close()andDispose()now raiseGrabbingChanged(false)andConnectionChanged(false)when live or
connected. 0.29.1 raised nothing.ReconnectingCam.Open()after a loss (with noClosein between) now restores live if it was on. AStartContinuous
called while the wrapper was closed is still ignored by the nextOpen, as in 0.29.1.- New
GevCamlog lines appear only on the paths they describe, not on a normal configuration:continuous grab not started: …(Info)exposure=… exposureAuto=…on the camera-state line; a declared node that could not be read shows(not read)
rather than(absent)- the ExposureAuto warnings described above
Version
0.29.2 — patch. No public type or member was added or removed. The behaviour changes are in the start/stop ordering of
GevCam and ReconnectingCam, the events ReconnectingCam raises on Close/Dispose/reconnect, the open-time state line,
and the automatic-exposure warnings. The four packages are released in lockstep.
Checks
-
dotnet build CvInspect.sln -c Release --no-incrementalreports 0 warnings - Tests: 56 (core) + 30 (Wpf) pass
- Four adversarial reviews of this range, each finding checked by three verifiers before being acted on. Everything that
survived is fixed above, except the known limitation. - Guards removed one at a time (14): 13 are caught by their test. The one that is not, the "closed" term in the connected
announcement, is almost always redundant because Close has already removed the inner camera. Without it the worst case is
an extra true/false pair, not a wrong final state. - Hardware (shared Basler acA2500-14gm): the start/stop race was reproduced before the fix. On the final code it is clean:
0 violations in 40 tries directly and 0 in 40 throughReconnectingCam, and live runs at 14–15 frames/s with no warning or
error lines. The ExposureAuto behaviour was measured before and after. The reconnect-path fixes (loss, reopen, Close/Dispose
races) were verified with the fake camera only. -
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