From 78ec78d6d1910086995e4d336abc698f07d21e0d Mon Sep 17 00:00:00 2001 From: Xiao Gui Date: Wed, 30 Jul 2025 08:47:27 +0200 Subject: [PATCH 01/14] fix bug in regex --- siibra/explorer/url.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/siibra/explorer/url.py b/siibra/explorer/url.py index 4486e1087..ef6697715 100644 --- a/siibra/explorer/url.py +++ b/siibra/explorer/url.py @@ -117,7 +117,7 @@ def decode_url(url: str, vp_length=1000): except Exception as e: raise DecodeNavigationException from e - nav_match = re.search(r'/@:(?P.+)/?', url) + nav_match = re.search(r'/@:(?P[^/]+)/?', url) navigation_str = nav_match.group("navigation_str") for char in navigation_str: assert char in cipher or char in [neg, separator], f"char {char} not in cipher, nor separator/neg" From a8d7d5ade63b9b5966d71ee64f86e6c04acb0fe0 Mon Sep 17 00:00:00 2001 From: Xiao Gui Date: Fri, 8 Dec 2023 18:08:20 +0100 Subject: [PATCH 02/14] feat: control siibra-explorer --- siibra/explorer/README.md | 24 ++ .../broadcast/allRegions/request/__init__.py | 118 +++++++++ .../atlasSelected/request/__init__.py | 30 +++ .../parcellationSelected/request/__init__.py | 139 +++++++++++ .../regionsSelected/request/__init__.py | 118 +++++++++ .../templateSelected/request/__init__.py | 100 ++++++++ .../api/handshake/init/request/__init__.py | 20 ++ .../addAnnotations/request/__init__.py | 60 +++++ .../addAnnotations/response/__init__.py | 15 ++ .../request/cancelRequest/request/__init__.py | 21 ++ .../cancelRequest/response/__init__.py | 15 ++ .../api/request/exit/request/__init__.py | 26 ++ .../api/request/exit/response/__init__.py | 15 ++ .../request/getAllAtlases/request/__init__.py | 16 ++ .../getAllAtlases/response/__init__.py | 30 +++ .../request/__init__.py | 16 ++ .../response/__init__.py | 139 +++++++++++ .../getSupportedTemplates/request/__init__.py | 16 ++ .../response/__init__.py | 100 ++++++++ .../getUserToSelectARoi/request/__init__.py | 22 ++ .../getUserToSelectARoi/response/__init__.py | 29 +++ .../request/loadLayers/request/__init__.py | 30 +++ .../request/loadLayers/response/__init__.py | 15 ++ .../navigateTo/request/MainState___state.py | 24 ++ .../request/navigateTo/request/__init__.py | 23 ++ .../request/navigateTo/response/__init__.py | 15 ++ .../request/removeLayers/request/__init__.py | 26 ++ .../request/removeLayers/response/__init__.py | 15 ++ .../request/rmAnnotations/request/__init__.py | 26 ++ .../rmAnnotations/response/__init__.py | 15 ++ .../request/selectAtlas/request/__init__.py | 21 ++ .../request/selectAtlas/response/__init__.py | 15 ++ .../selectParcellation/request/__init__.py | 21 ++ .../selectParcellation/response/__init__.py | 15 ++ .../selectTemplate/request/__init__.py | 21 ++ .../selectTemplate/response/__init__.py | 15 ++ .../request/updateLayers/request/__init__.py | 30 +++ .../request/updateLayers/response/__init__.py | 15 ++ siibra/explorer/generate_dataclass.sh | 66 +++++ siibra/explorer/plugin.py | 227 ++++++++++++++++++ siibra/explorer/template.html | 82 +++++++ 41 files changed, 1786 insertions(+) create mode 100644 siibra/explorer/README.md create mode 100644 siibra/explorer/api/broadcast/allRegions/request/__init__.py create mode 100644 siibra/explorer/api/broadcast/atlasSelected/request/__init__.py create mode 100644 siibra/explorer/api/broadcast/parcellationSelected/request/__init__.py create mode 100644 siibra/explorer/api/broadcast/regionsSelected/request/__init__.py create mode 100644 siibra/explorer/api/broadcast/templateSelected/request/__init__.py create mode 100644 siibra/explorer/api/handshake/init/request/__init__.py create mode 100644 siibra/explorer/api/request/addAnnotations/request/__init__.py create mode 100644 siibra/explorer/api/request/addAnnotations/response/__init__.py create mode 100644 siibra/explorer/api/request/cancelRequest/request/__init__.py create mode 100644 siibra/explorer/api/request/cancelRequest/response/__init__.py create mode 100644 siibra/explorer/api/request/exit/request/__init__.py create mode 100644 siibra/explorer/api/request/exit/response/__init__.py create mode 100644 siibra/explorer/api/request/getAllAtlases/request/__init__.py create mode 100644 siibra/explorer/api/request/getAllAtlases/response/__init__.py create mode 100644 siibra/explorer/api/request/getSupportedParcellations/request/__init__.py create mode 100644 siibra/explorer/api/request/getSupportedParcellations/response/__init__.py create mode 100644 siibra/explorer/api/request/getSupportedTemplates/request/__init__.py create mode 100644 siibra/explorer/api/request/getSupportedTemplates/response/__init__.py create mode 100644 siibra/explorer/api/request/getUserToSelectARoi/request/__init__.py create mode 100644 siibra/explorer/api/request/getUserToSelectARoi/response/__init__.py create mode 100644 siibra/explorer/api/request/loadLayers/request/__init__.py create mode 100644 siibra/explorer/api/request/loadLayers/response/__init__.py create mode 100644 siibra/explorer/api/request/navigateTo/request/MainState___state.py create mode 100644 siibra/explorer/api/request/navigateTo/request/__init__.py create mode 100644 siibra/explorer/api/request/navigateTo/response/__init__.py create mode 100644 siibra/explorer/api/request/removeLayers/request/__init__.py create mode 100644 siibra/explorer/api/request/removeLayers/response/__init__.py create mode 100644 siibra/explorer/api/request/rmAnnotations/request/__init__.py create mode 100644 siibra/explorer/api/request/rmAnnotations/response/__init__.py create mode 100644 siibra/explorer/api/request/selectAtlas/request/__init__.py create mode 100644 siibra/explorer/api/request/selectAtlas/response/__init__.py create mode 100644 siibra/explorer/api/request/selectParcellation/request/__init__.py create mode 100644 siibra/explorer/api/request/selectParcellation/response/__init__.py create mode 100644 siibra/explorer/api/request/selectTemplate/request/__init__.py create mode 100644 siibra/explorer/api/request/selectTemplate/response/__init__.py create mode 100644 siibra/explorer/api/request/updateLayers/request/__init__.py create mode 100644 siibra/explorer/api/request/updateLayers/response/__init__.py create mode 100755 siibra/explorer/generate_dataclass.sh create mode 100644 siibra/explorer/plugin.py create mode 100644 siibra/explorer/template.html diff --git a/siibra/explorer/README.md b/siibra/explorer/README.md new file mode 100644 index 000000000..7fbf7bc58 --- /dev/null +++ b/siibra/explorer/README.md @@ -0,0 +1,24 @@ +# (experimental) Explorer extension + +```python +from siibra.explorer.plugin import Explorer +explorer = Explorer() +explorer.start() + +# Open the link specified in the console +# And click "OK" to open the companion plugin + +explorer.navigate(position=(1e7,1e7,1e7)) # in nm + +explorer.overlay(url="nifti://https://data-proxy.ebrains.eu/api/v1/public/buckets/d-d69b70e2-3002-4eaf-9c61-9c56f019bbc8/probabilistic_maps_pmaps_175areas/Area-hOc1/Area-hOc1_pmap_l_N10_nlin2ICBM152asym2009c_4.2_public_258e8c1d846f92be76922b20287344ae.nii.gz") # TODO a bit buggy, and does not yet work + +from siibra.locations import Point +import siibra + +p1 = Point([1,2,3], space=siibra.spaces['mni152']) +p2 = Point([2,3,4], space=siibra.spaces['mni152']) +ps = p1.union(p2) + +explorer.annotate(points=ps) # maximize perspective view for best effect + +``` \ No newline at end of file diff --git a/siibra/explorer/api/broadcast/allRegions/request/__init__.py b/siibra/explorer/api/broadcast/allRegions/request/__init__.py new file mode 100644 index 000000000..42d4afb06 --- /dev/null +++ b/siibra/explorer/api/broadcast/allRegions/request/__init__.py @@ -0,0 +1,118 @@ +# generated by datamodel-codegen: +# filename: sxplr.on.allRegions__fromSxplr__request.json +# timestamp: 2023-12-07T15:40:35+00:00 + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, List, Optional, Union + + +@dataclass +class VocabModel: + field_vocab: str + + +@dataclass +class QuantitativeOverlapItem: + value: float + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + typeOfUncertainty: Optional[Any] = None + uncertainty: Optional[List[float]] = None + unit: Optional[Any] = None + + +@dataclass +class QuantitativeOverlapItem1: + maxValue: float + minValue: float + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + maxValueUnit: Optional[Any] = None + minValueUnit: Optional[Any] = None + + +@dataclass +class ApiModelsOpenmindsSANDSV3AtlasParcellationEntityVersionCoordinates: + value: float + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + typeOfUncertainty: Optional[Any] = None + uncertainty: Optional[List[float]] = None + unit: Optional[Any] = None + + +@dataclass +class RelationAssessmentItem: + inRelationTo: Any + qualitativeOverlap: Any + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + criteria: Optional[Any] = None + + +@dataclass +class RelationAssessmentItem1: + inRelationTo: Any + quantitativeOverlap: Union[QuantitativeOverlapItem, QuantitativeOverlapItem1] + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + criteria: Optional[Any] = None + + +@dataclass +class BestViewPoint: + coordinateSpace: Any + coordinates: List[ + ApiModelsOpenmindsSANDSV3AtlasParcellationEntityVersionCoordinates + ] + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + + +@dataclass +class HasAnnotation: + criteriaQualityType: Any + internalIdentifier: str + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + bestViewPoint: Optional[BestViewPoint] = None + criteria: Optional[Any] = None + displayColor: Optional[str] = None + inspiredBy: Optional[List] = None + laterality: Optional[List] = None + visualizedIn: Optional[Any] = None + + +@dataclass +class PTRegion: + field_type: str + field_id: str + versionIdentifier: str + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + hasAnnotation: Optional[HasAnnotation] = None + hasParent: Optional[List] = None + lookupLabel: Optional[str] = None + name: Optional[str] = None + ontologyIdentifier: Optional[List[str]] = None + relationAssessment: Optional[ + Union[RelationAssessmentItem, RelationAssessmentItem1] + ] = None + versionInnovation: Optional[str] = None + + +@dataclass +class Model: + jsonrpc: str = '2.0' + method: str = 'sxplr.on.allRegions' + params: Optional[List[PTRegion]] = None diff --git a/siibra/explorer/api/broadcast/atlasSelected/request/__init__.py b/siibra/explorer/api/broadcast/atlasSelected/request/__init__.py new file mode 100644 index 000000000..307084daf --- /dev/null +++ b/siibra/explorer/api/broadcast/atlasSelected/request/__init__.py @@ -0,0 +1,30 @@ +# generated by datamodel-codegen: +# filename: sxplr.on.atlasSelected__fromSxplr__request.json +# timestamp: 2023-12-07T15:40:36+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import List, Optional + + +@dataclass +class SiibraAtIdModel: + field_id: str + + +@dataclass +class PTAtlas: + field_type: str + field_id: str + name: str + spaces: List[SiibraAtIdModel] + parcellations: List[SiibraAtIdModel] + species: str + + +@dataclass +class Model: + jsonrpc: str = '2.0' + method: str = 'sxplr.on.atlasSelected' + params: Optional[PTAtlas] = None diff --git a/siibra/explorer/api/broadcast/parcellationSelected/request/__init__.py b/siibra/explorer/api/broadcast/parcellationSelected/request/__init__.py new file mode 100644 index 000000000..3d03bb4ed --- /dev/null +++ b/siibra/explorer/api/broadcast/parcellationSelected/request/__init__.py @@ -0,0 +1,139 @@ +# generated by datamodel-codegen: +# filename: sxplr.on.parcellationSelected__fromSxplr__request.json +# timestamp: 2023-12-07T15:40:36+00:00 + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, Dict, List, Optional + + +@dataclass +class EbrainsDsPerson: + field_type: str + field_id: str + schema_org_shortName: str + identifier: str + shortName: str + name: str + + +@dataclass +class EbrainsDsUrl: + field_type: str + url: str + + +@dataclass +class SiibraAtIdModel: + field_id: str + + +@dataclass +class VocabModel: + field_vocab: str + + +@dataclass +class EbrainsDatasetModel: + field_type: str + field_id: str + name: str + urls: List[EbrainsDsUrl] + contributors: List[EbrainsDsPerson] + custodians: List[EbrainsDsPerson] + description: Optional[str] = None + ebrains_page: Optional[str] = None + + +@dataclass +class SiibraParcellationVersionModel: + field_type: str + name: str + deprecated: Optional[bool] = None + prev: Optional[SiibraAtIdModel] = None + next: Optional[SiibraAtIdModel] = None + + +@dataclass +class Copyright: + holder: List + year: str + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + + +@dataclass +class HasTerminologyVersion: + hasEntityVersion: List + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + definedIn: Optional[List] = None + ontologyIdentifier: Optional[List[str]] = None + + +@dataclass +class OtherContribution: + contributionType: List + contributor: Any + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + + +@dataclass +class BrainAtlasVersionModel: + field_type: str + field_id: str + accessibility: Dict[str, Any] + coordinateSpace: Dict[str, Any] + fullDocumentation: Dict[str, Any] + hasTerminologyVersion: HasTerminologyVersion + license: Dict[str, Any] + releaseDate: str + shortName: str + versionIdentifier: str + versionInnovation: str + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + abbreviation: Optional[str] = None + atlasType: Optional[Dict[str, Any]] = None + author: Optional[List] = None + copyright: Optional[Copyright] = None + custodian: Optional[List] = None + description: Optional[str] = None + digitalIdentifier: Optional[Dict[str, Any]] = None + fullName: Optional[str] = None + funding: Optional[List] = None + homepage: Optional[Dict[str, Any]] = None + howToCite: Optional[str] = None + isAlternativeVersionOf: Optional[List] = None + isNewVersionOf: Optional[Dict[str, Any]] = None + keyword: Optional[List] = None + ontologyIdentifier: Optional[List[str]] = None + otherContribution: Optional[OtherContribution] = None + relatedPublication: Optional[List] = None + repository: Optional[Dict[str, Any]] = None + supportChannel: Optional[List[str]] = None + + +@dataclass +class PTParcellation: + field_type: str + field_id: str + name: str + datasets: List[EbrainsDatasetModel] + brainAtlasVersions: List[BrainAtlasVersionModel] + modality: Optional[str] = None + version: Optional[SiibraParcellationVersionModel] = None + shortname: Optional[str] = None + + +@dataclass +class Model: + jsonrpc: str = '2.0' + method: str = 'sxplr.on.parcellationSelected' + params: Optional[PTParcellation] = None diff --git a/siibra/explorer/api/broadcast/regionsSelected/request/__init__.py b/siibra/explorer/api/broadcast/regionsSelected/request/__init__.py new file mode 100644 index 000000000..c1b3dd0ae --- /dev/null +++ b/siibra/explorer/api/broadcast/regionsSelected/request/__init__.py @@ -0,0 +1,118 @@ +# generated by datamodel-codegen: +# filename: sxplr.on.regionsSelected__fromSxplr__request.json +# timestamp: 2023-12-07T15:40:36+00:00 + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, List, Optional, Union + + +@dataclass +class VocabModel: + field_vocab: str + + +@dataclass +class QuantitativeOverlapItem: + value: float + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + typeOfUncertainty: Optional[Any] = None + uncertainty: Optional[List[float]] = None + unit: Optional[Any] = None + + +@dataclass +class QuantitativeOverlapItem1: + maxValue: float + minValue: float + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + maxValueUnit: Optional[Any] = None + minValueUnit: Optional[Any] = None + + +@dataclass +class ApiModelsOpenmindsSANDSV3AtlasParcellationEntityVersionCoordinates: + value: float + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + typeOfUncertainty: Optional[Any] = None + uncertainty: Optional[List[float]] = None + unit: Optional[Any] = None + + +@dataclass +class RelationAssessmentItem: + inRelationTo: Any + qualitativeOverlap: Any + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + criteria: Optional[Any] = None + + +@dataclass +class RelationAssessmentItem1: + inRelationTo: Any + quantitativeOverlap: Union[QuantitativeOverlapItem, QuantitativeOverlapItem1] + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + criteria: Optional[Any] = None + + +@dataclass +class BestViewPoint: + coordinateSpace: Any + coordinates: List[ + ApiModelsOpenmindsSANDSV3AtlasParcellationEntityVersionCoordinates + ] + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + + +@dataclass +class HasAnnotation: + criteriaQualityType: Any + internalIdentifier: str + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + bestViewPoint: Optional[BestViewPoint] = None + criteria: Optional[Any] = None + displayColor: Optional[str] = None + inspiredBy: Optional[List] = None + laterality: Optional[List] = None + visualizedIn: Optional[Any] = None + + +@dataclass +class PTRegion: + field_type: str + field_id: str + versionIdentifier: str + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + hasAnnotation: Optional[HasAnnotation] = None + hasParent: Optional[List] = None + lookupLabel: Optional[str] = None + name: Optional[str] = None + ontologyIdentifier: Optional[List[str]] = None + relationAssessment: Optional[ + Union[RelationAssessmentItem, RelationAssessmentItem1] + ] = None + versionInnovation: Optional[str] = None + + +@dataclass +class Model: + jsonrpc: str = '2.0' + method: str = 'sxplr.on.regionsSelected' + params: Optional[List[PTRegion]] = None diff --git a/siibra/explorer/api/broadcast/templateSelected/request/__init__.py b/siibra/explorer/api/broadcast/templateSelected/request/__init__.py new file mode 100644 index 000000000..401af0916 --- /dev/null +++ b/siibra/explorer/api/broadcast/templateSelected/request/__init__.py @@ -0,0 +1,100 @@ +# generated by datamodel-codegen: +# filename: sxplr.on.templateSelected__fromSxplr__request.json +# timestamp: 2023-12-07T15:40:37+00:00 + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, Dict, List, Optional, Union + + +@dataclass +class VocabModel: + field_vocab: str + + +@dataclass +class EbrainsDsPerson: + field_type: str + field_id: str + schema_org_shortName: str + identifier: str + shortName: str + name: str + + +@dataclass +class EbrainsDsUrl: + field_type: str + url: str + + +@dataclass +class SiibraAtIdModel: + field_id: str + + +@dataclass +class AxesOrigin: + value: float + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + typeOfUncertainty: Optional[Any] = None + uncertainty: Optional[List[float]] = None + unit: Optional[Any] = None + + +@dataclass +class EbrainsDatasetModel: + field_type: str + field_id: str + name: str + urls: List[EbrainsDsUrl] + contributors: List[EbrainsDsPerson] + custodians: List[EbrainsDsPerson] + description: Optional[str] = None + ebrains_page: Optional[str] = None + + +@dataclass +class VolumeModel: + field_type: str + name: str + formats: List[str] + providesMesh: bool + providesImage: bool + fragments: Dict[str, List[str]] + providedVolumes: Dict[str, Union[str, Dict[str, str]]] + space: SiibraAtIdModel + datasets: List[EbrainsDatasetModel] + variant: Optional[str] = None + + +@dataclass +class PTSpace: + field_type: str + field_id: str + anatomicalAxesOrientation: Dict[str, Any] + axesOrigin: List[AxesOrigin] + fullName: str + nativeUnit: Dict[str, Any] + releaseDate: str + shortName: str + versionIdentifier: str + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + defaultImage: Optional[List[VolumeModel]] = None + digitalIdentifier: Optional[Dict[str, Any]] = None + homepage: Optional[Dict[str, Any]] = None + howToCite: Optional[str] = None + ontologyIdentifier: Optional[List[str]] = None + datasets: Optional[List[EbrainsDatasetModel]] = None + + +@dataclass +class Model: + jsonrpc: str = '2.0' + method: str = 'sxplr.on.templateSelected' + params: Optional[PTSpace] = None diff --git a/siibra/explorer/api/handshake/init/request/__init__.py b/siibra/explorer/api/handshake/init/request/__init__.py new file mode 100644 index 000000000..324ddb447 --- /dev/null +++ b/siibra/explorer/api/handshake/init/request/__init__.py @@ -0,0 +1,20 @@ +# generated by datamodel-codegen: +# filename: sxplr.init__fromSxplr__request.json +# timestamp: 2023-12-07T15:40:35+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class Result: + name: Optional[str] = None + + +@dataclass +class Model: + jsonrpc: str = '2.0' + id: Optional[str] = None + result: Optional[Result] = None diff --git a/siibra/explorer/api/request/addAnnotations/request/__init__.py b/siibra/explorer/api/request/addAnnotations/request/__init__.py new file mode 100644 index 000000000..760cf154d --- /dev/null +++ b/siibra/explorer/api/request/addAnnotations/request/__init__.py @@ -0,0 +1,60 @@ +# generated by datamodel-codegen: +# filename: sxplr.addAnnotations__toSxplr__request.json +# timestamp: 2023-12-08T16:22:30+00:00 + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, Dict, List, Optional + + +@dataclass +class AtId: + field_id: Optional[str] = None + + +@dataclass +class VocabModel: + field_vocab: str + + +@dataclass +class ApiModelsOpenmindsSANDSV3MiscellaneousCoordinatePointCoordinates: + value: float + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + typeOfUncertainty: Optional[Any] = None + uncertainty: Optional[List[float]] = None + unit: Optional[Any] = None + + +@dataclass +class CoordinatePointModel: + field_type: str + field_id: str + coordinateSpace: Dict[str, Any] + coordinates: List[ApiModelsOpenmindsSANDSV3MiscellaneousCoordinatePointCoordinates] + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + + +@dataclass +class SxplrCoordinatePointExtension(AtId): + name: Optional[str] = None + color: Optional[str] = None + openminds: Optional[CoordinatePointModel] = None + + +@dataclass +class Params: + annotations: Optional[List[SxplrCoordinatePointExtension]] = None + + +@dataclass +class Model: + id: Optional[str] = None + jsonrpc: str = '2.0' + method: str = 'sxplr.addAnnotations' + params: Optional[Params] = None diff --git a/siibra/explorer/api/request/addAnnotations/response/__init__.py b/siibra/explorer/api/request/addAnnotations/response/__init__.py new file mode 100644 index 000000000..280f80d2c --- /dev/null +++ b/siibra/explorer/api/request/addAnnotations/response/__init__.py @@ -0,0 +1,15 @@ +# generated by datamodel-codegen: +# filename: sxplr.addAnnotations__toSxplr__response.json +# timestamp: 2023-12-07T15:40:42+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class Model: + jsonrpc: str = '2.0' + id: Optional[str] = None + result: str = 'OK' diff --git a/siibra/explorer/api/request/cancelRequest/request/__init__.py b/siibra/explorer/api/request/cancelRequest/request/__init__.py new file mode 100644 index 000000000..394c3cff6 --- /dev/null +++ b/siibra/explorer/api/request/cancelRequest/request/__init__.py @@ -0,0 +1,21 @@ +# generated by datamodel-codegen: +# filename: sxplr.cancelRequest__toSxplr__request.json +# timestamp: 2023-12-07T15:40:41+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class Params: + id: Optional[str] = None + + +@dataclass +class Model: + id: Optional[str] = None + jsonrpc: str = '2.0' + method: str = 'sxplr.cancelRequest' + params: Optional[Params] = None diff --git a/siibra/explorer/api/request/cancelRequest/response/__init__.py b/siibra/explorer/api/request/cancelRequest/response/__init__.py new file mode 100644 index 000000000..cbd8bbec7 --- /dev/null +++ b/siibra/explorer/api/request/cancelRequest/response/__init__.py @@ -0,0 +1,15 @@ +# generated by datamodel-codegen: +# filename: sxplr.cancelRequest__toSxplr__response.json +# timestamp: 2023-12-07T15:40:44+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class Model: + jsonrpc: str = '2.0' + id: Optional[str] = None + result: str = 'OK' diff --git a/siibra/explorer/api/request/exit/request/__init__.py b/siibra/explorer/api/request/exit/request/__init__.py new file mode 100644 index 000000000..b9f7c6714 --- /dev/null +++ b/siibra/explorer/api/request/exit/request/__init__.py @@ -0,0 +1,26 @@ +# generated by datamodel-codegen: +# filename: sxplr.exit__toSxplr__request.json +# timestamp: 2023-12-07T15:40:44+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import List, Optional + + +@dataclass +class JRPCRequest: + id: Optional[str] = None + + +@dataclass +class Params: + requests: Optional[List[JRPCRequest]] = None + + +@dataclass +class Model: + id: Optional[str] = None + jsonrpc: str = '2.0' + method: str = 'sxplr.exit' + params: Optional[Params] = None diff --git a/siibra/explorer/api/request/exit/response/__init__.py b/siibra/explorer/api/request/exit/response/__init__.py new file mode 100644 index 000000000..50a41aba4 --- /dev/null +++ b/siibra/explorer/api/request/exit/response/__init__.py @@ -0,0 +1,15 @@ +# generated by datamodel-codegen: +# filename: sxplr.exit__toSxplr__response.json +# timestamp: 2023-12-07T15:40:43+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class Model: + jsonrpc: str = '2.0' + id: Optional[str] = None + result: str = 'OK' diff --git a/siibra/explorer/api/request/getAllAtlases/request/__init__.py b/siibra/explorer/api/request/getAllAtlases/request/__init__.py new file mode 100644 index 000000000..58d24a7d9 --- /dev/null +++ b/siibra/explorer/api/request/getAllAtlases/request/__init__.py @@ -0,0 +1,16 @@ +# generated by datamodel-codegen: +# filename: sxplr.getAllAtlases__toSxplr__request.json +# timestamp: 2023-12-07T15:40:41+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Optional + + +@dataclass +class Model: + id: Optional[str] = None + jsonrpc: str = '2.0' + method: str = 'sxplr.getAllAtlases' + params: Any = None diff --git a/siibra/explorer/api/request/getAllAtlases/response/__init__.py b/siibra/explorer/api/request/getAllAtlases/response/__init__.py new file mode 100644 index 000000000..0422dc7ed --- /dev/null +++ b/siibra/explorer/api/request/getAllAtlases/response/__init__.py @@ -0,0 +1,30 @@ +# generated by datamodel-codegen: +# filename: sxplr.getAllAtlases__toSxplr__response.json +# timestamp: 2023-12-07T15:40:42+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import List, Optional + + +@dataclass +class SiibraAtIdModel: + field_id: str + + +@dataclass +class PTAtlas: + field_type: str + field_id: str + name: str + spaces: List[SiibraAtIdModel] + parcellations: List[SiibraAtIdModel] + species: str + + +@dataclass +class Model: + jsonrpc: str = '2.0' + id: Optional[str] = None + result: Optional[List[PTAtlas]] = None diff --git a/siibra/explorer/api/request/getSupportedParcellations/request/__init__.py b/siibra/explorer/api/request/getSupportedParcellations/request/__init__.py new file mode 100644 index 000000000..b5c508ada --- /dev/null +++ b/siibra/explorer/api/request/getSupportedParcellations/request/__init__.py @@ -0,0 +1,16 @@ +# generated by datamodel-codegen: +# filename: sxplr.getSupportedParcellations__toSxplr__request.json +# timestamp: 2023-12-07T15:40:40+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Optional + + +@dataclass +class Model: + id: Optional[str] = None + jsonrpc: str = '2.0' + method: str = 'sxplr.getSupportedParcellations' + params: Any = None diff --git a/siibra/explorer/api/request/getSupportedParcellations/response/__init__.py b/siibra/explorer/api/request/getSupportedParcellations/response/__init__.py new file mode 100644 index 000000000..11ddb5087 --- /dev/null +++ b/siibra/explorer/api/request/getSupportedParcellations/response/__init__.py @@ -0,0 +1,139 @@ +# generated by datamodel-codegen: +# filename: sxplr.getSupportedParcellations__toSxplr__response.json +# timestamp: 2023-12-07T15:40:41+00:00 + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, Dict, List, Optional + + +@dataclass +class EbrainsDsPerson: + field_type: str + field_id: str + schema_org_shortName: str + identifier: str + shortName: str + name: str + + +@dataclass +class EbrainsDsUrl: + field_type: str + url: str + + +@dataclass +class SiibraAtIdModel: + field_id: str + + +@dataclass +class VocabModel: + field_vocab: str + + +@dataclass +class EbrainsDatasetModel: + field_type: str + field_id: str + name: str + urls: List[EbrainsDsUrl] + contributors: List[EbrainsDsPerson] + custodians: List[EbrainsDsPerson] + description: Optional[str] = None + ebrains_page: Optional[str] = None + + +@dataclass +class SiibraParcellationVersionModel: + field_type: str + name: str + deprecated: Optional[bool] = None + prev: Optional[SiibraAtIdModel] = None + next: Optional[SiibraAtIdModel] = None + + +@dataclass +class Copyright: + holder: List + year: str + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + + +@dataclass +class HasTerminologyVersion: + hasEntityVersion: List + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + definedIn: Optional[List] = None + ontologyIdentifier: Optional[List[str]] = None + + +@dataclass +class OtherContribution: + contributionType: List + contributor: Any + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + + +@dataclass +class BrainAtlasVersionModel: + field_type: str + field_id: str + accessibility: Dict[str, Any] + coordinateSpace: Dict[str, Any] + fullDocumentation: Dict[str, Any] + hasTerminologyVersion: HasTerminologyVersion + license: Dict[str, Any] + releaseDate: str + shortName: str + versionIdentifier: str + versionInnovation: str + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + abbreviation: Optional[str] = None + atlasType: Optional[Dict[str, Any]] = None + author: Optional[List] = None + copyright: Optional[Copyright] = None + custodian: Optional[List] = None + description: Optional[str] = None + digitalIdentifier: Optional[Dict[str, Any]] = None + fullName: Optional[str] = None + funding: Optional[List] = None + homepage: Optional[Dict[str, Any]] = None + howToCite: Optional[str] = None + isAlternativeVersionOf: Optional[List] = None + isNewVersionOf: Optional[Dict[str, Any]] = None + keyword: Optional[List] = None + ontologyIdentifier: Optional[List[str]] = None + otherContribution: Optional[OtherContribution] = None + relatedPublication: Optional[List] = None + repository: Optional[Dict[str, Any]] = None + supportChannel: Optional[List[str]] = None + + +@dataclass +class PTParcellation: + field_type: str + field_id: str + name: str + datasets: List[EbrainsDatasetModel] + brainAtlasVersions: List[BrainAtlasVersionModel] + modality: Optional[str] = None + version: Optional[SiibraParcellationVersionModel] = None + shortname: Optional[str] = None + + +@dataclass +class Model: + jsonrpc: str = '2.0' + id: Optional[str] = None + result: Optional[List[PTParcellation]] = None diff --git a/siibra/explorer/api/request/getSupportedTemplates/request/__init__.py b/siibra/explorer/api/request/getSupportedTemplates/request/__init__.py new file mode 100644 index 000000000..a0f62a8f3 --- /dev/null +++ b/siibra/explorer/api/request/getSupportedTemplates/request/__init__.py @@ -0,0 +1,16 @@ +# generated by datamodel-codegen: +# filename: sxplr.getSupportedTemplates__toSxplr__request.json +# timestamp: 2023-12-07T15:40:43+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Optional + + +@dataclass +class Model: + id: Optional[str] = None + jsonrpc: str = '2.0' + method: str = 'sxplr.getSupportedTemplates' + params: Any = None diff --git a/siibra/explorer/api/request/getSupportedTemplates/response/__init__.py b/siibra/explorer/api/request/getSupportedTemplates/response/__init__.py new file mode 100644 index 000000000..9ceac6483 --- /dev/null +++ b/siibra/explorer/api/request/getSupportedTemplates/response/__init__.py @@ -0,0 +1,100 @@ +# generated by datamodel-codegen: +# filename: sxplr.getSupportedTemplates__toSxplr__response.json +# timestamp: 2023-12-07T15:40:37+00:00 + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, Dict, List, Optional, Union + + +@dataclass +class VocabModel: + field_vocab: str + + +@dataclass +class EbrainsDsPerson: + field_type: str + field_id: str + schema_org_shortName: str + identifier: str + shortName: str + name: str + + +@dataclass +class EbrainsDsUrl: + field_type: str + url: str + + +@dataclass +class SiibraAtIdModel: + field_id: str + + +@dataclass +class AxesOrigin: + value: float + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + typeOfUncertainty: Optional[Any] = None + uncertainty: Optional[List[float]] = None + unit: Optional[Any] = None + + +@dataclass +class EbrainsDatasetModel: + field_type: str + field_id: str + name: str + urls: List[EbrainsDsUrl] + contributors: List[EbrainsDsPerson] + custodians: List[EbrainsDsPerson] + description: Optional[str] = None + ebrains_page: Optional[str] = None + + +@dataclass +class VolumeModel: + field_type: str + name: str + formats: List[str] + providesMesh: bool + providesImage: bool + fragments: Dict[str, List[str]] + providedVolumes: Dict[str, Union[str, Dict[str, str]]] + space: SiibraAtIdModel + datasets: List[EbrainsDatasetModel] + variant: Optional[str] = None + + +@dataclass +class PTSpace: + field_type: str + field_id: str + anatomicalAxesOrientation: Dict[str, Any] + axesOrigin: List[AxesOrigin] + fullName: str + nativeUnit: Dict[str, Any] + releaseDate: str + shortName: str + versionIdentifier: str + field_context: Optional[VocabModel] = field( + default_factory=lambda: {'@vocab': 'https://openminds.ebrains.eu/vocab/'} + ) + defaultImage: Optional[List[VolumeModel]] = None + digitalIdentifier: Optional[Dict[str, Any]] = None + homepage: Optional[Dict[str, Any]] = None + howToCite: Optional[str] = None + ontologyIdentifier: Optional[List[str]] = None + datasets: Optional[List[EbrainsDatasetModel]] = None + + +@dataclass +class Model: + jsonrpc: str = '2.0' + id: Optional[str] = None + result: Optional[List[PTSpace]] = None diff --git a/siibra/explorer/api/request/getUserToSelectARoi/request/__init__.py b/siibra/explorer/api/request/getUserToSelectARoi/request/__init__.py new file mode 100644 index 000000000..d722c7e80 --- /dev/null +++ b/siibra/explorer/api/request/getUserToSelectARoi/request/__init__.py @@ -0,0 +1,22 @@ +# generated by datamodel-codegen: +# filename: sxplr.getUserToSelectARoi__toSxplr__request.json +# timestamp: 2023-12-07T15:40:40+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Optional + + +@dataclass +class Params: + type: Optional[Any] = None + message: Optional[str] = None + + +@dataclass +class Model: + id: Optional[str] = None + jsonrpc: str = '2.0' + method: str = 'sxplr.getUserToSelectARoi' + params: Optional[Params] = None diff --git a/siibra/explorer/api/request/getUserToSelectARoi/response/__init__.py b/siibra/explorer/api/request/getUserToSelectARoi/response/__init__.py new file mode 100644 index 000000000..bedee2811 --- /dev/null +++ b/siibra/explorer/api/request/getUserToSelectARoi/response/__init__.py @@ -0,0 +1,29 @@ +# generated by datamodel-codegen: +# filename: sxplr.getUserToSelectARoi__toSxplr__response.json +# timestamp: 2023-12-07T15:40:45+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, List, Optional, Union + + +@dataclass +class Space: + id: Optional[str] = None + + +@dataclass +class Point: + loc: Optional[List[float]] = None + space: Optional[Space] = None + + +PTRegion = Any + + +@dataclass +class Model: + jsonrpc: str = '2.0' + id: Optional[str] = None + result: Optional[Union[PTRegion, Point]] = None diff --git a/siibra/explorer/api/request/loadLayers/request/__init__.py b/siibra/explorer/api/request/loadLayers/request/__init__.py new file mode 100644 index 000000000..087108cb9 --- /dev/null +++ b/siibra/explorer/api/request/loadLayers/request/__init__.py @@ -0,0 +1,30 @@ +# generated by datamodel-codegen: +# filename: sxplr.loadLayers__toSxplr__request.json +# timestamp: 2023-12-07T15:40:45+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import List, Optional + +Len4num = List[float] + + +@dataclass +class AddableLayer: + source: Optional[str] = None + shader: Optional[str] = None + transform: Optional[List[Len4num]] = None + + +@dataclass +class Params: + layers: Optional[List[AddableLayer]] = None + + +@dataclass +class Model: + id: Optional[str] = None + jsonrpc: str = '2.0' + method: str = 'sxplr.loadLayers' + params: Optional[Params] = None diff --git a/siibra/explorer/api/request/loadLayers/response/__init__.py b/siibra/explorer/api/request/loadLayers/response/__init__.py new file mode 100644 index 000000000..b63edf56a --- /dev/null +++ b/siibra/explorer/api/request/loadLayers/response/__init__.py @@ -0,0 +1,15 @@ +# generated by datamodel-codegen: +# filename: sxplr.loadLayers__toSxplr__response.json +# timestamp: 2023-12-07T15:40:39+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class Model: + jsonrpc: str = '2.0' + id: Optional[str] = None + result: str = 'OK' diff --git a/siibra/explorer/api/request/navigateTo/request/MainState___state.py b/siibra/explorer/api/request/navigateTo/request/MainState___state.py new file mode 100644 index 000000000..521099dab --- /dev/null +++ b/siibra/explorer/api/request/navigateTo/request/MainState___state.py @@ -0,0 +1,24 @@ +# generated by datamodel-codegen: +# filename: sxplr.navigateTo__toSxplr__request.json +# timestamp: 2023-12-07T15:40:46+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + +from typing import List, Optional + + +@dataclass +class AtlasSelectionNavigation: + position: Optional[Len3num] = None + orientation: Optional[Len4num] = None + zoom: Optional[float] = None + perspectiveOrientation: Optional[Len4num] = None + perspectiveZoom: Optional[float] = None + +Len3num = List[float] + + +Len4num = List[float] diff --git a/siibra/explorer/api/request/navigateTo/request/__init__.py b/siibra/explorer/api/request/navigateTo/request/__init__.py new file mode 100644 index 000000000..e32d764f3 --- /dev/null +++ b/siibra/explorer/api/request/navigateTo/request/__init__.py @@ -0,0 +1,23 @@ +# generated by datamodel-codegen: +# filename: sxplr.navigateTo__toSxplr__request.json +# timestamp: 2023-12-07T15:40:46+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import List, Optional + +from .MainState___state import AtlasSelectionNavigation + + +@dataclass +class Params(AtlasSelectionNavigation): + animate: Optional[bool] = None + + +@dataclass +class Model: + id: Optional[str] = None + jsonrpc: str = '2.0' + method: str = 'sxplr.navigateTo' + params: Optional[Params] = None diff --git a/siibra/explorer/api/request/navigateTo/response/__init__.py b/siibra/explorer/api/request/navigateTo/response/__init__.py new file mode 100644 index 000000000..009f48e19 --- /dev/null +++ b/siibra/explorer/api/request/navigateTo/response/__init__.py @@ -0,0 +1,15 @@ +# generated by datamodel-codegen: +# filename: sxplr.navigateTo__toSxplr__response.json +# timestamp: 2023-12-07T15:40:45+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class Model: + jsonrpc: str = '2.0' + id: Optional[str] = None + result: str = 'OK' diff --git a/siibra/explorer/api/request/removeLayers/request/__init__.py b/siibra/explorer/api/request/removeLayers/request/__init__.py new file mode 100644 index 000000000..f79c56170 --- /dev/null +++ b/siibra/explorer/api/request/removeLayers/request/__init__.py @@ -0,0 +1,26 @@ +# generated by datamodel-codegen: +# filename: sxplr.removeLayers__toSxplr__request.json +# timestamp: 2023-12-07T15:40:38+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import List, Optional + + +@dataclass +class Layer: + id: Optional[str] = None + + +@dataclass +class Params: + layers: Optional[List[Layer]] = None + + +@dataclass +class Model: + id: Optional[str] = None + jsonrpc: str = '2.0' + method: str = 'sxplr.removeLayers' + params: Optional[Params] = None diff --git a/siibra/explorer/api/request/removeLayers/response/__init__.py b/siibra/explorer/api/request/removeLayers/response/__init__.py new file mode 100644 index 000000000..af057f095 --- /dev/null +++ b/siibra/explorer/api/request/removeLayers/response/__init__.py @@ -0,0 +1,15 @@ +# generated by datamodel-codegen: +# filename: sxplr.removeLayers__toSxplr__response.json +# timestamp: 2023-12-07T15:40:39+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class Model: + jsonrpc: str = '2.0' + id: Optional[str] = None + result: str = 'OK' diff --git a/siibra/explorer/api/request/rmAnnotations/request/__init__.py b/siibra/explorer/api/request/rmAnnotations/request/__init__.py new file mode 100644 index 000000000..23649ec7e --- /dev/null +++ b/siibra/explorer/api/request/rmAnnotations/request/__init__.py @@ -0,0 +1,26 @@ +# generated by datamodel-codegen: +# filename: sxplr.rmAnnotations__toSxplr__request.json +# timestamp: 2023-12-07T15:40:39+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import List, Optional + + +@dataclass +class AtId: + field_id: Optional[str] = None + + +@dataclass +class Params: + annotations: Optional[List[AtId]] = None + + +@dataclass +class Model: + id: Optional[str] = None + jsonrpc: str = '2.0' + method: str = 'sxplr.rmAnnotations' + params: Optional[Params] = None diff --git a/siibra/explorer/api/request/rmAnnotations/response/__init__.py b/siibra/explorer/api/request/rmAnnotations/response/__init__.py new file mode 100644 index 000000000..83394328f --- /dev/null +++ b/siibra/explorer/api/request/rmAnnotations/response/__init__.py @@ -0,0 +1,15 @@ +# generated by datamodel-codegen: +# filename: sxplr.rmAnnotations__toSxplr__response.json +# timestamp: 2023-12-07T15:40:38+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class Model: + jsonrpc: str = '2.0' + id: Optional[str] = None + result: str = 'OK' diff --git a/siibra/explorer/api/request/selectAtlas/request/__init__.py b/siibra/explorer/api/request/selectAtlas/request/__init__.py new file mode 100644 index 000000000..64271dfad --- /dev/null +++ b/siibra/explorer/api/request/selectAtlas/request/__init__.py @@ -0,0 +1,21 @@ +# generated by datamodel-codegen: +# filename: sxplr.selectAtlas__toSxplr__request.json +# timestamp: 2023-12-07T15:40:42+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class AtId: + field_id: Optional[str] = None + + +@dataclass +class Model: + id: Optional[str] = None + jsonrpc: str = '2.0' + method: str = 'sxplr.selectAtlas' + params: Optional[AtId] = None diff --git a/siibra/explorer/api/request/selectAtlas/response/__init__.py b/siibra/explorer/api/request/selectAtlas/response/__init__.py new file mode 100644 index 000000000..e6bf58a27 --- /dev/null +++ b/siibra/explorer/api/request/selectAtlas/response/__init__.py @@ -0,0 +1,15 @@ +# generated by datamodel-codegen: +# filename: sxplr.selectAtlas__toSxplr__response.json +# timestamp: 2023-12-07T15:40:44+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class Model: + jsonrpc: str = '2.0' + id: Optional[str] = None + result: str = 'OK' diff --git a/siibra/explorer/api/request/selectParcellation/request/__init__.py b/siibra/explorer/api/request/selectParcellation/request/__init__.py new file mode 100644 index 000000000..146e2fb07 --- /dev/null +++ b/siibra/explorer/api/request/selectParcellation/request/__init__.py @@ -0,0 +1,21 @@ +# generated by datamodel-codegen: +# filename: sxplr.selectParcellation__toSxplr__request.json +# timestamp: 2023-12-07T15:40:41+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class AtId: + field_id: Optional[str] = None + + +@dataclass +class Model: + id: Optional[str] = None + jsonrpc: str = '2.0' + method: str = 'sxplr.selectParcellation' + params: Optional[AtId] = None diff --git a/siibra/explorer/api/request/selectParcellation/response/__init__.py b/siibra/explorer/api/request/selectParcellation/response/__init__.py new file mode 100644 index 000000000..b21739aa7 --- /dev/null +++ b/siibra/explorer/api/request/selectParcellation/response/__init__.py @@ -0,0 +1,15 @@ +# generated by datamodel-codegen: +# filename: sxplr.selectParcellation__toSxplr__response.json +# timestamp: 2023-12-07T15:40:45+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class Model: + jsonrpc: str = '2.0' + id: Optional[str] = None + result: str = 'OK' diff --git a/siibra/explorer/api/request/selectTemplate/request/__init__.py b/siibra/explorer/api/request/selectTemplate/request/__init__.py new file mode 100644 index 000000000..8605bfc6e --- /dev/null +++ b/siibra/explorer/api/request/selectTemplate/request/__init__.py @@ -0,0 +1,21 @@ +# generated by datamodel-codegen: +# filename: sxplr.selectTemplate__toSxplr__request.json +# timestamp: 2023-12-07T15:40:39+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class AtId: + field_id: Optional[str] = None + + +@dataclass +class Model: + id: Optional[str] = None + jsonrpc: str = '2.0' + method: str = 'sxplr.selectTemplate' + params: Optional[AtId] = None diff --git a/siibra/explorer/api/request/selectTemplate/response/__init__.py b/siibra/explorer/api/request/selectTemplate/response/__init__.py new file mode 100644 index 000000000..e62c82b97 --- /dev/null +++ b/siibra/explorer/api/request/selectTemplate/response/__init__.py @@ -0,0 +1,15 @@ +# generated by datamodel-codegen: +# filename: sxplr.selectTemplate__toSxplr__response.json +# timestamp: 2023-12-07T15:40:37+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class Model: + jsonrpc: str = '2.0' + id: Optional[str] = None + result: str = 'OK' diff --git a/siibra/explorer/api/request/updateLayers/request/__init__.py b/siibra/explorer/api/request/updateLayers/request/__init__.py new file mode 100644 index 000000000..7ca4f5b06 --- /dev/null +++ b/siibra/explorer/api/request/updateLayers/request/__init__.py @@ -0,0 +1,30 @@ +# generated by datamodel-codegen: +# filename: sxplr.updateLayers__toSxplr__request.json +# timestamp: 2023-12-07T15:40:40+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import List, Optional + +Len4num = List[float] + + +@dataclass +class AddableLayer: + source: Optional[str] = None + shader: Optional[str] = None + transform: Optional[List[Len4num]] = None + + +@dataclass +class Params: + layers: Optional[List[AddableLayer]] = None + + +@dataclass +class Model: + id: Optional[str] = None + jsonrpc: str = '2.0' + method: str = 'sxplr.updateLayers' + params: Optional[Params] = None diff --git a/siibra/explorer/api/request/updateLayers/response/__init__.py b/siibra/explorer/api/request/updateLayers/response/__init__.py new file mode 100644 index 000000000..d2e57728f --- /dev/null +++ b/siibra/explorer/api/request/updateLayers/response/__init__.py @@ -0,0 +1,15 @@ +# generated by datamodel-codegen: +# filename: sxplr.updateLayers__toSxplr__response.json +# timestamp: 2023-12-07T15:40:43+00:00 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class Model: + jsonrpc: str = '2.0' + id: Optional[str] = None + result: str = 'OK' diff --git a/siibra/explorer/generate_dataclass.sh b/siibra/explorer/generate_dataclass.sh new file mode 100755 index 000000000..75e922b6e --- /dev/null +++ b/siibra/explorer/generate_dataclass.sh @@ -0,0 +1,66 @@ +#! /bin/bash + +# Populate datamodels based on jsonschema generated by siibra-explorer +# require +# pip install datamodel-code-generator +# which is not a part of requirements +# Usage: +# ./generate_dataclass.sh \ +# $PATH_SIIBRA_EXPLORER_ROOT_DIR \ +# $PATH_TO_SIIBRA_PYTHON_EXPLORERAPI_DIR +# +# $$PATH_TO_SIIBRA_PYTHON_EXPLORERAPI_DIR is usually ~/siibra/explorer/api + +if [[ -z "$1" ]] +then + echo "Path to siibra-explorer is required to populate" + exit 1 +fi + +if [[ -z "$2" ]] +then + echo "Output path is required" + exit 1 +fi + +for f in $(find $1/src/api -type f -name '*.json') +do + echo "Processing $f" + dst_file=${f#$1/src/api/} + dst_file=${dst_file%.json} + dst_name=$(basename $dst_file) + dst_dir=$(dirname $dst_file) + + subdir="other" + if [[ $dst_name == *"request"* ]] + then + subdir="request" + fi + if [[ $dst_name == *"response"* ]] + then + subdir="response" + fi + + dst_name=${dst_name%__request} + dst_name=${dst_name%__response} + dst_name=${dst_name%__fromSxplr} + dst_name=${dst_name%__toSxplr} + dst_name=${dst_name#sxplr.} + dst_name=${dst_name#on.} + + dst=$2/$dst_dir/$dst_name/$subdir + + mkdir -p $dst + datamodel-codegen --input $f \ + --input-file-type jsonschema \ + --output-model-type dataclasses.dataclass \ + --output $dst/__init__.py + + echo "=============================" +done + +datamodel-codegen --input $1/src/api/request/sxplr.navigateTo__toSxplr__request.json \ + --input-file-type jsonschema \ + --output-model-type dataclasses.dataclass \ + --output $2/request/navigateTo/request + diff --git a/siibra/explorer/plugin.py b/siibra/explorer/plugin.py new file mode 100644 index 000000000..3e37cc35c --- /dev/null +++ b/siibra/explorer/plugin.py @@ -0,0 +1,227 @@ +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path +import json +from dataclasses import is_dataclass, asdict as _asdict +from threading import Thread +from typing import Any, TYPE_CHECKING, Union +import sys +from uuid import uuid4 + +if TYPE_CHECKING: + from siibra.locations import Point, PointSet + +IDENTITY=[ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], +] + +def asdict(val_in: dict|list|str|int|float): + if is_dataclass(val_in): + val_in = _asdict(val_in) + if isinstance(val_in, (str, int, float)): + return val_in + if isinstance(val_in, (list, tuple)): + return [asdict(v) for v in val_in if v is not None] + if isinstance(val_in, dict): + return { + key: asdict(value) + for key, value in val_in.items() + if value is not None + } + raise Exception(f"Cannot deal with type {type(val_in)}") + +TEMPLATE: str = None + +LOG_FLAG = False + +class ReqHndl(BaseHTTPRequestHandler): + sxplr_requests = [] + + @property + def tmpl(self): + with open(Path(__file__).parent / "template.html", "r") as fp: + return fp.read() + + def queue_sxplr_request(self, req) -> None: + self.sxplr_requests.append(req) + + def log_message(self, format: str, *args: Any) -> None: + if LOG_FLAG: + return super().log_message(format, *args) + + def do_GET(self): + if self.path == "/template.html": + self.send_response(200) + self.send_header("Content-Type", "text/html") + self.end_headers() + + self.wfile.write(bytes(self.tmpl, "utf-8")) + return + if self.path == "/ping": + self.send_response(200) + + self.send_header("Content-Type", "application/json") + self.end_headers() + ReqHndl.sxplr_requests + result = json.dumps([ + asdict(req) + for req in ReqHndl.sxplr_requests + ]) + ReqHndl.sxplr_requests = [] + self.wfile.write(bytes(result, "utf-8")) + return + self.send_error(404, f"{self.path} Not Found") + + def do_POST(self): + if self.path.endswith("data"): + self.send_response(200) + self.end_headers() + self.wfile.write(bytes("OK", "utf-8")) + return + self.send_error(500, f"{self.path} is not supported") + + +class ThreadedController(Thread): + + # exits on main thread exits + daemon = True + + def __init__(self, port=7099, *, debug=False): + super().__init__() + global LOG_FLAG + if debug: + LOG_FLAG = True + self.port = port + self.server = ThreadingHTTPServer(("localhost", self.port), ReqHndl) + + + def run(self) -> None: + print("!!!NOT FOR PRODUCTION USE!!!") + print(f"Listening on {self.port}") + self.server.serve_forever() + + def stop(self): + self.server.shutdown() + + +class Explorer: + """Start a controller for explorer. + If used as a context manager, will start the server automatically. + If used normally, user should call start() to start the server, and call stop() when done. + + Args: + root_url: str The viewer URL that will be launched + port: int Port plugin is running + """ + + def __init__(self, root_url="https://atlases.ebrains.eu/viewer/", port:int=7099) -> None: + self.controller = ThreadedController(port) + self.root_url = root_url + + def __enter__(self): + self.start() + return self + + def __exit__(self): + self.stop() + + def start(self): + from . import encode_url + import siibra + + atlas = siibra.atlases['human'] + space = siibra.spaces['mni 152'] + parc = siibra.parcellations['julich 3'] + self.goto_url = encode_url(atlas, space, parc, root_url=self.root_url, query_params={ + "pl": '["http://localhost:7099/template.html"]' + }) + print(f"Go to {self.goto_url}", file=sys.stderr) + self.controller.start() + + def stop(self): + self.controller.stop() + + def _check_alive(self): + if not self.controller.is_alive(): + raise Exception(f"controller is not yet live") + + def navigate(self, *, position=None, orientation=None, zoom=None, + perspective_orientation=None, perspective_zoom=None): + self._check_alive() + + from .api.request.navigateTo.request import Model, Params + ReqHndl.sxplr_requests.append( + Model( + id=str(uuid4()), + params=Params( + position=position, + orientation=orientation, + perspectiveOrientation=perspective_orientation, + perspectiveZoom=perspective_zoom, + zoom=zoom, + animate=True, + ) + ) + ) + + def overlay(self, *, url: str, transform=IDENTITY): + self._check_alive() + from .api.request.loadLayers.request import Model, Params, AddableLayer + ReqHndl.sxplr_requests.append( + Model( + id=str(uuid4()), + params=Params(layers=[ + AddableLayer(source=url, transform=transform) + ]) + ) + ) + + + @staticmethod + def _point_to_coords(point: 'Point', name="Untitled", color: str="#ffffff"): + from .api.request.addAnnotations.request import ( + SxplrCoordinatePointExtension, CoordinatePointModel, + ApiModelsOpenmindsSANDSV3MiscellaneousCoordinatePointCoordinates as Coord + ) + return SxplrCoordinatePointExtension( + color=color, + name=name, + openminds=CoordinatePointModel( + field_type="", + field_id="", + coordinateSpace={ + "@id": point.space.id + }, + coordinates=[Coord(value=v) for v in point] + ) + ) + + @staticmethod + def _pointset_to_coords(pointset: 'PointSet', name="Untitled", color: str="#ff0000"): + return [Explorer._point_to_coords(point, name, color) + for point in pointset] + + def annotate(self, *, points: Union['Point', 'PointSet']): + self._check_alive() + from .api.request.addAnnotations.request import Model, Params + from siibra.locations import Point, PointSet + append_points = [] + + if isinstance(points, PointSet): + append_points.extend( + Explorer._pointset_to_coords(points) + ) + if isinstance(points, Point): + append_points.append( + Explorer._point_to_coords(points) + ) + ReqHndl.sxplr_requests.append( + Model( + id=str(uuid4()), + params=Params( + annotations=append_points + ) + ) + ) diff --git a/siibra/explorer/template.html b/siibra/explorer/template.html new file mode 100644 index 000000000..7cd589cef --- /dev/null +++ b/siibra/explorer/template.html @@ -0,0 +1,82 @@ + + + + + + Siibra Explorer Plugin + + + This is a companion plugin for siibra-python. + You can safely minimize (NOT close!) this plugin, and control it via python. + + + \ No newline at end of file From c8084e092aa5eb6557925e6eff07337b80ffcae8 Mon Sep 17 00:00:00 2001 From: Xiao Gui Date: Wed, 30 Jul 2025 16:51:32 +0200 Subject: [PATCH 03/14] exmpt: python control siibra-explorer --- siibra/explorer/README.md | 40 +++++++++++++-- siibra/explorer/generate_dataclass.sh | 2 +- siibra/explorer/plugin.py | 66 +++++++++++++++++++++++-- siibra/explorer/requirements.txt | 1 + siibra/explorer/sample_playwrite.py | 71 +++++++++++++++++++++++++++ siibra/explorer/template.html | 10 ++-- siibra/explorer/url.py | 5 +- 7 files changed, 179 insertions(+), 16 deletions(-) create mode 100644 siibra/explorer/requirements.txt create mode 100644 siibra/explorer/sample_playwrite.py diff --git a/siibra/explorer/README.md b/siibra/explorer/README.md index 7fbf7bc58..5449d6f06 100644 --- a/siibra/explorer/README.md +++ b/siibra/explorer/README.md @@ -1,5 +1,7 @@ # (experimental) Explorer extension +Control siibra-explorer with python + ```python from siibra.explorer.plugin import Explorer explorer = Explorer() @@ -12,13 +14,41 @@ explorer.navigate(position=(1e7,1e7,1e7)) # in nm explorer.overlay(url="nifti://https://data-proxy.ebrains.eu/api/v1/public/buckets/d-d69b70e2-3002-4eaf-9c61-9c56f019bbc8/probabilistic_maps_pmaps_175areas/Area-hOc1/Area-hOc1_pmap_l_N10_nlin2ICBM152asym2009c_4.2_public_258e8c1d846f92be76922b20287344ae.nii.gz") # TODO a bit buggy, and does not yet work -from siibra.locations import Point +from siibra.locations import PointSet import siibra -p1 = Point([1,2,3], space=siibra.spaces['mni152']) -p2 = Point([2,3,4], space=siibra.spaces['mni152']) -ps = p1.union(p2) +ptst = PointSet([ + [1,2,3], + [2,3,4] +], space=siibra.spaces['mni152']) + +explorer.annotate(points=ptst) # maximize perspective view for best effect + +``` + +## Automated tests + +use [playwrite](https://playwright.dev/python/) to automate user interactions. + +n.b. this functionality will depend largely on network speed, and geographical closeness to the siibra data centers. + +### Installation + +```sh +pip install -r siibra/explorer/requirements.txt +playwrite install chromium +``` + +### Example + +see -explorer.annotate(points=ps) # maximize perspective view for best effect +```sh +$ python siibra/explorer/sample_playwrite.py + +start Point in MNI Colin 27 [0.0,0.0,0.0] +navigated Point in MNI Colin 27 [10.0,10.0,10.0] +space_specced Point in MNI 152 ICBM 2009c Nonlinear Asymmetric [9.323640000000012,11.565300000000008,9.66219000000001] +returned Point in MNI Colin 27 [9.995550000000009,9.996489999999994,10.009999999999991] ``` \ No newline at end of file diff --git a/siibra/explorer/generate_dataclass.sh b/siibra/explorer/generate_dataclass.sh index 75e922b6e..10177b5d7 100755 --- a/siibra/explorer/generate_dataclass.sh +++ b/siibra/explorer/generate_dataclass.sh @@ -9,7 +9,7 @@ # $PATH_SIIBRA_EXPLORER_ROOT_DIR \ # $PATH_TO_SIIBRA_PYTHON_EXPLORERAPI_DIR # -# $$PATH_TO_SIIBRA_PYTHON_EXPLORERAPI_DIR is usually ~/siibra/explorer/api +# $PATH_TO_SIIBRA_PYTHON_EXPLORERAPI_DIR is usually ~/siibra/explorer/api if [[ -z "$1" ]] then diff --git a/siibra/explorer/plugin.py b/siibra/explorer/plugin.py index 3e37cc35c..207746bd0 100644 --- a/siibra/explorer/plugin.py +++ b/siibra/explorer/plugin.py @@ -17,9 +17,17 @@ [0, 0, 0, 1], ] +key_mapping = { + "field_id": "@id" +} + +def custom_dict_factory(list_of_tuples): + updated = [(key_mapping.get(n, n), v) for n, v in list_of_tuples] + return dict(updated) + def asdict(val_in: dict|list|str|int|float): if is_dataclass(val_in): - val_in = _asdict(val_in) + val_in = _asdict(val_in, dict_factory=custom_dict_factory) if isinstance(val_in, (str, int, float)): return val_in if isinstance(val_in, (list, tuple)): @@ -127,18 +135,19 @@ def __enter__(self): def __exit__(self): self.stop() - def start(self): + def start(self, *, atlas_spec: str="human", space_spec: str="mni 152", parcellation_spec: str="julich 3"): from . import encode_url import siibra - atlas = siibra.atlases['human'] - space = siibra.spaces['mni 152'] - parc = siibra.parcellations['julich 3'] + atlas = siibra.atlases[atlas_spec] + space = siibra.spaces[space_spec] + parc = siibra.parcellations[parcellation_spec] self.goto_url = encode_url(atlas, space, parc, root_url=self.root_url, query_params={ "pl": '["http://localhost:7099/template.html"]' }) print(f"Go to {self.goto_url}", file=sys.stderr) self.controller.start() + return self.goto_url def stop(self): self.controller.stop() @@ -225,3 +234,50 @@ def annotate(self, *, points: Union['Point', 'PointSet']): ) ) ) + + def select(self, *, atlas_spec: str=None, template_spec: str=None, parcellation_spec: str=None): + assert ( + bool(atlas_spec) + bool(template_spec) + bool(parcellation_spec) == 1, + f""" + Expected one and only one of {atlas_spec}, {template_spec}, and {parcellation_spec} to be set + """ + ) + import siibra + if bool(atlas_spec): + from .api.request.selectAtlas.request import Model, AtId + atlas = siibra.atlases[atlas_spec] + if atlas is None: + raise Exception(f"{atlas_spec} did not resolve to any atlas") + ReqHndl.sxplr_requests.append( + Model( + id=str(uuid4()), + params=AtId(field_id=atlas.id) + ) + ) + return + + if bool(template_spec): + from .api.request.selectTemplate.request import Model, AtId + space = siibra.spaces[template_spec] + if space is None: + raise Exception(f"{template_spec} did not resolve to any space") + ReqHndl.sxplr_requests.append( + Model( + id=str(uuid4()), + params=AtId(field_id=space.id) + ) + ) + return + + if bool(parcellation_spec): + from .api.request.selectParcellation.request import Model, AtId + parcellation = siibra.parcellations[parcellation_spec] + if parcellation is None: + raise Exception(f"{parcellation_spec} did not resolve to any parcellation") + ReqHndl.sxplr_requests.append( + Model( + id=str(uuid4()), + params=AtId(field_id=parcellation.id) + ) + ) + return diff --git a/siibra/explorer/requirements.txt b/siibra/explorer/requirements.txt new file mode 100644 index 000000000..62b829ea1 --- /dev/null +++ b/siibra/explorer/requirements.txt @@ -0,0 +1 @@ +pytest-playwright \ No newline at end of file diff --git a/siibra/explorer/sample_playwrite.py b/siibra/explorer/sample_playwrite.py new file mode 100644 index 000000000..8005f7f0f --- /dev/null +++ b/siibra/explorer/sample_playwrite.py @@ -0,0 +1,71 @@ +from siibra.locations import Point +from siibra.explorer.plugin import Explorer +from siibra.explorer.url import decode_url +from playwright.sync_api import sync_playwright + +dismiss_preamble = [ + ("Welcome to ebrains siibra explorer", "Dismiss"), + ("Privacy Policy", "Ok"), +] + +wanted_dialog = [ + ("http://localhost:7099/template.html", "OK"), +] + + +def assess_roundtrip(pt: Point, space_spec: str): + + explorer = Explorer(root_url="https://atlases.ebrains.eu/viewer-staging/") + url = explorer.start(space_spec=pt.space) + with sync_playwright() as playwrite: + chromium = playwrite.chromium + browser = chromium.launch() + context = browser.new_context() + page = context.new_page() + + page.goto(url) + page.wait_for_timeout(1000) + + curr_url = page.url + + for text, click in dismiss_preamble: + dialog = page.get_by_role("dialog").filter(has_text=text) + btn = dialog.locator("//button").filter(has_text=click) + btn.click(force=True) + + for text, click in wanted_dialog: + dialog = page.get_by_role("dialog").filter(has_text=text) + btn = dialog.locator("//button").filter(has_text=click) + btn.click() + + explorer.navigate(position=[coord * 1e6 for coord in pt]) # in nm + + page.wait_for_timeout(5000) + now_url = page.url + + explorer.select(template_spec=space_spec) + page.wait_for_timeout(30000) + space_specced_url = page.url + + + explorer.select(template_spec=pt.space) + page.wait_for_timeout(5000) + return_url = page.url + + start = decode_url(curr_url) + navigated = decode_url(now_url) + space_specced = decode_url(space_specced_url) + returned = decode_url(return_url) + + print_result = zip( + ("start", "navigated", "space_specced", "returned"), + (start, navigated, space_specced, returned), + ) + + print("end") + for name, bbox in print_result: + print(name, bbox.bounding_box.center) + +if __name__ == "__main__": + pt = Point([10, 10, 10], "colin 27") + assess_roundtrip(pt, "mni152") diff --git a/siibra/explorer/template.html b/siibra/explorer/template.html index 7cd589cef..57e928d70 100644 --- a/siibra/explorer/template.html +++ b/siibra/explorer/template.html @@ -10,7 +10,9 @@ You can safely minimize (NOT close!) this plugin, and control it via python.