Skip to content

himena.standards.model_meta

ArrayMeta

Preset for describing an array metadata.

Parameters:

Name Type Description Default
axes list[DimAxis] | None

Axes of the array.

None
current_indices tuple[Optional[int], ...] | None

Current slice indices to render the array in GUI.

None
selections list[tuple[tuple[int, int], tuple[int, int]]]

Selections of the array. This attribute should be any sliceable objects that can passed to the backend array object.

<dynamic>
unit str | None

Unit of the array values.

None
Source code in src\himena\standards\model_meta.py
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
class ArrayMeta(BaseMetadata):
    """Preset for describing an array metadata."""

    axes: list[DimAxis] | None = Field(None, description="Axes of the array.")
    current_indices: tuple[int | None, ...] | None = Field(
        None, description="Current slice indices to render the array in GUI."
    )
    selections: list[tuple[tuple[int, int], tuple[int, int]]] = Field(
        default_factory=list,
        description="Selections of the array. This attribute should be any sliceable "
        "objects that can passed to the backend array object.",
    )
    unit: str | None = Field(
        None,
        description="Unit of the array values.",
    )

    def without_selections(self) -> "ArrayMeta":
        """Make a copy of the metadata without selections."""
        return self.model_copy(update={"selections": []})

    def expected_type(self):
        return StandardType.ARRAY
without_selections()

Make a copy of the metadata without selections.

Source code in src\himena\standards\model_meta.py
221
222
223
def without_selections(self) -> "ArrayMeta":
    """Make a copy of the metadata without selections."""
    return self.model_copy(update={"selections": []})

DataFrameMeta

Preset for describing the metadata for a "dataframe" type.

Parameters:

Name Type Description Default
current_position list[int] | None

Current index position of (row, column) in the table.

None
selections list[tuple[tuple[int, int], tuple[int, int]]]

Table selections in the format of ((row_start, row_end), (col_start, col_end)), where the end index is exclusive, as is always the case for the Python indexing.

<dynamic>
separator str | None

Separator of the table.

None
transpose bool

Whether the table is transposed.

False
Source code in src\himena\standards\model_meta.py
59
60
61
62
63
64
65
66
67
class DataFrameMeta(TableMeta):
    """Preset for describing the metadata for a "dataframe" type."""

    transpose: bool = Field(
        default=False, description="Whether the table is transposed."
    )

    def expected_type(self):
        return StandardType.DATAFRAME

DataFramePlotMeta

Preset for describing the metadata for a "dataframe.plot" type.

Parameters:

Name Type Description Default
current_position list[int] | None

Current index position of (row, column) in the table.

None
selections list[tuple[tuple[int, int], tuple[int, int]]]

Table selections in the format of ((row_start, row_end), (col_start, col_end)), where the end index is exclusive, as is always the case for the Python indexing.

<dynamic>
separator str | None

Separator of the table.

None
transpose bool

Whether the table is transposed.

False
plot_type Literal[str, str]

Type of the plot.

'line'
plot_color_cycle list[str] | None

Color cycle of the plot.

None
plot_background_color str | None

Background color of the plot.

'#FFFFFF'
rois RoiListModel | Callable[list, RoiListModel]

Regions of interest.

<dynamic>
Source code in src\himena\standards\model_meta.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
class DataFramePlotMeta(DataFrameMeta):
    """Preset for describing the metadata for a "dataframe.plot" type."""

    model_config = {"arbitrary_types_allowed": True}

    plot_type: Literal["line", "scatter"] = Field(
        "line", description="Type of the plot."
    )
    plot_color_cycle: list[str] | None = Field(
        None, description="Color cycle of the plot."
    )
    plot_background_color: str | None = Field(
        "#FFFFFF", description="Background color of the plot."
    )
    rois: roi.RoiListModel | Callable[[], roi.RoiListModel] = Field(
        default_factory=roi.RoiListModel, description="Regions of interest."
    )

    @classmethod
    def from_metadata(cls, dir_path: Path) -> "DataFramePlotMeta":
        self = cls.model_validate_json(dir_path.joinpath(_META_NAME).read_text())
        if (rois_path := dir_path.joinpath("rois.roi.json")).exists():
            self.rois = roi.RoiListModel.model_validate_json(rois_path.read_text())
        return self

    def unwrap_rois(self) -> roi.RoiListModel:
        """Unwrap the lazy-evaluation of the ROIs."""
        if isinstance(self.rois, roi.RoiListModel):
            return self.rois
        self.rois = self.rois()
        return self.rois

    def write_metadata(self, dir_path: Path) -> None:
        dir_path.joinpath(_META_NAME).write_text(self.model_dump_json(exclude={"rois"}))
        rois = self.unwrap_rois()
        if len(rois) > 0:
            dir_path.joinpath("rois.roi.json").write_text(
                json.dumps(rois.model_dump_typed())
            )
        return None

    def expected_type(self):
        return StandardType.DATAFRAME_PLOT
unwrap_rois()

Unwrap the lazy-evaluation of the ROIs.

Source code in src\himena\standards\model_meta.py
114
115
116
117
118
119
def unwrap_rois(self) -> roi.RoiListModel:
    """Unwrap the lazy-evaluation of the ROIs."""
    if isinstance(self.rois, roi.RoiListModel):
        return self.rois
    self.rois = self.rois()
    return self.rois

DictMeta

Parameters:

Name Type Description Default
current_tab str | None

Current tab name.

None
Source code in src\himena\standards\model_meta.py
70
71
72
73
74
75
76
77
class DictMeta(BaseMetadata):
    current_tab: str | None = Field(None, description="Current tab name.")
    child_meta: dict[str, BaseMetadata] = Field(
        default_factory=dict, description="Metadata of the child models."
    )

    def expected_type(self):
        return StandardType.DICT

DimAxis

A dimension axis.

Parameters:

Name Type Description Default
name str

Name of the axis.

required
scale float

Pixel scale of the axis.

1.0
origin float

Offset of the axis.

0.0
unit str

Unit of the axis spacing.

''
labels list[str]

Category labels of the axis.

<dynamic>
default_label_format str

Default format of the labels.

'{:s}'
Source code in src\himena\standards\model_meta.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
class DimAxis(BaseModel):
    """A dimension axis."""

    name: str = Field(..., description="Name of the axis.")
    scale: float = Field(default=1.0, description="Pixel scale of the axis.")
    origin: float = Field(default=0.0, description="Offset of the axis.")
    unit: str = Field("", description="Unit of the axis spacing.")
    labels: list[str] = Field(
        default_factory=list, description="Category labels of the axis."
    )
    default_label_format: str = Field(
        "{:s}", description="Default format of the labels."
    )

    @field_validator("name", mode="before")
    def _name_to_str(cls, v):
        return str(v)

    @classmethod
    def parse(cls, obj) -> "DimAxis":
        if isinstance(obj, str):
            axis = DimAxis(name=obj)
        elif isinstance(obj, dict):
            axis = DimAxis(**obj)
        elif isinstance(obj, DimAxis):
            axis = obj
        else:
            raise TypeError(f"Cannot convert {type(obj)} to DimAxis.")
        return axis

    def get_label(self, index: int) -> str:
        """Return the label of the axis at the given index."""
        if index < 0:
            raise ValueError("Index must be non-negative.")
        try:
            return self.labels[index]
        except IndexError:
            return self.default_label_format.format(str(index))
get_label(index)

Return the label of the axis at the given index.

Source code in src\himena\standards\model_meta.py
183
184
185
186
187
188
189
190
def get_label(self, index: int) -> str:
    """Return the label of the axis at the given index."""
    if index < 0:
        raise ValueError("Index must be non-negative.")
    try:
        return self.labels[index]
    except IndexError:
        return self.default_label_format.format(str(index))

FunctionMeta

Preset for describing the metadata for a "function" type.

Parameters:

Name Type Description Default
source_code str | None

Source code of the function.

None
Source code in src\himena\standards\model_meta.py
80
81
82
83
84
85
86
class FunctionMeta(BaseMetadata):
    """Preset for describing the metadata for a "function" type."""

    source_code: str | None = Field(None, description="Source code of the function.")

    def expected_type(self):
        return StandardType.FUNCTION

ImageChannel

A channel in an image file.

Parameters:

Name Type Description Default
colormap str | None

Color map of the channel.

None
contrast_limits tuple[float, float] | None

Contrast limits of the channel.

None
visible bool

Whether the channel is visible.

True
Source code in src\himena\standards\model_meta.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
class ImageChannel(BaseModel):
    """A channel in an image file."""

    colormap: str | None = Field(None, description="Color map of the channel.")
    contrast_limits: tuple[float, float] | None = Field(
        None, description="Contrast limits of the channel."
    )
    visible: bool = Field(True, description="Whether the channel is visible.")

    @classmethod
    def default(cls) -> "ImageChannel":
        """Return a default channel (also used for mono-channel images)."""
        return cls(name=None, colormap="gray", contrast_limits=None)

    def with_colormap(self, colormap: str) -> "ImageChannel":
        """Set the colormap of the channel."""
        return self.model_copy(update={"colormap": colormap})
default() classmethod

Return a default channel (also used for mono-channel images).

Source code in src\himena\standards\model_meta.py
143
144
145
146
@classmethod
def default(cls) -> "ImageChannel":
    """Return a default channel (also used for mono-channel images)."""
    return cls(name=None, colormap="gray", contrast_limits=None)
with_colormap(colormap)

Set the colormap of the channel.

Source code in src\himena\standards\model_meta.py
148
149
150
def with_colormap(self, colormap: str) -> "ImageChannel":
    """Set the colormap of the channel."""
    return self.model_copy(update={"colormap": colormap})

ImageMeta

Preset for describing an image file metadata.

Parameters:

Name Type Description Default
axes list[DimAxis] | None

Axes of the array.

None
current_indices tuple[Optional[int], ...] | None

Current slice indices to render the array in GUI.

None
selections list[tuple[tuple[int, int], tuple[int, int]]]

Selections of the array. This attribute should be any sliceable objects that can passed to the backend array object.

<dynamic>
unit str | None

Unit of the array values.

None
channels list[ImageChannel]

Channels of the image. At least one channel is required.

[ImageChannel(colormap='gray', contrast_limits=None, visible=True)]
channel_axis int | None

Channel axis of the image.

None
is_rgb bool

Whether the image is RGB.

False
current_roi RoiModel | int | None

Current region of interest

None
current_roi_index int | None

Current index of the ROI in the rois, if applicable.

None
rois RoiListModel | Callable[list, RoiListModel]

Regions of interest.

<dynamic>
interpolation str | None

Interpolation method.

None
skip_image_rerendering bool

Skip image rerendering when the model is passed to the update_model method. This field is only used when a function does not touch the image data itself.

False
more_metadata Any | None

More metadata if exists.

None
Source code in src\himena\standards\model_meta.py
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
class ImageMeta(ArrayMeta):
    """Preset for describing an image file metadata."""

    model_config = {"arbitrary_types_allowed": True}

    channels: list[ImageChannel] = Field(
        default_factory=lambda: [ImageChannel.default()],
        description="Channels of the image. At least one channel is required.",
    )
    channel_axis: int | None = Field(None, description="Channel axis of the image.")
    is_rgb: bool = Field(False, description="Whether the image is RGB.")
    current_roi: roi.RoiModel | int | None = Field(
        None, description="Current region of interest"
    )
    current_roi_index: int | None = Field(
        None, description="Current index of the ROI in the `rois`, if applicable."
    )
    rois: roi.RoiListModel | Callable[[], roi.RoiListModel] = Field(
        default_factory=roi.RoiListModel, description="Regions of interest."
    )
    interpolation: str | None = Field(
        default=None,
        description="Interpolation method.",
    )
    skip_image_rerendering: bool = Field(
        default=False,
        description="Skip image rerendering when the model is passed to the `update_model` method. This field is only used when a function does not touch the image data itself.",
    )
    more_metadata: Any | None = Field(None, description="More metadata if exists.")

    def without_rois(self) -> "ImageMeta":
        return self.model_copy(update={"rois": roi.RoiListModel(), "current_roi": None})

    def get_one_axis(self, index: int, value: int) -> "ImageMeta":
        """Drop an axis by index for the array slicing arr[..., value, ...]."""
        if index < 0:
            index += len(self.axes)
        if index < 0 or index >= len(self.axes):
            raise IndexError(f"Invalid axis index: {index}.")
        axes = self.axes.copy()
        del axes[index]
        update = {"axes": axes, "rois": self.unwrap_rois().take_axis(index, value)}
        if (caxis := self.channel_axis) == index:
            update["channels"] = [self.channels[value]]
            update["channel_axis"] = None
            update["is_rgb"] = False
        elif caxis is not None:
            update["channel_axis"] = caxis - 1 if caxis > index else caxis
        return self.model_copy(update=update)

    def with_current_roi(self, roi: roi.RoiModel) -> "ImageMeta":
        """Set the current ROI."""
        update = {"current_roi": roi}
        if self.current_roi_index is not None:
            rois = self.unwrap_rois()
            rois.items[self.current_roi_index] = roi
            update["rois"] = rois
        return self.model_copy(update=update)

    def unwrap_rois(self) -> roi.RoiListModel:
        """Unwrap the lazy-evaluation of the ROIs."""
        if isinstance(self.rois, roi.RoiListModel):
            return self.rois
        self.rois = self.rois()
        return self.rois

    @field_validator("axes", mode="before")
    def _strings_to_axes(cls, v):
        if v is None:
            return None
        return [DimAxis.parse(axis) for axis in v]

    @field_validator("channel_axis")
    def _is_rgb_and_channels_exclusive(cls, v, values: "ValidationInfo"):
        if values.data.get("is_rgb") and v is not None:
            raise ValueError("Channel axis must be None for RGB images.")
        if v is None and len(values.data.get("channels", [])) > 1:
            raise ValueError("Channel axis is required for multi-channel images.")
        return v

    @field_validator("channels")
    def _channels_not_empty(cls, v, values: "ValidationInfo"):
        if not v:
            raise ValueError("At least one channel is required.")
        return v

    @property
    def contrast_limits(self) -> tuple[float, float] | None:
        """Return the contrast limits of the first visible channel."""
        return self.channels[0].contrast_limits

    @contrast_limits.setter
    def contrast_limits(self, value: tuple[float, float] | None):
        """Set the contrast limits of all channels."""
        for channel in self.channels:
            channel.contrast_limits = value

    @property
    def colormap(self) -> Any | None:
        """Return the colormap of the first visible channel."""
        return self.channels[0].colormap

    @colormap.setter
    def colormap(self, value: Any | None):
        """Set the colormap of all channels."""
        for channel in self.channels:
            channel.colormap = value

    @classmethod
    def from_metadata(cls, dir_path: Path) -> "ImageMeta":
        self = cls.model_validate_json(dir_path.joinpath(_META_NAME).read_text())
        if (rois_path := dir_path.joinpath(_ROIS)).exists():
            self.rois = roi.RoiListModel.model_validate_json(rois_path.read_text())
        if (cur_roi_path := dir_path.joinpath(_CURRENT_ROI)).exists():
            roi_js = json.loads(cur_roi_path.read_text())
            self.current_roi = roi.RoiModel.construct(roi_js.pop("type"), roi_js)
        if (more_meta_path := dir_path.joinpath(_MORE_META)).exists():
            with more_meta_path.open() as f:
                self.more_metadata = json.load(f)
        return self

    def write_metadata(self, dir_path: Path) -> None:
        dir_path.joinpath(_META_NAME).write_text(
            self.model_dump_json(
                exclude={"current_roi", "rois", "labels", "more_metadata"}
            )
        )
        rois = self.unwrap_rois()
        if isinstance(cur_roi := self.current_roi, roi.RoiModel):
            dir_path.joinpath(_CURRENT_ROI).write_text(
                json.dumps(cur_roi.model_dump_typed())
            )
        if len(rois) > 0:
            dir_path.joinpath(_ROIS).write_text(json.dumps(rois.model_dump_typed()))
        if (more_metadata := self.more_metadata) is not None:
            try:
                dir_path.joinpath(_MORE_META).write_text(json.dumps(more_metadata))
            except Exception as e:
                warnings.warn(
                    f"Failed to save `more_metadata`: {e}",
                    UserWarning,
                    stacklevel=2,
                )
        return None

    def expected_type(self):
        return StandardType.IMAGE
colormap property writable

Return the colormap of the first visible channel.

contrast_limits property writable

Return the contrast limits of the first visible channel.

get_one_axis(index, value)

Drop an axis by index for the array slicing arr[..., value, ...].

Source code in src\himena\standards\model_meta.py
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
def get_one_axis(self, index: int, value: int) -> "ImageMeta":
    """Drop an axis by index for the array slicing arr[..., value, ...]."""
    if index < 0:
        index += len(self.axes)
    if index < 0 or index >= len(self.axes):
        raise IndexError(f"Invalid axis index: {index}.")
    axes = self.axes.copy()
    del axes[index]
    update = {"axes": axes, "rois": self.unwrap_rois().take_axis(index, value)}
    if (caxis := self.channel_axis) == index:
        update["channels"] = [self.channels[value]]
        update["channel_axis"] = None
        update["is_rgb"] = False
    elif caxis is not None:
        update["channel_axis"] = caxis - 1 if caxis > index else caxis
    return self.model_copy(update=update)
unwrap_rois()

Unwrap the lazy-evaluation of the ROIs.

Source code in src\himena\standards\model_meta.py
288
289
290
291
292
293
def unwrap_rois(self) -> roi.RoiListModel:
    """Unwrap the lazy-evaluation of the ROIs."""
    if isinstance(self.rois, roi.RoiListModel):
        return self.rois
    self.rois = self.rois()
    return self.rois
with_current_roi(roi)

Set the current ROI.

Source code in src\himena\standards\model_meta.py
279
280
281
282
283
284
285
286
def with_current_roi(self, roi: roi.RoiModel) -> "ImageMeta":
    """Set the current ROI."""
    update = {"current_roi": roi}
    if self.current_roi_index is not None:
        rois = self.unwrap_rois()
        rois.items[self.current_roi_index] = roi
        update["rois"] = rois
    return self.model_copy(update=update)

ImageRoisMeta

Preset for describing an image-rois metadata.

Parameters:

Name Type Description Default
selections list[int]

Built-in mutable sequence.

If no argument is given, the constructor creates a new empty list. The argument must be an iterable if specified.

<dynamic>
axes list[DimAxis] | None

Axes of the ROIs.

None
Source code in src\himena\standards\model_meta.py
389
390
391
392
393
394
395
class ImageRoisMeta(ListMeta):
    """Preset for describing an image-rois metadata."""

    axes: list[DimAxis] | None = Field(None, description="Axes of the ROIs.")

    def expected_type(self):
        return StandardType.ROIS

ListMeta

Preset for describing a metadata for a list-like object.

Parameters:

Name Type Description Default
selections list[int]

Built-in mutable sequence.

If no argument is given, the constructor creates a new empty list. The argument must be an iterable if specified.

<dynamic>
Source code in src\himena\standards\model_meta.py
383
384
385
386
class ListMeta(BaseMetadata):
    """Preset for describing a metadata for a list-like object."""

    selections: list[int] = Field(default_factory=list)

TableMeta

Preset for describing the metadata for a "table" type.

Parameters:

Name Type Description Default
current_position list[int] | None

Current index position of (row, column) in the table.

None
selections list[tuple[tuple[int, int], tuple[int, int]]]

Table selections in the format of ((row_start, row_end), (col_start, col_end)), where the end index is exclusive, as is always the case for the Python indexing.

<dynamic>
separator str | None

Separator of the table.

None
Source code in src\himena\standards\model_meta.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class TableMeta(BaseMetadata):
    """Preset for describing the metadata for a "table" type."""

    current_position: list[int] | None = Field(
        default=None,
        description="Current index position of (row, column) in the table.",
    )
    selections: list[tuple[tuple[int, int], tuple[int, int]]] = Field(
        default_factory=list,
        description="Table selections in the format of ((row_start, row_end), (col_start, col_end)), where the end index is exclusive, as is always the case for the Python indexing.",
    )
    separator: str | None = Field(None, description="Separator of the table.")

    def expected_type(self):
        return StandardType.TABLE

TextMeta

Preset for describing the metadata for a "text" type.

Parameters:

Name Type Description Default
language str | None

Language of the text file.

None
spaces int

Number of spaces for indentation.

4
selection tuple[int, int] | None

Selection range.

None
font_family str | None

Font family.

None
font_size float

Font size.

10
encoding str | None

Encoding of the text file.

None
Source code in src\himena\standards\model_meta.py
28
29
30
31
32
33
34
35
36
37
38
39
class TextMeta(BaseMetadata):
    """Preset for describing the metadata for a "text" type."""

    language: str | None = Field(None, description="Language of the text file.")
    spaces: int = Field(4, description="Number of spaces for indentation.")
    selection: tuple[int, int] | None = Field(None, description="Selection range.")
    font_family: str | None = Field(None, description="Font family.")
    font_size: float = Field(10, description="Font size.")
    encoding: str | None = Field(None, description="Encoding of the text file.")

    def expected_type(self):
        return StandardType.TEXT