Aravis: correct what the adapter reports, and handle cameras that lack features - #994
Merged
marktsuchida merged 2 commits intoSep 17, 2026
Conversation
Six defects, all in what the adapter tells Micro-Manager about the image it is producing. GetROI() assigned gx to both x and y, and assigned both sizes to themselves, so every caller got a garbage region. On a camera sitting at 0,0 the first two values happened to be right, which is how this survived. The image dimensions and the buffer size were only ever learned from a frame, so they were zero until the first snap -- MMCore sizes its circular buffer from them before that -- and stale after every change to the region or the binning, because Micro-Manager reads them back immediately and the next frame had not arrived yet. Both now come from the camera's region, read at Initialize() and after every change. Initialize() also took the geometry from the width and height *bounds* rather than the region, so a camera opening on anything smaller than its maximum described every image it produced with the wrong dimensions. Mono12 reported a bit depth of 10. rgb_to_rgba() copied the three colour bytes in the order the camera sent them, so an RGB8 camera came out with red and blue exchanged -- Micro-Manager's RGB32 is BGRA -- and never wrote the alpha byte at all, which MM's documentation says should contain zeroes. It held whatever the image buffer held last: zeros on a fresh allocation, stale pixels once the buffer had been reused. PixelType carried the camera's GenICam format names, and setting it drove the camera. That is two vocabularies in one property, and there was a third: ArvPixelFormatUpdate() wrote strings like "8bit mono" that belong to neither, and GetImageBuffer() pushed them into the property on the image path. They are now separate. PixelFormat selects what the camera sends, under the camera's names. PixelType is read-only and says what the buffer looks like, as 8bit / 16bit / 32bitRGB -- the strings the other device adapters use. Note that MMDevice's g_Keyword_PixelType_* constants are a different vocabulary for a different thing: they are the image metadata tag of the same name, which MMCore writes itself. That last split also fixes something the previous change introduced: the "Unknown" marker for a format with no implementation was reaching the format setter through GetImageBuffer(), so the adapter asked the camera to switch to a pixel format called "Unknown" once per image. Nothing writes PixelType now. Also here, because they are one line each and the same kind of mistake: a format the adapter cannot decode is reported when the format changes rather than once per frame, SetROI() no longer divides by an increment the camera never supplied, and OnPixelFormat() does not write a format the camera is already in -- a camera offering one format has no reason to make PixelFormat writable, so that write failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The adapter assumed a camera with binning, a settable region, an exposure time and a frame rate, and called all of them unconditionally. Each assumption is now a question asked once, at Initialize(). The region is two questions, not one. Aravis' region-offset probe reports on OffsetX and OffsetY only, so a camera with a settable size and a fixed offset -- or the reverse -- was handled wrongly in both directions. Width and Height are now asked for their GenICam access mode, which is what decides whether a write can succeed. ClearROI() returns immediately on a camera whose size is fixed, because such a camera is always at full frame and there was nothing to clear; it also no longer sets a 64x64 region as an intermediate step, which was superstition -- arv_camera_set_region() already zeroes the offsets before writing a new size -- and which failed outright on any camera whose minimum width is above 64. SetExposure() called set_frame_rate(-1.0) on every exposure change, which is an error on every exposure change for a camera with no frame rate control. It also read frame rate bounds it never looked at, and whose own comment said they never change. The binning increment is read through a guard. It is the step of the loop that builds the allowed-value list, so an increment the camera could not report -- which comes back as zero -- never terminated. The rest of that block was the same fix as 5145ff8, arrived at independently; this branch keeps that commit's version. ClearROI() and SetBinning() returned DEVICE_OK whatever happened, so a camera that refused a setting looked like one that accepted it. Both report now, and the two error codes this adapter returns have text: a user whose camera is switched off gets a sentence naming the camera id instead of "3141". numImages was ignored: asking for 8 frames delivered hundreds and the sequence never finished. The stream callback now counts and ends the sequence itself. It cannot call StopSequenceAcquisition() to do that -- unreffing the stream joins the very thread the callback runs on -- so it stops the camera over the control channel and leaves the stream for whichever stop or shutdown comes next. It also stops on any InsertImage() failure, which is what MMDevice.h asks of a camera and which covers stopOnOverflow, since the Core no longer reports an overflow it was told not to stop on. A camera whose formats the adapter cannot decode used to open anyway, reporting zero bytes per pixel, which MMCore turns into "memory requirements not adequate" the first time live acquisition starts. It now opens on a format it can decode if the camera offers one, and refuses to open at all if it does not, saying which formats the camera offered. Formats dropped from the list are logged rather than silently omitted. Finally, the camera says who it is: vendor, model, serial number, device id and firmware version as read-only properties. A configuration with two cameras of the same model recorded nothing about which was which. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Aravis: correct what the adapter reports, and handle cameras that lack features
marktsuchida
pushed a commit
that referenced
this pull request
Sep 26, 2026
SetROI() used arv_camera_set_region(), which writes the new size and then the offsets back to back. An Allied Vision Alvium 1800 U-1240m refuses an offset written that way, as "invalid-parameter", for any offset at all, so on that camera every region that did not start at the corner failed. Before #994 it failed silently, leaving the right size in the wrong place. Narrowed down on the camera with arv-tool, with no adapter involved: the region itself is legal, and neither the order of the writes nor reading Width back makes any difference. Reading the offset's limit between the size write and the offset write does. The camera appears to check an offset against a limit it recomputes only when that limit is read. Allied Vision's own adapter reads each feature before writing it, which is presumably why it works there. The camera's GenICam marks the limit as invalidated by the width, so the read reaches the camera rather than Aravis' cache. SetROI() now writes the region itself, in the order arv_camera_set_region() uses, reading each offset's bounds before writing the offset. On other cameras that is one extra read per offset. A camera with no offsets is also no longer asked for offset increments, which only ever logged errors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #981. Two commits: the first fixes what the adapter tells
Micro-Manager about the image it is producing, the second stops it assuming
every camera has every feature.
What it reports
GetROIreturned a garbage region. It assignedgxto bothxandy,and assigned both sizes to themselves. On a camera at 0,0 the first two values
happened to come out right, which is how this survived.
Dimensions and buffer size were only ever learned from a frame. Zero until
the first snap — MMCore sizes its circular buffer from them before that — and
stale after every change to the region or binning. Both now come from the
camera's region.
Initializealso took the geometry from the width and heightbounds rather than the region, so a camera opening on anything smaller than
its maximum described every image it produced with the wrong dimensions.
Mono12 reported a bit depth of 10.
RGB8 came out with red and blue exchanged, and the alpha byte was never
written. MM's RGB32 is BGRA and its documentation says alpha should be zero;
rgb_to_rgbacopied three bytes in source order and left the fourth holdingwhatever the buffer held last.
PixelTypecarried two vocabularies, and really three — the camera'sGenICam name at
Initialize, strings like"8bit mono"written from the imagepath, and a setter that drove the camera. Now two properties:
PixelFormat(writable, the camera's names, selects the format) and
PixelType(read-only,8bit/16bit/32bitRGB, describes the buffer). That also removes something#981 introduced, where the
"Unknown"marker reached the format setter and theadapter asked the camera for a pixel format called
Unknownonce per image.What it assumes
Every capability the adapter used to assume is now asked once at
Initialize.The region is two questions. Aravis' region-offset probe reports on
OffsetX/OffsetYonly, so a camera with a settable size and a fixed offset —or the reverse — was wrong in both directions.
WidthandHeightare nowasked for their GenICam access mode.
ClearROIreturns immediately on a fixedcamera, and no longer sets a 64×64 intermediate region first, which was
superstition (
arv_camera_set_regionalready zeroes the offsets) and failedoutright on any camera whose minimum width is above 64.
SetExposurecalledset_frame_rate(-1.0)on every write — an error everytime on a camera with no frame-rate control — and read frame-rate bounds it
never looked at.
The binning increment is read through a guard. It is the step of the loop
that builds the allowed-value list, so an increment the camera cannot report —
which comes back as zero — never terminated.
This branch originally also replaced the binning property limits with a single
allowed-value list. 5145ff8 landed the same fix first, arrived at
independently, so that part is gone and this branch keeps that commit's version
verbatim; the increment guard is all that remains in that block.
numImageswas ignored: asking for 8 frames delivered hundreds and thesequence never finished. The stream callback now counts and ends the sequence
itself — it cannot call
StopSequenceAcquisition, since unreffing the streamjoins the thread the callback runs on, so it stops the camera over the control
channel. It also stops on any
InsertImagefailure, which is what MMDevice.hasks and which covers
stopOnOverflow.A camera whose formats the adapter cannot decode used to open anyway,
reporting zero bytes per pixel, which MMCore turns into "memory requirements not
adequate" the first time live acquisition starts. It now moves to a format it
can decode when the camera offers one, and refuses to open when it does not,
naming the formats the camera offered. Dropped formats are logged instead of
silently omitted.
ClearROIandSetBinningreturnedDEVICE_OKwhatever happened. Bothreport now, and both error codes have text — a user whose camera is switched off
gets a sentence naming the camera id rather than
3141.The camera says who it is: vendor, model, serial number, device id and
firmware version as read-only properties.
Compatibility
Saved configurations that set
PixelTypeto an Aravis format name will nolonger apply — they need
PixelFormat.PixelTypeis read-only now, and itsvalues are the ones the other adapters use. This is the intended fix rather
than a side effect, but users will notice.
A camera offering no decodable format now fails to open where it previously
opened and then produced nothing. That is deliberate: the failure used to
surface later and somewhere else.
Testing
Eleven emulated camera profiles covering absent capabilities, mono depths, Bayer
and packed RGB, settable and fixed regions, binning, packed mono formats and
hardware triggering. Every check passes except
camera_formats_all_advertisedon the two packed profiles, which is the packed-format support that comes next.
Also run against
arv-fake-gv-camera, which ships with Aravis and has its ownGenICam XML — a camera implementation that is not mine, as a control.
Under AddressSanitizer: no errors, and no leak frame naming the adapter.
Not tested against physical hardware. The things I would most want a real
camera for are vendor GenICam quirks —
<pMax>,<pInvalidator>, features thatgo read-only during acquisition — and none of those are exercised here.