MMDevice/ImageMetadata.h defines the Metadata class and associated classes (MetadataSingleTag, MetadataKeyError, etc.).
Metadata is a somewhat confusing class because it's a key-value container, but the keys can be either (device label + tag) or just tag, except that half the time the device + tag is treated as a single string. And it appears to contain support for "array tags", which were never fully implemented (probably a good thing because I have issues with its design, but I digress). There is also a "read only" flag, also never fully supported.
It currently performs double duty:
-
It is the container for camera devices to pass per-frame metadata to the Core (in serialized form). It is also used internally in CCameraBase for the AddTag()/RemoveTag()/GetTags() mechanism used by Multi Camera et al.
-
It is the container for CMMCore to provide per-frame metadata for sequence acquisition images. In MMCoreJ it is more standard to use TaggedImage, but Metadata and related classes are also exposed and used in the MMCoreJ API. They are also exposed in the pymmcore API.
This double role makes it hard to make changes and improvements, because any change will potentially affect both camera device source compatibility (not the device interface version, any more) and the MMCore API at the same time. Also, MMDevice and MMCore could use different potential improvements. The fewer ways in which MMDevice constrains MMCore's API, the better, and the Metadata classes are the worst offender (the others are mostly just constants).
There are additional issues with the Metadata class being used in MMDevice:
-
Some of the functions throw exceptions. This is not an appropriate design for something used in device adapters. We get away with it because use cases that could cause errors is rare.
-
The license header says ImageMetadata.h is part of MMCore and is LGPL-licensed. This is purely an error we can correct, but betrays the origin of the code (explains why it's poorly matched to MMDevice's needs).
'Nuff said. So I propose that we move ImageMetadata.h back to MMCore, where it can continue to serve as an API type in MMCoreJ/pymmcore (and evolve or get deprecated as needed).
We would then need a replacement for MMDevice's purposes. In fact, I suggest we first add a replacement, then convert all cameras to use the replacement, then finally perform the moving of ImageMetadata.h.
The requirements for the replacement (let's call it MM::MetadataMap):
-
The class name should be different from Metadata to avoid confusion when deealing with old camera code (e.g., those not in our repos).
-
Provide straightforward replacements for all current and correct uses of Metadata by camera adapters
- Most cameras just add tags and serialize
-
Have no auxiliary classes (tags, exceptions) -- we don't need them in devices
-
Support serialization/deserialization compatible with the existing Metadata (so that we can migrate cameras without bumping the DIV)
-
Have unit tests, especially for the serialization/deserialization
- We'll start by adding unit tests for
Metadata's existing ser/des
Limiting the device-facing API to the absolute minimum will leave room for future improvements, including performance optimizations (some of which will require bumping the DIV; best to do that separately).
Here is what MetadataMap needs, I believe:
- Construct empty or copy of another instance; copy-assign
PutTag(name, deviceLabel, value)
- TBD: do we also need a version without
deviceLabel? Or only such a version? See below.
Serialize() (probably return internal const char *, not std::string)
Deserialize() (not needed by camera devices, but it's probably best if MMCore uses a paired implementation)
Clear() (strictly speaking optional, but reusing an existing instance could be good for performance so don't discourage it)
The following are probably NOT needed in the public API (some may be needed in CCameraBase's internal use):
GetKeys(), GetSingleTag()
RemoveTag()
- Everything else in
Metadata (currently not called by devices, even indirectly)
There are two camera adapters (Andor, TIScam) that call CCameraBase::GetTagKeys() and CCameraBase::GetTagValue() (both protected). But the use is probably unnecessary and should be removed after verification. (EDIT: #832)
Leaving out random-access read/remov leaves open the possibility of a single-buffer implementation that serializes as it goes -- a possible future performance optimization.
Most uses in device adapters look like the following and will convert easily:
Pattern 1 - MetadataSingleTag + SetTag:
Metadata md;
MetadataSingleTag mst(MM::g_Keyword_Elapsed_Time_ms, label, true);
mst.SetValue(CDeviceUtils::ConvertToString(elapsed));
md.SetTag(mst);
Pattern 2 - PutImageTag (edited to reflect #830):
md.PutImageTag(MM::g_Keyword_Elapsed_Time_ms, elapsed);
-- and it's at this point that I realize that cameras produce different metadata tags depending on whether they use Pattern 1 or 2 😱
I'll need to create a separate issue for this inconsistency. But this is precisely why we need a structure that can be evolved separately for devices vs MMCore API. (EDIT: #831, #833)
MMDevice/ImageMetadata.hdefines theMetadataclass and associated classes (MetadataSingleTag,MetadataKeyError, etc.).Metadatais a somewhat confusing class because it's a key-value container, but the keys can be either (device label + tag) or just tag, except that half the time the device + tag is treated as a single string. And it appears to contain support for "array tags", which were never fully implemented (probably a good thing because I have issues with its design, but I digress). There is also a "read only" flag, also never fully supported.It currently performs double duty:
It is the container for camera devices to pass per-frame metadata to the Core (in serialized form). It is also used internally in
CCameraBasefor theAddTag()/RemoveTag()/GetTags()mechanism used by Multi Camera et al.It is the container for CMMCore to provide per-frame metadata for sequence acquisition images. In MMCoreJ it is more standard to use
TaggedImage, butMetadataand related classes are also exposed and used in the MMCoreJ API. They are also exposed in the pymmcore API.This double role makes it hard to make changes and improvements, because any change will potentially affect both camera device source compatibility (not the device interface version, any more) and the MMCore API at the same time. Also, MMDevice and MMCore could use different potential improvements. The fewer ways in which MMDevice constrains MMCore's API, the better, and the
Metadataclasses are the worst offender (the others are mostly just constants).There are additional issues with the
Metadataclass being used in MMDevice:Some of the functions throw exceptions. This is not an appropriate design for something used in device adapters. We get away with it because use cases that could cause errors is rare.
The license header says
ImageMetadata.his part of MMCore and is LGPL-licensed. This is purely an error we can correct, but betrays the origin of the code (explains why it's poorly matched to MMDevice's needs).'Nuff said. So I propose that we move
ImageMetadata.hback to MMCore, where it can continue to serve as an API type in MMCoreJ/pymmcore (and evolve or get deprecated as needed).We would then need a replacement for MMDevice's purposes. In fact, I suggest we first add a replacement, then convert all cameras to use the replacement, then finally perform the moving of
ImageMetadata.h.The requirements for the replacement (let's call it
MM::MetadataMap):The class name should be different from
Metadatato avoid confusion when deealing with old camera code (e.g., those not in our repos).Provide straightforward replacements for all current and correct uses of
Metadataby camera adaptersHave no auxiliary classes (tags, exceptions) -- we don't need them in devices
Support serialization/deserialization compatible with the existing
Metadata(so that we can migrate cameras without bumping the DIV)Have unit tests, especially for the serialization/deserialization
Metadata's existing ser/desLimiting the device-facing API to the absolute minimum will leave room for future improvements, including performance optimizations (some of which will require bumping the DIV; best to do that separately).
Here is what
MetadataMapneeds, I believe:PutTag(name, deviceLabel, value)deviceLabel? Or only such a version? See below.Serialize()(probably return internalconst char *, notstd::string)Deserialize()(not needed by camera devices, but it's probably best if MMCore uses a paired implementation)Clear()(strictly speaking optional, but reusing an existing instance could be good for performance so don't discourage it)The following are probably NOT needed in the public API (some may be needed in CCameraBase's internal use):
GetKeys(),GetSingleTag()RemoveTag()Metadata(currently not called by devices, even indirectly)There are two camera adapters (
Andor,TIScam) that callCCameraBase::GetTagKeys()andCCameraBase::GetTagValue()(bothprotected). But the use is probably unnecessary and should be removed after verification. (EDIT: #832)Leaving out random-access read/remov leaves open the possibility of a single-buffer implementation that serializes as it goes -- a possible future performance optimization.
Most uses in device adapters look like the following and will convert easily:
Pattern 1 - MetadataSingleTag + SetTag:
Pattern 2 - PutImageTag (edited to reflect #830):
-- and it's at this point that I realize that cameras produce different metadata tags depending on whether they use Pattern 1 or 2 😱
I'll need to create a separate issue for this inconsistency. But this is precisely why we need a structure that can be evolved separately for devices vs MMCore API. (EDIT: #831, #833)