[ENH]: Improve Marker interface #477

Merged
synchon merged 6 commits from refactor/marker into main 2025-11-17 15:11:12 +00:00
30 changed files with 72 additions and 71 deletions

View file

@ -0,0 +1 @@
Improve ``Marker`` interface and implementations by `Synchon Mandal`_

View file

@ -6,10 +6,10 @@
fraimondo commented 2025-11-12 18:30:47 +00:00 (Migrated from github.com)

This is MRI-specific and should not be here.

This is MRI-specific and should not be here.
from abc import ABC, abstractmethod
from copy import deepcopy
from typing import Any, Optional, Union
from typing import Any, ClassVar, Optional, Union
from ..pipeline import PipelineStepMixin, UpdateMetaMixin
from ..typing import StorageLike
from ..typing import MarkerInOutMappings, StorageLike
from ..utils import logger, raise_error
@ -17,9 +17,9 @@ __all__ = ["BaseMarker"]
class BaseMarker(ABC, PipelineStepMixin, UpdateMetaMixin):
"""Abstract base class for all markers.
"""Abstract base class for marker.
For every interface that is required, one needs to provide a concrete
For every marker, one needs to provide a concrete
implementation of this abstract class.
Parameters
@ -34,12 +34,14 @@ class BaseMarker(ABC, PipelineStepMixin, UpdateMetaMixin):
Raises
------
AttributeError
If the marker does not have `_MARKER_INOUT_MAPPINGS` attribute.
If the marker does not have ``_MARKER_INOUT_MAPPINGS`` attribute.
ValueError
If required input data type(s) is(are) not found.
"""
_MARKER_INOUT_MAPPINGS: ClassVar[MarkerInOutMappings]
def __init__(
self,
on: Optional[Union[list[str], str]] = None,
@ -105,8 +107,8 @@ class BaseMarker(ABC, PipelineStepMixin, UpdateMetaMixin):
"""
return list(self._MARKER_INOUT_MAPPINGS.keys())
def get_output_type(self, input_type: str, output_feature: str) -> str:
"""Get output type.
def storage_type(self, input_type: str, output_feature: str) -> str:
"""Get storage type for a feature.
Parameters
----------
@ -172,7 +174,7 @@ class BaseMarker(ABC, PipelineStepMixin, UpdateMetaMixin):
The storage class, for example, SQLiteFeatureStorage.
"""
output_type_ = self.get_output_type(type_, feature)
output_type_ = self.storage_type(type_, feature)
logger.debug(f"Storing {output_type_} in {storage}")
storage.store(kind=output_type_, **out)

View file

@ -43,11 +43,11 @@ def test_compute() -> None:
assert feature_map["BOLD"]["complexity"]["data"].ndim == 2
def test_get_output_type() -> None:
"""Test HurstExponent get_output_type()."""
assert "vector" == HurstExponent(
parcellation=PARCELLATION
).get_output_type(input_type="BOLD", output_feature="complexity")
def test_storage_type() -> None:
"""Test HurstExponent storage_type."""
assert "vector" == HurstExponent(parcellation=PARCELLATION).storage_type(
input_type="BOLD", output_feature="complexity"
)
@pytest.mark.skipif(

View file

@ -42,11 +42,11 @@ def test_compute() -> None:
assert feature_map["BOLD"]["complexity"]["data"].ndim == 2
def test_get_output_type() -> None:
"""Test MultiscaleEntropyAUC get_output_type()."""
def test_storage_type() -> None:
"""Test MultiscaleEntropyAUC storage_type."""
assert "vector" == MultiscaleEntropyAUC(
parcellation=PARCELLATION
).get_output_type(input_type="BOLD", output_feature="complexity")
).storage_type(input_type="BOLD", output_feature="complexity")
@pytest.mark.skipif(

View file

@ -42,9 +42,9 @@ def test_compute() -> None:
assert feature_map["BOLD"]["complexity"]["data"].ndim == 2
def test_get_output_type() -> None:
"""Test PermEntropy get_output_type()."""
assert "vector" == PermEntropy(parcellation=PARCELLATION).get_output_type(
def test_storage_type() -> None:
"""Test PermEntropy storage_type."""
assert "vector" == PermEntropy(parcellation=PARCELLATION).storage_type(
input_type="BOLD", output_feature="complexity"
)

View file

@ -43,9 +43,9 @@ def test_compute() -> None:
assert feature_map["BOLD"]["complexity"]["data"].ndim == 2
def test_get_output_type() -> None:
"""Test RangeEntropy get_output_type()."""
assert "vector" == RangeEntropy(parcellation=PARCELLATION).get_output_type(
def test_storage_type() -> None:
"""Test RangeEntropy storage_type."""
assert "vector" == RangeEntropy(parcellation=PARCELLATION).storage_type(
input_type="BOLD", output_feature="complexity"
)

View file

@ -43,11 +43,11 @@ def test_compute() -> None:
assert feature_map["BOLD"]["complexity"]["data"].ndim == 2
def test_get_output_type() -> None:
"""Test RangeEntropyAUC get_output_type()."""
assert "vector" == RangeEntropyAUC(
parcellation=PARCELLATION
).get_output_type(input_type="BOLD", output_feature="complexity")
def test_storage_type() -> None:
"""Test RangeEntropyAUC storage_type."""
assert "vector" == RangeEntropyAUC(parcellation=PARCELLATION).storage_type(
input_type="BOLD", output_feature="complexity"
)
@pytest.mark.skipif(

View file

@ -42,11 +42,11 @@ def test_compute() -> None:
assert feature_map["BOLD"]["complexity"]["data"].ndim == 2
def test_get_output_type() -> None:
"""Test SampleEntropy get_output_type()."""
assert "vector" == SampleEntropy(
parcellation=PARCELLATION
).get_output_type(input_type="BOLD", output_feature="complexity")
def test_storage_type() -> None:
"""Test SampleEntropy storage_type."""
assert "vector" == SampleEntropy(parcellation=PARCELLATION).storage_type(
input_type="BOLD", output_feature="complexity"
)
@pytest.mark.skipif(

View file

@ -42,11 +42,11 @@ def test_compute() -> None:
assert feature_map["BOLD"]["complexity"]["data"].ndim == 2
def test_get_output_type() -> None:
"""Test WeightedPermEntropy get_output_type()."""
def test_storage_type() -> None:
"""Test WeightedPermEntropy storage_type."""
assert "vector" == WeightedPermEntropy(
parcellation=PARCELLATION
).get_output_type(input_type="BOLD", output_feature="complexity")
).storage_type(input_type="BOLD", output_feature="complexity")
@pytest.mark.skipif(

View file

@ -27,8 +27,8 @@ MAPS = "Smith_rsn_10"
"falff",
],
)
def test_ALFFMaps_get_output_type(feature: str) -> None:
"""Test ALFFMaps get_output_type().
def test_ALFFMaps_storage_type(feature: str) -> None:
"""Test ALFFMaps storage_type.
Parameters
----------
@ -39,7 +39,7 @@ def test_ALFFMaps_get_output_type(feature: str) -> None:
assert "vector" == ALFFMaps(
maps=MAPS,
using="junifer",
).get_output_type(input_type="BOLD", output_feature=feature)
).storage_type(input_type="BOLD", output_feature=feature)
def test_ALFFMaps(
@ -74,7 +74,7 @@ def test_ALFFMaps(
)
# Check correct output
for name in ["alff", "falff"]:
assert "vector" == marker.get_output_type(
assert "vector" == marker.storage_type(
input_type="BOLD", output_feature=name
)

View file

@ -28,8 +28,8 @@ PARCELLATION = "TianxS1x3TxMNInonlinear2009cAsym"
"falff",
],
)
def test_ALFFParcels_get_output_type(feature: str) -> None:
"""Test ALFFParcels get_output_type().
def test_ALFFParcels_storage_type(feature: str) -> None:
"""Test ALFFParcels storage_type.
Parameters
----------
@ -40,7 +40,7 @@ def test_ALFFParcels_get_output_type(feature: str) -> None:
assert "vector" == ALFFParcels(
parcellation=PARCELLATION,
using="junifer",
).get_output_type(input_type="BOLD", output_feature=feature)
).storage_type(input_type="BOLD", output_feature=feature)
def test_ALFFParcels(caplog: pytest.LogCaptureFixture, tmp_path: Path) -> None:

View file

@ -28,8 +28,8 @@ COORDINATES = "DMNBuckner"
"falff",
],
)
def test_ALFFSpheres_get_output_type(feature: str) -> None:
"""Test ALFFSpheres get_output_type().
def test_ALFFSpheres_storage_type(feature: str) -> None:
"""Test ALFFSpheres storage_type.
Parameters
----------
@ -40,7 +40,7 @@ def test_ALFFSpheres_get_output_type(feature: str) -> None:
assert "vector" == ALFFSpheres(
coords=COORDINATES,
using="junifer",
).get_output_type(input_type="BOLD", output_feature=feature)
).storage_type(input_type="BOLD", output_feature=feature)
def test_ALFFSpheres(caplog: pytest.LogCaptureFixture, tmp_path: Path) -> None:

View file

@ -31,13 +31,11 @@ def test_init() -> None:
)
def test_get_output_type() -> None:
"""Test CrossParcellationFC get_output_type()."""
def test_storage_type() -> None:
"""Test CrossParcellationFC storage_type."""
assert "matrix" == CrossParcellationFC(
parcellation_one=parcellation_one, parcellation_two=parcellation_two
).get_output_type(
input_type="BOLD", output_feature="functional_connectivity"
)
).storage_type(input_type="BOLD", output_feature="functional_connectivity")
@pytest.mark.skipif(

View file

@ -47,7 +47,7 @@ def test_EdgeCentricFCMaps(
conn_method_params=conn_method_params,
)
# Check correct output
assert "matrix" == marker.get_output_type(
assert "matrix" == marker.storage_type(
input_type="BOLD", output_feature="functional_connectivity"
)

View file

@ -45,7 +45,7 @@ def test_EdgeCentricFCParcels(
conn_method_params=conn_method_params,
)
# Check correct output
assert "matrix" == marker.get_output_type(
assert "matrix" == marker.storage_type(
input_type="BOLD", output_feature="functional_connectivity"
)

View file

@ -46,7 +46,7 @@ def test_EdgeCentricFCSpheres(
conn_method_params=conn_method_params,
)
# Check correct output
assert "matrix" == marker.get_output_type(
assert "matrix" == marker.storage_type(
input_type="BOLD", output_feature="functional_connectivity"
)

View file

@ -60,7 +60,7 @@ def test_FunctionalConnectivityMaps(
conn_method_params=conn_method_params,
)
# Check correct output
assert "matrix" == marker.get_output_type(
assert "matrix" == marker.storage_type(
input_type="BOLD", output_feature="functional_connectivity"
)

View file

@ -61,7 +61,7 @@ def test_FunctionalConnectivityParcels(
conn_method_params=conn_method_params,
)
# Check correct output
assert "matrix" == marker.get_output_type(
assert "matrix" == marker.storage_type(
input_type="BOLD", output_feature="functional_connectivity"
)

View file

@ -63,7 +63,7 @@ def test_FunctionalConnectivitySpheres(
conn_method_params=conn_method_params,
)
# Check correct output
assert "matrix" == marker.get_output_type(
assert "matrix" == marker.storage_type(
input_type="BOLD", output_feature="functional_connectivity"
)

View file

@ -48,7 +48,7 @@ def test_ReHoMaps(
using="junifer",
)
# Check correct output
assert "vector" == marker.get_output_type(
assert "vector" == marker.storage_type(
input_type="BOLD", output_feature="reho"
)

View file

@ -43,7 +43,7 @@ def test_ReHoParcels(caplog: pytest.LogCaptureFixture, tmp_path: Path) -> None:
using="junifer",
)
# Check correct output
assert "vector" == marker.get_output_type(
assert "vector" == marker.storage_type(
input_type="BOLD", output_feature="reho"
)

View file

@ -41,7 +41,7 @@ def test_ReHoSpheres(caplog: pytest.LogCaptureFixture, tmp_path: Path) -> None:
coords=COORDINATES, using="junifer", radius=10.0
)
# Check correct output
assert "vector" == marker.get_output_type(
assert "vector" == marker.storage_type(
input_type="BOLD", output_feature="reho"
)

View file

@ -27,7 +27,7 @@ def test_TemporalSNRMaps_computation(
element_data = DefaultDataReader().fit_transform(element)
marker = TemporalSNRMaps(maps="Smith_rsn_10")
# Check correct output
assert "vector" == marker.get_output_type(
assert "vector" == marker.storage_type(
input_type="BOLD", output_feature="tsnr"
)

View file

@ -20,7 +20,7 @@ def test_TemporalSNRParcels_computation() -> None:
parcellation="TianxS1x3TxMNInonlinear2009cAsym"
)
# Check correct output
assert "vector" == marker.get_output_type(
assert "vector" == marker.storage_type(
input_type="BOLD", output_feature="tsnr"
)

View file

@ -20,7 +20,7 @@ def test_TemporalSNRSpheres_computation() -> None:
element_data = DefaultDataReader().fit_transform(dg["sub001"])
marker = TemporalSNRSpheres(coords="DMNBuckner", radius=5.0)
# Check correct output
assert "vector" == marker.get_output_type(
assert "vector" == marker.storage_type(
input_type="BOLD", output_feature="tsnr"
)

View file

@ -22,8 +22,8 @@ from junifer.pipeline.utils import _check_freesurfer
("distances", "vector"),
],
)
def test_get_output_type(feature: str, storage_type: str) -> None:
"""Test BrainPrint get_output_type().
def test_storage_type(feature: str, storage_type: str) -> None:
"""Test BrainPrint storage_type.
Parameters
----------
@ -33,7 +33,7 @@ def test_get_output_type(feature: str, storage_type: str) -> None:
The parametrized storage type.
"""
assert storage_type == BrainPrint().get_output_type(
assert storage_type == BrainPrint().storage_type(
input_type="FreeSurfer", output_feature=feature
)

View file

@ -45,11 +45,11 @@ def test_compute() -> None:
assert extacted_timeseries.shape[0] == len(rss_ets["rss_ets"]["data"])
def test_get_output_type() -> None:
"""Test RSS ETS get_output_type()."""
def test_storage_type() -> None:
"""Test RSS ETS storage_type."""
assert "timeseries" == RSSETSMarker(
parcellation=PARCELLATION
).get_output_type(input_type="BOLD", output_feature="rss_ets")
).storage_type(input_type="BOLD", output_feature="rss_ets")
def test_store(tmp_path: Path) -> None:

View file

@ -74,7 +74,7 @@ def test_MapsAggregation_input_output(
"""
assert storage_type == MapsAggregation(
maps="Smith_rsn_10", on=input_type
).get_output_type(input_type=input_type, output_feature="aggregation")
).storage_type(input_type=input_type, output_feature="aggregation")
def test_MapsAggregation_3D(

View file

@ -79,7 +79,7 @@ def test_ParcelAggregation_input_output(
"""
assert storage_type == ParcelAggregation(
parcellation="Schaefer100x7", method="mean", on=input_type
).get_output_type(input_type=input_type, output_feature="aggregation")
).storage_type(input_type=input_type, output_feature="aggregation")
def test_ParcelAggregation_3D() -> None:

View file

@ -83,7 +83,7 @@ def test_SphereAggregation_input_output(
coords="DMNBuckner",
method="mean",
on=input_type,
).get_output_type(input_type=input_type, output_feature="aggregation")
).storage_type(input_type=input_type, output_feature="aggregation")
def test_SphereAggregation_3D() -> None: