Skip to content

Move ImageMetadata.h from MMDevice (back) to MMCore #797

@marktsuchida

Description

@marktsuchida

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions