Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/release_notes/release_1.04.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* **Group UUID from the remote client**: `add_group` of the remote client (`sigima.client`) now returns the UUID of the created group, so that objects can be added to it with `group_id=...` (DataLab ≥ 1.4.0; older versions return `None`).

* **Pixel grid conversions**: New `ImageObj` methods convert between physical coordinates and pixel indices consistently with ROIs: `physical_to_pixel_range` and `pixel_range_to_physical` (rectangle and range of pixels), `axis_to_float_indices` and `axis_to_physical` (fractional indices, for uniform and non-uniform coordinates) and `get_roi_crop_indices` (part of the image holding a ROI). `transformer.transform_like_image` (`sigima.proc.image`) moves a geometry result as a flip, a transposition or a 90° or 270° rotation moves the pixels of the image.
* **Removal of file formats**: The I/O registries (`SignalIORegistry`, `ImageIORegistry`) have new `unregister_format` and `register_format` methods, so that a plugin defining file formats can remove them from Sigima when it is unloaded, and register them again, without accessing the registry internals.
* **NPZ object archives**: New functions `serialize_object_to_npz` and `deserialize_object_from_npz` (`sigima.io`) convert a signal or an image to and from an in-memory archive of NumPy arrays and JSON metadata, keeping labels, units, uncertainties, uniform or non-uniform image coordinates and object metadata, without ever pickling arrays. This is the format used by the Web API of DataLab to transfer objects, so that DataLab and its clients (such as DataLab-Kernel) share one implementation.

The [user guide](../user_guide/overview.rst) shows how readers and connectors attach experimental-source metadata, save and restore it, and send an acquisition to a DataLab group. Source recording is opt-in and does not make the experiment reproducible.
Expand Down
43 changes: 43 additions & 0 deletions sigima/io/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,49 @@ def get_formats(cls) -> list[FormatBase]:
"""
return cls._io_format_instances

@classmethod
def register_format(cls, format_class: type[FormatBase]) -> bool:
"""Register the handler of a format class, if it is not registered

Format classes are registered when they are defined: this method registers
again a format class removed with :meth:`unregister_format`.

Args:
format_class: format class of this registry

Returns:
True if the format was registered, False if it already was

Raises:
TypeError: if format_class is not a format class of this registry
"""
if not isinstance(format_class, cls):
raise TypeError(f"{format_class!r} is not a {cls.__name__} format class")
if any(fmt.__class__ is format_class for fmt in cls._io_format_instances):
return False
cls._io_format_instances.append(format_class())
return True

@classmethod
def unregister_format(cls, format_class: type[FormatBase]) -> bool:
"""Unregister the handler of a format class, e.g. when the plugin defining
it is unloaded

Handlers of subclasses of format_class are kept: they are formats of their
own.

Args:
format_class: format class of this registry

Returns:
True if the format was unregistered, False if it was not registered
"""
instances = cls._io_format_instances
kept = [fmt for fmt in instances if fmt.__class__ is not format_class]
removed = len(kept) != len(instances)
instances[:] = kept
return removed

@classmethod
def get_format_info(cls, mode: Literal["rst", "text"] = "rst") -> str:
"""Return I/O format info
Expand Down
23 changes: 23 additions & 0 deletions sigima/tests/io/addnewformat_unit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing import Type

import numpy as np
import pytest

from sigima.io import ImageIORegistry, SignalIORegistry
from sigima.io.base import FormatInfo
Expand Down Expand Up @@ -120,6 +121,28 @@ def test_add_signal_format() -> None:
assert finfo_str in SignalIORegistry.get_format_info(mode="text")


def test_unregister_and_register_format() -> None:
"""Test removing a format from the registry, then registering it again"""
signal_class = _add_signal_format()
image_class = _add_image_format()
for registry, fmt_class in (
(SignalIORegistry, signal_class),
(ImageIORegistry, image_class),
):
count = len(registry.get_formats())
assert registry.unregister_format(fmt_class)
assert len(registry.get_formats()) == count - 1
assert not any(fmt.__class__ is fmt_class for fmt in registry.get_formats())
assert not registry.unregister_format(fmt_class)
assert registry.register_format(fmt_class)
assert not registry.register_format(fmt_class)
assert sum(fmt.__class__ is fmt_class for fmt in registry.get_formats()) == 1
assert registry.unregister_format(fmt_class)
with pytest.raises(TypeError, match="is not a SignalIORegistry format class"):
SignalIORegistry.register_format(image_class)


if __name__ == "__main__":
test_add_image_format()
test_add_signal_format()
test_unregister_and_register_format()
Loading