diff --git a/python-spec/src/somacore/coordinates.py b/python-spec/src/somacore/coordinates.py index 2703cb2..bf0d1c7 100644 --- a/python-spec/src/somacore/coordinates.py +++ b/python-spec/src/somacore/coordinates.py @@ -2,11 +2,13 @@ import abc import collections.abc -from typing import Optional, Sequence, Tuple, Union +import itertools +from typing import Iterable, Optional, Sequence, Tuple, Union import attrs import numpy as np import numpy.typing as npt +from typing_extensions import Self from .types import str_or_seq_length from .types import to_string_tuple @@ -37,6 +39,10 @@ class CoordinateSpace(collections.abc.Sequence[Axis]): axes: Tuple[Axis, ...] = attrs.field(converter=tuple) + @classmethod + def from_axis_names(cls, axis_names: Sequence[str,]) -> Self: + return cls(tuple(Axis(name) for name in axis_names)) # type: ignore[misc] + @axes.validator def _validate(self, _, axes: Tuple[Axis, ...]) -> None: if not axes: @@ -95,6 +101,25 @@ def _check_rmatmul_inner_axes(self, other: "CoordinateTransform"): f"{type(self).__name__}." ) + @abc.abstractmethod + def _contents_lines(self) -> Iterable[str]: + return + yield + + def _my_repr(self) -> Iterable[str]: + yield f"{type(self).__name__}" + yield f" input axes: {self._input_axes}" + yield f" output axes: {self._output_axes}" + + def __repr__(self) -> str: + content = self._contents_lines + lines = ( + self._my_repr() + if content is None + else itertools.chain(self._my_repr(), self._contents_lines()) + ) + return "<" + "\n".join(lines) + ">" + @abc.abstractmethod def __matmul__(self, other: object) -> "CoordinateTransform": raise NotImplementedError() @@ -185,6 +210,10 @@ def __init__( f"Unexpected shape {self._matrix.shape} for the input affine matrix." ) + def _contents_lines(self) -> Iterable[str]: + yield " augmented matrix:" + yield " " + str(self._matrix).replace("\n", "\n ") + def __matmul__(self, other: object) -> CoordinateTransform: if not isinstance(other, CoordinateTransform): raise NotImplementedError( @@ -259,6 +288,9 @@ def __init__( super().__init__(input_axes, output_axes, np.diag(self._scale_factors)) + def _contents_lines(self) -> Iterable[str]: + yield f" scales: {self._scale_factors}" + def __matmul__(self, other: object) -> CoordinateTransform: if not isinstance(other, CoordinateTransform): raise NotImplementedError( @@ -312,6 +344,9 @@ def __init__( rank = str_or_seq_length(input_axes) super().__init__(input_axes, output_axes, rank * [self._scale]) + def _contents_lines(self) -> Iterable[str]: + yield f" scale: {self._scale}" + def __matmul__(self, other: object) -> CoordinateTransform: if not isinstance(other, CoordinateTransform): raise NotImplementedError( @@ -361,6 +396,10 @@ def __init__( ): super().__init__(input_axes, output_axes, 1) + def _contents_lines(self) -> Iterable[str]: + return + yield + def __matmul__(self, other: object) -> CoordinateTransform: if not isinstance(other, CoordinateTransform): raise NotImplementedError( diff --git a/python-spec/testing/test_coordinates.py b/python-spec/testing/test_coordinates.py index 1bccb8a..e2cb2f7 100644 --- a/python-spec/testing/test_coordinates.py +++ b/python-spec/testing/test_coordinates.py @@ -39,6 +39,14 @@ def test_coordinate_space(): assert coord_space[0] == Axis("x", unit="nanometer") +def test_coordiante_space_from_axis_names(): + coord_space = CoordinateSpace.from_axis_names(["alpha", "beta"]) + assert len(coord_space) == 2 + assert coord_space.axis_names == ("alpha", "beta") + assert coord_space[0] == Axis("alpha", unit=None) + assert coord_space[1] == Axis("beta", unit=None) + + @pytest.mark.parametrize( ("input", "expected"), [