siibra 0.4a91__py3-none-any.whl → 0.4a92__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
siibra/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4a91
1
+ 0.4a92
@@ -26,7 +26,7 @@ from ..features.image import sections, volume_of_interest
26
26
  from ..core import atlas, parcellation, space, region
27
27
  from ..locations import point, pointset
28
28
  from ..retrieval import datasets, repositories
29
- from ..volumes import gifti, volume, nifti, neuroglancer, sparsemap, parcellationmap
29
+ from ..volumes import gifti, volume, nifti, neuroglancer, freesurfer, sparsemap, parcellationmap
30
30
 
31
31
  from os import path
32
32
  import json
@@ -253,7 +253,9 @@ class Factory:
253
253
  nifti.NiftiProvider,
254
254
  nifti.ZipContainedNiftiProvider,
255
255
  gifti.GiftiMesh,
256
- gifti.GiftiSurfaceLabeling
256
+ gifti.GiftiSurfaceLabeling,
257
+ freesurfer.FreesurferAnnot,
258
+ freesurfer.ZippedFreesurferAnnot,
257
259
  ]
258
260
 
259
261
  for srctype, provider_spec in spec.get("providers", {}).items():
@@ -30,7 +30,7 @@ import json
30
30
  from zipfile import ZipFile
31
31
  import requests
32
32
  import os
33
- from nibabel import Nifti1Image, GiftiImage, streamlines
33
+ from nibabel import Nifti1Image, GiftiImage, streamlines, freesurfer
34
34
  from skimage import io as skimage_io
35
35
  import gzip
36
36
  from io import BytesIO
@@ -54,6 +54,34 @@ if TYPE_CHECKING:
54
54
 
55
55
  USER_AGENT_HEADER = {"User-Agent": f"siibra-python/{__version__}"}
56
56
 
57
+
58
+ def read_as_bytesio(function: Callable, suffix: str, bytesio: BytesIO):
59
+ """
60
+ Helper method to provide BytesIO to methods that only takes file path and
61
+ cannot handle BytesIO normally (e.g., `nibabel.freesurfer.read_annot()`).
62
+
63
+ Writes the bytes to a temporary file on cache and reads with the
64
+ original function.
65
+
66
+ Parameters
67
+ ----------
68
+ function : Callable
69
+ suffix : str
70
+ Must match the suffix expected by the function provided.
71
+ bytesio : BytesIO
72
+
73
+ Returns
74
+ -------
75
+ Return type of the provided function.
76
+ """
77
+ tempfile = CACHE.build_filename(f"temp_{suffix}") + suffix
78
+ with open(tempfile, "wb") as bf:
79
+ bf.write(bytesio.getbuffer())
80
+ result = function(tempfile)
81
+ os.remove(tempfile)
82
+ return result
83
+
84
+
57
85
  DECODERS = {
58
86
  ".nii": lambda b: Nifti1Image.from_bytes(b),
59
87
  ".gii": lambda b: GiftiImage.from_bytes(b),
@@ -65,6 +93,7 @@ DECODERS = {
65
93
  ".zip": lambda b: ZipFile(BytesIO(b)),
66
94
  ".png": lambda b: skimage_io.imread(BytesIO(b)),
67
95
  ".npy": lambda b: np.load(BytesIO(b)),
96
+ ".annot": lambda b: read_as_bytesio(freesurfer.read_annot, '.annot', BytesIO(b)),
68
97
  }
69
98
 
70
99
 
@@ -18,6 +18,7 @@ from .parcellationmap import Map
18
18
  from .neuroglancer import NeuroglancerProvider, NeuroglancerMesh
19
19
  from .nifti import NiftiProvider
20
20
  from .gifti import GiftiSurfaceLabeling, GiftiMesh
21
+ from .freesurfer import FreesurferAnnot, ZippedFreesurferAnnot
21
22
 
22
23
 
23
24
  def warm_cache():
@@ -0,0 +1,123 @@
1
+ # Copyright 2018-2025
2
+ # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
+
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ """Handles reading and preparing gii files."""
16
+
17
+ from typing import Union, Dict, TYPE_CHECKING
18
+
19
+ import numpy as np
20
+
21
+ from . import volume
22
+ from ..retrieval.requests import HttpRequest, ZipfileRequest
23
+
24
+ if TYPE_CHECKING:
25
+ from ..locations import boundingbox as _boundingbox
26
+
27
+
28
+ class FreesurferAnnot(volume.VolumeProvider, srctype="freesurfer-annot"):
29
+ def __init__(self, url: Union[str, dict]):
30
+ self._init_url = url
31
+ if isinstance(url, str): # single mesh labelling
32
+ self._loaders = {None: HttpRequest(url)}
33
+ elif isinstance(url, dict): # named label fragments
34
+ self._loaders = {lbl: HttpRequest(u) for lbl, u in url.items()}
35
+ else:
36
+ raise NotImplementedError(f"Urls for {self.__class__.__name__} are expected to be of type str.")
37
+
38
+ def fetch(self, fragment: str = None, label: int = None, **kwargs):
39
+ """Returns a 1D numpy array of label indices."""
40
+ vertex_labels = []
41
+ if fragment is None:
42
+ matched_frags = list(self._loaders.keys())
43
+ else:
44
+ matched_frags = [frg for frg in self._loaders.keys() if fragment.lower() in frg.lower()]
45
+ if len(matched_frags) != 1:
46
+ raise ValueError(
47
+ f"Requested fragment '{fragment}' could not be matched uniquely "
48
+ f"to [{', '.join(self._loaders)}]"
49
+ )
50
+ for frag in matched_frags:
51
+ frag_labels, *_ = self._loaders[frag].data
52
+ if label is not None: # create the mask
53
+ selected_label = frag_labels == label
54
+ frag_labels[selected_label] = 1
55
+ frag_labels[~selected_label] = 0
56
+ else:
57
+ frag_labels[frag_labels == -1] = 0 # annot files store background as -1 while siibra uses 0
58
+ vertex_labels.append(frag_labels)
59
+
60
+ return {"labels": np.hstack(vertex_labels)}
61
+
62
+ @property
63
+ def boundingbox(self) -> '_boundingbox.BoundingBox':
64
+ raise NotImplementedError(
65
+ f"Bounding box access to {self.__class__.__name__} objects not yet implemented."
66
+ )
67
+
68
+ @property
69
+ def fragments(self):
70
+ return [k for k in self._loaders if k is not None]
71
+
72
+ @property
73
+ def _url(self) -> Union[str, Dict[str, str]]:
74
+ return self._init_url
75
+
76
+
77
+ class ZippedFreesurferAnnot(volume.VolumeProvider, srctype="zip/freesurfer-annot"):
78
+ def __init__(self, url: Union[str, dict]):
79
+ self._init_url = url
80
+ if isinstance(url, str): # single mesh labelling
81
+ self._loaders = {None: ZipfileRequest(*url.split(" "))}
82
+ elif isinstance(url, dict): # named label fragments
83
+ self._loaders = {lbl: ZipfileRequest(*u.split(" ")) for lbl, u in url.items()}
84
+ else:
85
+ raise NotImplementedError(f"Urls for {self.__class__.__name__} are expected to be of type str.")
86
+
87
+ def fetch(self, fragment: str = None, label: int = None, **kwargs):
88
+ """Returns a 1D numpy array of label indices."""
89
+ vertex_labels = []
90
+ if fragment is None:
91
+ matched_frags = list(self._loaders.keys())
92
+ else:
93
+ matched_frags = [frg for frg in self._loaders.keys() if fragment.lower() in frg.lower()]
94
+ if len(matched_frags) != 1:
95
+ raise ValueError(
96
+ f"Requested fragment '{fragment}' could not be matched uniquely "
97
+ f"to [{', '.join(self._loaders)}]"
98
+ )
99
+ for frag in matched_frags:
100
+ frag_labels, *_ = self._loaders[frag].data
101
+ if label is not None: # create the mask
102
+ selected_label = frag_labels == label
103
+ frag_labels[selected_label] = 1
104
+ frag_labels[~selected_label] = 0
105
+ else:
106
+ frag_labels[frag_labels == -1] = 0 # annot files store background as -1 while siibra uses 0
107
+ vertex_labels.append(frag_labels)
108
+
109
+ return {"labels": np.hstack(vertex_labels)}
110
+
111
+ @property
112
+ def boundingbox(self) -> '_boundingbox.BoundingBox':
113
+ raise NotImplementedError(
114
+ f"Bounding box access to {self.__class__.__name__} objects not yet implemented."
115
+ )
116
+
117
+ @property
118
+ def fragments(self):
119
+ return [k for k in self._loaders if k is not None]
120
+
121
+ @property
122
+ def _url(self) -> Union[str, Dict[str, str]]:
123
+ return self._init_url
siibra/volumes/volume.py CHANGED
@@ -49,7 +49,9 @@ class Volume:
49
49
  "neuroglancer/precompmesh",
50
50
  "neuroglancer/precompmesh/surface",
51
51
  "gii-mesh",
52
- "gii-label"
52
+ "gii-label",
53
+ "freesurfer-annot",
54
+ "zip/freesurfer-annot",
53
55
  ]
54
56
 
55
57
  SUPPORTED_FORMATS = IMAGE_FORMATS + MESH_FORMATS
@@ -218,7 +220,7 @@ class Volume:
218
220
  # try the selected format only
219
221
  for try_count in range(6):
220
222
  try:
221
- if selected_format == "gii-label":
223
+ if selected_format in ["gii-label", "freesurfer-annot", "zip/freesurfer-annot"]:
222
224
  tpl = self.space.get_template(variant=kwargs.get('variant'))
223
225
  mesh = tpl.fetch(**kwargs)
224
226
  labels = self._providers[selected_format].fetch(**kwargs)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: siibra
3
- Version: 0.4a91
3
+ Version: 0.4a92
4
4
  Summary: siibra - Software interfaces for interacting with brain atlases
5
5
  Home-page: https://github.com/FZJ-INM1-BDA/siibra-python
6
6
  Author: Big Data Analytics Group, Forschungszentrum Juelich, Institute of Neuroscience and Medicine (INM-1)
@@ -1,9 +1,9 @@
1
- siibra/VERSION,sha256=0miaCbL3UWtbg4acr9iCvJj5M7KYoOmn_KjDDK2e85c,7
1
+ siibra/VERSION,sha256=r7rbswsvUpFkOmvUh_KLSJ9BH6UKUoIdnkkC3J8IeOc,7
2
2
  siibra/__init__.py,sha256=qBxxMRyl9RojNt0twQr2LDk1Nyk5eNsPHFxxoIwnpx4,4540
3
3
  siibra/commons.py,sha256=y0yLQw1wWn8N9EgQGW2B0aAuo3FM_5zthMcEcvuvmH8,25895
4
4
  siibra/configuration/__init__.py,sha256=-_Kuf0TfMEdFXiSCTAdcHUL_krA8-cxhJypSNNay53Q,752
5
5
  siibra/configuration/configuration.py,sha256=x06QsaqGQbce1d9TuFCCYEgMWJBlLbkt8B3zTfYz5RE,6887
6
- siibra/configuration/factory.py,sha256=olRMm__Q2DeQX5CoBFkmfBdgXlWhZ3Wsy-oKG7s9BuQ,21071
6
+ siibra/configuration/factory.py,sha256=WzfDlD2u7eeONVzy8wdp56Sqe_Uv4yTCveYRl7RRDOg,21170
7
7
  siibra/core/__init__.py,sha256=z22elfoi5_TscUb17-pBoGyfT_q9PQpvgOgSLEJe2WE,916
8
8
  siibra/core/atlas.py,sha256=YwhVWAIT0XGfzKjSuQUNpljdIE4VP4crNQ3VNfTrgKg,7868
9
9
  siibra/core/concept.py,sha256=rKR7FO1HaFdy076Dy0y9Bjcdu_tcYUHbxLYNZxH7XTY,9595
@@ -54,21 +54,22 @@ siibra/retrieval/__init__.py,sha256=pAJ2WsNk9tVY1RddmXzya1iFgJXJarO4TVCJTob1Ej0,
54
54
  siibra/retrieval/cache.py,sha256=9Wssws2YHiI_OW4EZp72OzYV9nw8h5cAEAakSxtRMts,5143
55
55
  siibra/retrieval/datasets.py,sha256=zDiSgbmL6aqnZpYrJMcfbv2c_Kh-2KMwmHQTJkdF9GM,11267
56
56
  siibra/retrieval/repositories.py,sha256=wYE3oYcIBgD3WOqIerS3darsg_TSrfpdDJ72l2uytak,25961
57
- siibra/retrieval/requests.py,sha256=tRmgocvob7619e_4Vw-dVd8R3JJ95UGkVyESXF2EBE0,22013
57
+ siibra/retrieval/requests.py,sha256=DExE1Bj2QEu6RWjCJRlnqtuFKCskk1k7PuLK6aVfuPY,22889
58
58
  siibra/retrieval/exceptions/__init__.py,sha256=_w996kp5rxuvi-_iLhrByrH2OtApxm_TykthkaQvKpo,874
59
59
  siibra/vocabularies/__init__.py,sha256=h38zIUd44RRFOR6X09VnKsC-_WrhtB0xFzYWX-Nyncg,1293
60
60
  siibra/vocabularies/gene_names.json,sha256=i-gnh753GyZtQfX_dWibNYr_d5ccDPHooOwsdeKUYqE,1647972
61
61
  siibra/vocabularies/receptor_symbols.json,sha256=F6DZIArPCBmJV_lWGV-zDpBBH_GOJOZm67LBE4qzMa4,5722
62
62
  siibra/vocabularies/region_aliases.json,sha256=T2w1wRlxPNTsPppXn0bzC70tNsb8mOjLsoHuxDSYm2w,8563
63
- siibra/volumes/__init__.py,sha256=KFOT756W63pIEtw9lZGDzQYExOg94A1wvsuUEgLwVak,965
63
+ siibra/volumes/__init__.py,sha256=fkSZFZhbUiDf8fkWu-lNta-LvCrGI8Pp4-Imkz2SRqg,1028
64
+ siibra/volumes/freesurfer.py,sha256=q5pHcWEvluzQ_2ENaDgy4jfb_k266QXUPa_LLkWniZw,5032
64
65
  siibra/volumes/gifti.py,sha256=_LUHgPqndzSj9nNH70Ee2lWMkockVGdi2_JufuaExG4,5551
65
66
  siibra/volumes/neuroglancer.py,sha256=HWxhTh2FviLkNHdKL-_OMtl3oC6-WQb5b8skdvQ0ql8,24504
66
67
  siibra/volumes/nifti.py,sha256=nGSCedjpsiy43XIiHQ2SRy9rPRK8Ci9QDq4AHKclCck,9030
67
68
  siibra/volumes/parcellationmap.py,sha256=phoMzNLI3Ig9rWvuD_c1yBm3ejLxAJGXtTHMXqw-ivc,44243
68
69
  siibra/volumes/sparsemap.py,sha256=j41ozLSgKri68-KDsqo46gEs4lT4aYsf2yMF4l-ruCo,21752
69
- siibra/volumes/volume.py,sha256=h_KrdDkpeoENnOrE7BlTZNXaZQGTrJXHX0eSyArxF-4,11667
70
- siibra-0.4a91.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
71
- siibra-0.4a91.dist-info/METADATA,sha256=CQFLI6K61JNfgK6wxmsFyFh1RcK8-G11vpm-UbAHab4,8547
72
- siibra-0.4a91.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
73
- siibra-0.4a91.dist-info/top_level.txt,sha256=NF0OSGLL0li2qyC7MaU0iBB5Y9S09_euPpvisD0-8Hg,7
74
- siibra-0.4a91.dist-info/RECORD,,
70
+ siibra/volumes/volume.py,sha256=QJDbOkS3DXzuqiC-O-hBqBHkqS91zoDte9UXT-UuMdY,11774
71
+ siibra-0.4a92.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
72
+ siibra-0.4a92.dist-info/METADATA,sha256=C3Tm_qAofj15fAvGZrivkoJL3mswTz1tqc3jFu7D89M,8547
73
+ siibra-0.4a92.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
74
+ siibra-0.4a92.dist-info/top_level.txt,sha256=NF0OSGLL0li2qyC7MaU0iBB5Y9S09_euPpvisD0-8Hg,7
75
+ siibra-0.4a92.dist-info/RECORD,,