siibra 1.0.1a1__py3-none-any.whl → 1.0.1a2__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.

Potentially problematic release.


This version of siibra might be problematic. Click here for more details.

Files changed (66) hide show
  1. siibra/VERSION +1 -1
  2. siibra/__init__.py +7 -16
  3. siibra/commons.py +9 -7
  4. siibra/configuration/configuration.py +5 -5
  5. siibra/configuration/factory.py +9 -8
  6. siibra/core/__init__.py +1 -1
  7. siibra/core/assignment.py +1 -0
  8. siibra/core/atlas.py +3 -3
  9. siibra/core/concept.py +4 -2
  10. siibra/core/parcellation.py +5 -5
  11. siibra/core/region.py +24 -25
  12. siibra/core/space.py +4 -6
  13. siibra/core/structure.py +2 -2
  14. siibra/features/anchor.py +2 -4
  15. siibra/features/connectivity/regional_connectivity.py +10 -13
  16. siibra/features/dataset/ebrains.py +1 -1
  17. siibra/features/feature.py +21 -18
  18. siibra/features/image/__init__.py +4 -2
  19. siibra/features/image/image.py +2 -4
  20. siibra/features/image/sections.py +81 -2
  21. siibra/features/image/volume_of_interest.py +0 -8
  22. siibra/features/tabular/__init__.py +1 -1
  23. siibra/features/tabular/bigbrain_intensity_profile.py +2 -1
  24. siibra/features/tabular/cell_density_profile.py +8 -9
  25. siibra/features/tabular/cortical_profile.py +6 -6
  26. siibra/features/tabular/gene_expression.py +6 -5
  27. siibra/features/tabular/layerwise_bigbrain_intensities.py +4 -3
  28. siibra/features/tabular/layerwise_cell_density.py +4 -6
  29. siibra/features/tabular/receptor_density_fingerprint.py +34 -9
  30. siibra/features/tabular/receptor_density_profile.py +1 -2
  31. siibra/features/tabular/regional_timeseries_activity.py +7 -7
  32. siibra/features/tabular/tabular.py +4 -5
  33. siibra/livequeries/allen.py +20 -22
  34. siibra/livequeries/bigbrain.py +239 -51
  35. siibra/livequeries/ebrains.py +13 -10
  36. siibra/livequeries/query.py +3 -3
  37. siibra/locations/__init__.py +17 -8
  38. siibra/locations/boundingbox.py +7 -6
  39. siibra/{experimental/plane3d.py → locations/experimental.py} +113 -13
  40. siibra/locations/location.py +10 -12
  41. siibra/locations/point.py +7 -16
  42. siibra/locations/pointcloud.py +51 -10
  43. siibra/retrieval/cache.py +1 -0
  44. siibra/retrieval/datasets.py +19 -13
  45. siibra/retrieval/repositories.py +10 -11
  46. siibra/retrieval/requests.py +26 -24
  47. siibra/vocabularies/__init__.py +1 -2
  48. siibra/volumes/__init__.py +4 -3
  49. siibra/volumes/parcellationmap.py +30 -16
  50. siibra/volumes/providers/freesurfer.py +4 -4
  51. siibra/volumes/providers/gifti.py +4 -4
  52. siibra/volumes/providers/neuroglancer.py +19 -22
  53. siibra/volumes/providers/nifti.py +6 -6
  54. siibra/volumes/providers/provider.py +3 -2
  55. siibra/volumes/sparsemap.py +7 -6
  56. siibra/volumes/volume.py +21 -28
  57. {siibra-1.0.1a1.dist-info → siibra-1.0.1a2.dist-info}/METADATA +10 -6
  58. siibra-1.0.1a2.dist-info/RECORD +80 -0
  59. {siibra-1.0.1a1.dist-info → siibra-1.0.1a2.dist-info}/WHEEL +1 -1
  60. siibra/experimental/__init__.py +0 -19
  61. siibra/experimental/contour.py +0 -61
  62. siibra/experimental/cortical_profile_sampler.py +0 -57
  63. siibra/experimental/patch.py +0 -98
  64. siibra-1.0.1a1.dist-info/RECORD +0 -84
  65. {siibra-1.0.1a1.dist-info → siibra-1.0.1a2.dist-info}/LICENSE +0 -0
  66. {siibra-1.0.1a1.dist-info → siibra-1.0.1a2.dist-info}/top_level.txt +0 -0
@@ -13,18 +13,113 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
- from . import contour
17
- from . import patch
18
- from ..locations import point, pointcloud
19
- from ..volumes import volume
16
+ from typing import List
17
+ from math import atan2
20
18
 
21
19
  import numpy as np
20
+ from nilearn import image
21
+
22
+ from . import point, pointcloud, boundingbox
23
+ from ..volumes import volume
24
+ from ..commons import translation_matrix, y_rotation_matrix
25
+
26
+
27
+ class AxisAlignedPatch(pointcloud.PointCloud):
28
+
29
+ def __init__(self, corners: pointcloud.PointCloud):
30
+ """Construct a patch in physical coordinates.
31
+ As of now, only patches aligned in the y plane of the physical space
32
+ are supported."""
33
+ # TODO: need to ensure that the points are planar, if more than 3
34
+ assert len(corners) == 4
35
+ assert len(np.unique(corners.coordinates[:, 1])) == 1
36
+ pointcloud.PointCloud.__init__(
37
+ self,
38
+ coordinates=corners.coordinates,
39
+ space=corners.space,
40
+ sigma_mm=corners.sigma_mm,
41
+ labels=corners.labels
42
+ )
43
+ # self.corners = corners
44
+
45
+ def __str__(self):
46
+ return f"Patch with boundingbox {self.boundingbox}"
47
+
48
+ def flip(self):
49
+ """Returns a flipped version of the patch."""
50
+ new_corners = self.coordinates.copy()[[2, 3, 0, 1]]
51
+ return AxisAlignedPatch(pointcloud.PointCloud(new_corners, self.space))
52
+
53
+ def extract_volume(
54
+ self,
55
+ image_volume: volume.Volume,
56
+ resolution_mm: float,
57
+ ):
58
+ """
59
+ fetches image data in a planar patch.
60
+ TODO The current implementation only covers patches which are strictly
61
+ define in the y plane. A future implementation should accept arbitrary
62
+ oriented patches.accept arbitrary oriented patches.
63
+ """
64
+ assert image_volume.space == self.space
65
+
66
+ # Extend the 2D patch into a 3D structure
67
+ # this is only valid if the patch plane lies within the image canvas.
68
+ canvas = image_volume.get_boundingbox()
69
+ assert canvas.minpoint[1] <= self.coordinates[0, 1]
70
+ assert canvas.maxpoint[1] >= self.coordinates[0, 1]
71
+ XYZ = self.coordinates
72
+ voi = boundingbox.BoundingBox(
73
+ XYZ.min(0)[:3], XYZ.max(0)[:3], space=image_volume.space
74
+ )
75
+ # enforce the patch to have the same y dimensions
76
+ voi.minpoint[1] = canvas.minpoint[1]
77
+ voi.maxpoint[1] = canvas.maxpoint[1]
78
+ patch = image_volume.fetch(voi=voi, resolution_mm=resolution_mm)
79
+ assert patch is not None
22
80
 
81
+ # patch rotation defined in physical space
82
+ vx, vy, vz = XYZ[1] - XYZ[0]
83
+ alpha = -atan2(-vz, -vx)
84
+ cx, cy, cz = XYZ.mean(0)
85
+ rot_phys = np.linalg.multi_dot(
86
+ [
87
+ translation_matrix(cx, cy, cz),
88
+ y_rotation_matrix(alpha),
89
+ translation_matrix(-cx, -cy, -cz),
90
+ ]
91
+ )
23
92
 
24
- class Plane3D:
93
+ # rotate the patch in physical space
94
+ affine_rot = np.dot(rot_phys, patch.affine)
95
+
96
+ # crop in the rotated space
97
+ pixels = (
98
+ np.dot(np.linalg.inv(affine_rot), self.homogeneous.T)
99
+ .astype("int")
100
+ .T
101
+ )
102
+ # keep a pixel distance to avoid black border pixels
103
+ xmin, ymin, zmin = pixels.min(0)[:3] + 1
104
+ xmax, ymax, zmax = pixels.max(0)[:3] - 1
105
+ h, w = xmax - xmin, zmax - zmin
106
+ affine = np.dot(affine_rot, translation_matrix(xmin, 0, zmin))
107
+ return volume.from_nifti(
108
+ image.resample_img(
109
+ patch,
110
+ target_affine=affine,
111
+ target_shape=[h, 1, w],
112
+ force_resample=True
113
+ ),
114
+ space=image_volume.space,
115
+ name=f"Rotated patch with corner points {self.coordinates} sampled from {image_volume.name}",
116
+ )
117
+
118
+
119
+ class Plane:
25
120
  """
26
121
  A 3D plane in reference space.
27
- This shall eventually be derived from siibra.Location
122
+ TODO This shall eventually be derived from siibra.Location
28
123
  """
29
124
 
30
125
  def __init__(self, point1: point.Point, point2: point.Point, point3: point.Point):
@@ -64,8 +159,6 @@ class Plane3D:
64
159
  Returns the set of intersection points.
65
160
  The line segments are given by two Nx3 arrays of their start- and endpoints.
66
161
  The result is an Nx3 list of intersection coordinates.
67
- TODO This returns an intersection even if the line segment intersects the plane,
68
-
69
162
  """
70
163
  directions = endpoints - startpoints
71
164
  lengths = np.linalg.norm(directions, axis=1)
@@ -83,7 +176,7 @@ class Plane3D:
83
176
  )
84
177
  return result
85
178
 
86
- def intersect_mesh(self, mesh: dict):
179
+ def intersect_mesh(self, mesh: dict) -> List[pointcloud.Contour]:
87
180
  """
88
181
  Intersects a 3D surface mesh with the plane.
89
182
  Returns a set of split 2D contours, represented by ordered coordinate lists.
@@ -148,7 +241,7 @@ class Plane3D:
148
241
  # should include the exact same set of points. Verify this now.
149
242
  sortrows = lambda A: A[np.lexsort(A.T[::-1]), :]
150
243
  err = (sortrows(fwd_intersections) - sortrows(bwd_intersections)).sum()
151
- assert err == 0
244
+ assert err == 0, f"intersection inconsistency: {err}"
152
245
 
153
246
  # Due to the above property, we can construct closed contours in the
154
247
  # intersection plane by following the interleaved fwd/bwd roles of intersection
@@ -175,7 +268,7 @@ class Plane3D:
175
268
 
176
269
  # finish the current contour.
177
270
  result.append(
178
- contour.Contour(np.array(points), labels=labels, space=self.space)
271
+ pointcloud.Contour(np.array(points), labels=labels, space=self.space)
179
272
  )
180
273
  if len(face_indices) > 0:
181
274
  # prepare to process another contour segment
@@ -236,8 +329,15 @@ class Plane3D:
236
329
  )
237
330
  err = (self.project_points(corners).coordinates - corners.coordinates).sum()
238
331
  if err > 1e-5:
239
- print(f"WARNING: patch coordinates were not exactly in-plane (error={err}).")
240
- return patch.Patch(self.project_points(corners))
332
+ print(
333
+ f"WARNING: patch coordinates were not exactly in-plane (error={err})."
334
+ )
335
+
336
+ try:
337
+ patch = AxisAlignedPatch(self.project_points(corners))
338
+ except AssertionError:
339
+ patch = None
340
+ return patch
241
341
 
242
342
  @classmethod
243
343
  def from_image(cls, image: volume.Volume):
@@ -16,14 +16,13 @@
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- from ..core.structure import BrainStructure
19
+ from typing import Union, Dict
20
+ from abc import abstractmethod
20
21
 
21
22
  import numpy as np
22
- from abc import abstractmethod
23
23
 
24
- from typing import TYPE_CHECKING, Union, Dict
25
- if TYPE_CHECKING:
26
- from siibra.core.space import Space
24
+ from ..core.structure import BrainStructure
25
+ from ..core import space as _space
27
26
 
28
27
 
29
28
  class Location(BrainStructure):
@@ -46,29 +45,28 @@ class Location(BrainStructure):
46
45
  _MASK_MEMO = {} # cache region masks for Location._assign_region()
47
46
  _ASSIGNMENT_CACHE = {} # caches assignment results, see Region.assign()
48
47
 
49
- def __init__(self, spacespec: Union[str, Dict[str, str], "Space"]):
48
+ def __init__(self, spacespec: Union[str, Dict[str, str], "_space.Space"]):
50
49
  self._space_spec = spacespec
51
50
  self._space_cached = None
52
51
 
53
52
  @property
54
- def space(self):
53
+ def space(self) -> "_space.Space":
55
54
  if self._space_cached is None:
56
- from ..core.space import Space
57
55
  if isinstance(self._space_spec, dict):
58
56
  spec = self._space_spec.get("@id") or self._space_spec.get("name")
59
- self._space_cached = Space.get_instance(spec)
57
+ self._space_cached = _space.Space.get_instance(spec)
60
58
  else:
61
- self._space_cached = Space.get_instance(self._space_spec)
59
+ self._space_cached = _space.Space.get_instance(self._space_spec)
62
60
  return self._space_cached
63
61
 
64
62
  @abstractmethod
65
- def warp(self, space):
63
+ def warp(self, space: Union[str, "_space.Space"]):
66
64
  """Generates a new location by warping the
67
65
  current one into another reference space."""
68
66
  pass
69
67
 
70
68
  @abstractmethod
71
- def transform(self, affine: np.ndarray, space=None):
69
+ def transform(self, affine: np.ndarray, space: Union[str, "_space.Space", None] = None):
72
70
  """Returns a new location obtained by transforming the
73
71
  reference coordinates of this one with the given affine matrix.
74
72
 
siibra/locations/point.py CHANGED
@@ -14,20 +14,20 @@
14
14
  # limitations under the License.
15
15
  """Singular coordinate defined on a space, possibly with an uncertainty."""
16
16
 
17
- from . import location, boundingbox, pointcloud
18
-
19
- from ..commons import logger
20
- from ..retrieval.requests import HttpRequest
21
- from ..exceptions import SpaceWarpingFailedError, NoneCoordinateSuppliedError
22
-
23
17
  from urllib.parse import quote
24
18
  import re
25
- import numpy as np
26
19
  import json
27
20
  import numbers
28
21
  import hashlib
29
22
  from typing import Tuple, Union
30
23
 
24
+ import numpy as np
25
+
26
+ from . import location, pointcloud
27
+ from ..commons import logger
28
+ from ..retrieval.requests import HttpRequest
29
+ from ..exceptions import SpaceWarpingFailedError, NoneCoordinateSuppliedError
30
+
31
31
 
32
32
  class Point(location.Location):
33
33
  """A single 3D point in reference space."""
@@ -115,8 +115,6 @@ class Point(location.Location):
115
115
  def intersection(self, other: location.Location) -> "Point":
116
116
  if isinstance(other, Point):
117
117
  return self if self == other else None
118
- elif isinstance(other, pointcloud.PointCloud):
119
- return self if self in other else None
120
118
  else:
121
119
  return self if other.intersection(self) else None
122
120
 
@@ -305,13 +303,6 @@ class Point(location.Location):
305
303
  assert 0 <= index < 3
306
304
  return self.coordinate[index]
307
305
 
308
- @property
309
- def boundingbox(self):
310
- w = max(self.sigma or 0, 1e-6) # at least a micrometer
311
- return boundingbox.BoundingBox(
312
- self - w, self + w, self.space, self.sigma
313
- )
314
-
315
306
  def bigbrain_section(self):
316
307
  """
317
308
  Estimate the histological section number of BigBrain
@@ -14,15 +14,10 @@
14
14
  # limitations under the License.
15
15
  """A set of coordinates on a reference space."""
16
16
 
17
- from . import location, point, boundingbox as _boundingbox
18
-
19
- from ..retrieval.requests import HttpRequest
20
- from ..commons import logger
21
- from ..exceptions import SpaceWarpingFailedError, EmptyPointCloudError
22
-
23
17
  from typing import List, Union, Tuple
24
18
  import numbers
25
19
  import json
20
+
26
21
  import numpy as np
27
22
  try:
28
23
  from sklearn.cluster import HDBSCAN
@@ -30,10 +25,11 @@ try:
30
25
  except ImportError:
31
26
  import sklearn
32
27
  _HAS_HDBSCAN = False
33
- logger.warning(
34
- f"HDBSCAN is not available with your version {sklearn.__version__} of sckit-learn."
35
- "`PointCloud.find_clusters()` will not be available."
36
- )
28
+
29
+ from . import location, point, boundingbox as _boundingbox
30
+ from ..retrieval.requests import HttpRequest
31
+ from ..commons import logger
32
+ from ..exceptions import SpaceWarpingFailedError, EmptyPointCloudError
37
33
 
38
34
 
39
35
  def from_points(points: List["point.Point"], newlabels: List[Union[int, float, tuple]] = None) -> "PointCloud":
@@ -122,6 +118,8 @@ class PointCloud(location.Location):
122
118
 
123
119
  if isinstance(other, PointCloud):
124
120
  intersecting_points = [p for p in self if p.coordinate in other.coordinates]
121
+ elif isinstance(other, point.Point):
122
+ return other if other in self else None
125
123
  else:
126
124
  intersecting_points = [p for p in self if p.intersects(other)]
127
125
  if len(intersecting_points) == 0:
@@ -342,3 +340,46 @@ class PointCloud(location.Location):
342
340
  logger.error("Matplotlib is not available. Label colors is disabled.")
343
341
  return None
344
342
  return colormaps.rainbow(np.linspace(0, 1, max(self.labels) + 1))
343
+
344
+
345
+ class Contour(PointCloud):
346
+ """
347
+ A PointCloud that represents a contour line.
348
+ The only difference is that the point order is relevant,
349
+ and consecutive points are thought as being connected by an edge.
350
+
351
+ In fact, PointCloud assumes order as well, but no connections between points.
352
+ """
353
+
354
+ def __init__(self, coordinates, space=None, sigma_mm=0, labels: list = None):
355
+ PointCloud.__init__(self, coordinates, space, sigma_mm, labels)
356
+
357
+ def crop(self, voi: "_boundingbox.BoundingBox"):
358
+ """
359
+ Crop the contour with a volume of interest.
360
+ Since the contour might be split from the cropping,
361
+ returns a set of contour segments.
362
+ """
363
+ segments = []
364
+
365
+ # set the contour point labels to a linear numbering
366
+ # so we can use them after the intersection to detect splits.
367
+ old_labels = self.labels
368
+ self.labels = list(range(len(self)))
369
+ cropped = self.intersection(voi)
370
+
371
+ if cropped is not None and not isinstance(cropped, point.Point):
372
+ assert isinstance(cropped, PointCloud)
373
+ # Identify contour splits are by discontinuouities ("jumps")
374
+ # of their labels, which denote positions in the original contour
375
+ jumps = np.diff([self.labels.index(lb) for lb in cropped.labels])
376
+ splits = [0] + list(np.where(jumps > 1)[0] + 1) + [len(cropped)]
377
+ for i, j in zip(splits[:-1], splits[1:]):
378
+ segments.append(
379
+ self.__class__(cropped.coordinates[i:j, :], space=cropped.space)
380
+ )
381
+
382
+ # reset labels of the input contour points.
383
+ self.labels = old_labels
384
+
385
+ return segments
siibra/retrieval/cache.py CHANGED
@@ -23,6 +23,7 @@ from enum import Enum
23
23
  from typing import Callable, List, NamedTuple, Union
24
24
  from concurrent.futures import ThreadPoolExecutor
25
25
  from pathlib import Path
26
+
26
27
  from filelock import FileLock as Lock
27
28
 
28
29
  from ..commons import logger, SIIBRA_CACHEDIR, SKIP_CACHEINIT_MAINTENANCE, siibra_tqdm
@@ -14,19 +14,18 @@
14
14
  # limitations under the License.
15
15
  """Metadata connection to EBRAINS datasets."""
16
16
 
17
- from .requests import MultiSourcedRequest, GitlabProxy, GitlabProxyEnum
18
-
19
17
  import re
20
- from typing import Union, List
21
- from abc import ABC, abstractproperty
18
+ from abc import ABC, abstractmethod
22
19
  from hashlib import md5
23
-
20
+ from typing import Union, List
24
21
  try:
25
22
  from typing import TypedDict
26
23
  except ImportError:
27
24
  # support python 3.7
28
25
  from typing_extensions import TypedDict
29
26
 
27
+ from .requests import MultiSourcedRequest, GitlabProxy, GitlabProxyEnum
28
+
30
29
 
31
30
  class EbrainsDatasetUrl(TypedDict):
32
31
  url: str
@@ -48,31 +47,38 @@ EbrainsDatasetEmbargoStatus = TypedDict("EbrainsDatasetEmbargoStatus", {
48
47
 
49
48
 
50
49
  class EbrainsBaseDataset(ABC):
51
- @abstractproperty
50
+ @property
51
+ @abstractmethod
52
52
  def id(self) -> str:
53
53
  raise NotImplementedError
54
54
 
55
- @abstractproperty
55
+ @property
56
+ @abstractmethod
56
57
  def name(self) -> str:
57
58
  raise NotImplementedError
58
59
 
59
- @abstractproperty
60
+ @property
61
+ @abstractmethod
60
62
  def urls(self) -> List[EbrainsDatasetUrl]:
61
63
  raise NotImplementedError
62
64
 
63
- @abstractproperty
65
+ @property
66
+ @abstractmethod
64
67
  def description(self) -> str:
65
68
  raise NotImplementedError
66
69
 
67
- @abstractproperty
70
+ @property
71
+ @abstractmethod
68
72
  def contributors(self) -> List[EbrainsDatasetPerson]:
69
73
  raise NotImplementedError
70
74
 
71
- @abstractproperty
75
+ @property
76
+ @abstractmethod
72
77
  def ebrains_page(self) -> str:
73
78
  raise NotImplementedError
74
79
 
75
- @abstractproperty
80
+ @property
81
+ @abstractmethod
76
82
  def custodians(self) -> List[EbrainsDatasetPerson]:
77
83
  raise NotImplementedError
78
84
 
@@ -342,7 +348,7 @@ class EbrainsV3Dataset(EbrainsBaseDataset):
342
348
  return [version.get("id") for version in self._detail.get("versions", [])]
343
349
 
344
350
 
345
- class GenericDataset():
351
+ class GenericDataset:
346
352
 
347
353
  def __init__(
348
354
  self,
@@ -14,25 +14,24 @@
14
14
  # limitations under the License.
15
15
  """Connect to repositories to browse and pull files within."""
16
16
 
17
+ from abc import ABC, abstractmethod
18
+ from urllib.parse import quote
19
+ import pathlib
20
+ import os
21
+ from zipfile import ZipFile
22
+ from typing import List
23
+
24
+ from .cache import CACHE
17
25
  from .requests import (
18
26
  HttpRequest,
19
27
  EbrainsRequest,
20
28
  SiibraHttpRequestError,
21
- find_suitiable_decoder,
29
+ find_suitable_decoder,
22
30
  DECODERS,
23
31
  FileLoader
24
32
  )
25
- from .cache import CACHE
26
-
27
33
  from ..commons import logger, siibra_tqdm
28
34
 
29
- from abc import ABC, abstractmethod
30
- from urllib.parse import quote
31
- import pathlib
32
- import os
33
- from zipfile import ZipFile
34
- from typing import List
35
-
36
35
 
37
36
  class RepositoryConnector(ABC):
38
37
  """
@@ -67,7 +66,7 @@ class RepositoryConnector(ABC):
67
66
  pass
68
67
 
69
68
  def _decode_response(self, response, filename: str):
70
- decoder = find_suitiable_decoder(filename)
69
+ decoder = find_suitable_decoder(filename)
71
70
  return decoder(response) if decoder else response
72
71
 
73
72
  def get(self, filename, folder="", decode_func=None):
@@ -14,35 +14,37 @@
14
14
  # limitations under the License.
15
15
  """Request files with decoders, lazy loading, and caching."""
16
16
 
17
- from .cache import CACHE, cache_user_fn
18
- from .exceptions import EbrainsAuthenticationError
19
- from ..commons import (
20
- logger,
21
- HBP_AUTH_TOKEN,
22
- KEYCLOAK_CLIENT_ID,
23
- KEYCLOAK_CLIENT_SECRET,
24
- siibra_tqdm,
25
- SIIBRA_USE_LOCAL_SNAPSPOT,
26
- )
27
- from .. import __version__
28
-
29
17
  import json
30
18
  from zipfile import ZipFile
31
19
  import requests
32
20
  import os
33
- from nibabel import Nifti1Image, GiftiImage, streamlines, freesurfer
34
- from skimage import io as skimage_io
35
21
  import gzip
36
22
  from io import BytesIO
37
23
  import urllib.parse
38
- import pandas as pd
39
- import numpy as np
40
24
  from typing import List, Callable, TYPE_CHECKING
41
25
  from enum import Enum
42
26
  from functools import wraps
43
27
  from time import sleep
44
28
  import sys
29
+
45
30
  from filelock import FileLock as Lock
31
+ import numpy as np
32
+ import pandas as pd
33
+ from skimage import io as skimage_io
34
+ from nibabel import Nifti1Image, GiftiImage, streamlines, freesurfer
35
+
36
+ from . import exceptions as _exceptions
37
+ from .cache import CACHE, cache_user_fn
38
+ from .. import __version__
39
+ from ..commons import (
40
+ logger,
41
+ HBP_AUTH_TOKEN,
42
+ KEYCLOAK_CLIENT_ID,
43
+ KEYCLOAK_CLIENT_SECRET,
44
+ siibra_tqdm,
45
+ SIIBRA_USE_LOCAL_SNAPSPOT,
46
+ )
47
+
46
48
  if TYPE_CHECKING:
47
49
  from .repositories import GitlabConnector
48
50
 
@@ -91,7 +93,7 @@ DECODERS = {
91
93
  }
92
94
 
93
95
 
94
- def find_suitiable_decoder(url: str) -> Callable:
96
+ def find_suitable_decoder(url: str) -> Callable:
95
97
  """
96
98
  By supplying a url or a filename, obtain a suitable decoder function
97
99
  for siibra to digest based on predefined DECODERS. An extra layer of
@@ -108,7 +110,7 @@ def find_suitiable_decoder(url: str) -> Callable:
108
110
  """
109
111
  urlpath = urllib.parse.urlsplit(url).path
110
112
  if urlpath.endswith(".gz"):
111
- dec = find_suitiable_decoder(urlpath[:-3])
113
+ dec = find_suitable_decoder(urlpath[:-3])
112
114
  if dec is None:
113
115
  return lambda b: gzip.decompress(b)
114
116
  else:
@@ -183,7 +185,7 @@ class HttpRequest:
183
185
  ----------
184
186
  func : Callable, default: None
185
187
  """
186
- self.func = func or find_suitiable_decoder(self.url)
188
+ self.func = func or find_suitable_decoder(self.url)
187
189
 
188
190
  @property
189
191
  def cached(self):
@@ -277,7 +279,7 @@ class FileLoader(HttpRequest):
277
279
  def __init__(self, filepath, func=None):
278
280
  HttpRequest.__init__(
279
281
  self, filepath, refresh=False,
280
- func=func or find_suitiable_decoder(filepath)
282
+ func=func or find_suitable_decoder(filepath)
281
283
  )
282
284
  self.cachefile = filepath
283
285
 
@@ -291,7 +293,7 @@ class ZipfileRequest(HttpRequest):
291
293
  def __init__(self, url, filename, func=None, refresh=False):
292
294
  HttpRequest.__init__(
293
295
  self, url, refresh=refresh,
294
- func=func or find_suitiable_decoder(filename)
296
+ func=func or find_suitable_decoder(filename)
295
297
  )
296
298
  self.filename = filename
297
299
 
@@ -388,7 +390,7 @@ class EbrainsRequest(HttpRequest):
388
390
  ), # if explicitly enabled by env var, do not raise
389
391
  ]
390
392
  ):
391
- raise EbrainsAuthenticationError(
393
+ raise _exceptions.EbrainsAuthenticationError(
392
394
  "sys.stdout is not tty, SIIBRA_ENABLE_DEVICE_FLOW is not set,"
393
395
  "and not running in a notebook. Are you running in batch mode?"
394
396
  )
@@ -444,7 +446,7 @@ class EbrainsRequest(HttpRequest):
444
446
  f"exceeded max attempts: {cls._IAM_DEVICE_MAXTRIES}, aborting..."
445
447
  )
446
448
  logger.error(message)
447
- raise EbrainsAuthenticationError(message)
449
+ raise _exceptions.EbrainsAuthenticationError(message)
448
450
  attempt_number += 1
449
451
  resp = requests.post(
450
452
  url=cls._IAM_TOKEN_ENDPOINT,
@@ -470,7 +472,7 @@ class EbrainsRequest(HttpRequest):
470
472
  logger.debug(f"400 error: {resp.content}")
471
473
  continue
472
474
 
473
- raise EbrainsAuthenticationError(resp.content)
475
+ raise _exceptions.EbrainsAuthenticationError(resp.content)
474
476
 
475
477
  @classmethod
476
478
  def set_token(cls, token):
@@ -14,11 +14,10 @@
14
14
  # limitations under the License.
15
15
  """Abbreviations and aliases."""
16
16
 
17
- from ..commons import InstanceTable
18
-
19
17
  import json
20
18
  from os import path
21
19
 
20
+ from ..commons import InstanceTable
22
21
 
23
22
  RT_DIR = path.dirname(__file__)
24
23
 
@@ -14,10 +14,11 @@
14
14
  # limitations under the License.
15
15
  """Package handling variety of volumes and volume operations"""
16
16
 
17
+ from typing import List, Union
18
+
19
+ import numpy as np
20
+
17
21
  from .parcellationmap import Map
18
22
  from .providers import provider
19
23
  from .volume import from_array, from_file, from_pointcloud, from_nifti, Volume
20
-
21
24
  from ..commons import logger
22
- from typing import List, Union
23
- import numpy as np