siibra 1.0.1a1__py3-none-any.whl → 1.0.1a4__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 (67) hide show
  1. siibra/VERSION +1 -1
  2. siibra/__init__.py +7 -16
  3. siibra/commons.py +19 -8
  4. siibra/configuration/configuration.py +5 -6
  5. siibra/configuration/factory.py +13 -8
  6. siibra/core/__init__.py +1 -1
  7. siibra/core/assignment.py +19 -7
  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/explorer/url.py +2 -2
  15. siibra/features/anchor.py +3 -7
  16. siibra/features/connectivity/regional_connectivity.py +51 -40
  17. siibra/features/dataset/ebrains.py +1 -1
  18. siibra/features/feature.py +29 -20
  19. siibra/features/image/__init__.py +6 -3
  20. siibra/features/image/image.py +2 -4
  21. siibra/features/image/sections.py +81 -2
  22. siibra/features/image/volume_of_interest.py +8 -7
  23. siibra/features/tabular/__init__.py +1 -1
  24. siibra/features/tabular/bigbrain_intensity_profile.py +2 -1
  25. siibra/features/tabular/cell_density_profile.py +8 -9
  26. siibra/features/tabular/cortical_profile.py +6 -6
  27. siibra/features/tabular/gene_expression.py +34 -16
  28. siibra/features/tabular/layerwise_bigbrain_intensities.py +4 -3
  29. siibra/features/tabular/layerwise_cell_density.py +83 -24
  30. siibra/features/tabular/receptor_density_fingerprint.py +34 -9
  31. siibra/features/tabular/receptor_density_profile.py +1 -2
  32. siibra/features/tabular/regional_timeseries_activity.py +7 -7
  33. siibra/features/tabular/tabular.py +14 -7
  34. siibra/livequeries/allen.py +23 -22
  35. siibra/livequeries/bigbrain.py +239 -51
  36. siibra/livequeries/ebrains.py +13 -10
  37. siibra/livequeries/query.py +3 -3
  38. siibra/locations/__init__.py +17 -8
  39. siibra/locations/boundingbox.py +10 -8
  40. siibra/{experimental/plane3d.py → locations/experimental.py} +113 -13
  41. siibra/locations/location.py +17 -13
  42. siibra/locations/point.py +14 -19
  43. siibra/locations/pointcloud.py +57 -12
  44. siibra/retrieval/cache.py +1 -0
  45. siibra/retrieval/datasets.py +19 -13
  46. siibra/retrieval/repositories.py +10 -11
  47. siibra/retrieval/requests.py +26 -24
  48. siibra/vocabularies/__init__.py +1 -2
  49. siibra/volumes/__init__.py +4 -3
  50. siibra/volumes/parcellationmap.py +33 -17
  51. siibra/volumes/providers/freesurfer.py +4 -4
  52. siibra/volumes/providers/gifti.py +4 -4
  53. siibra/volumes/providers/neuroglancer.py +19 -22
  54. siibra/volumes/providers/nifti.py +6 -6
  55. siibra/volumes/providers/provider.py +3 -2
  56. siibra/volumes/sparsemap.py +19 -26
  57. siibra/volumes/volume.py +21 -28
  58. {siibra-1.0.1a1.dist-info → siibra-1.0.1a4.dist-info}/METADATA +37 -17
  59. siibra-1.0.1a4.dist-info/RECORD +80 -0
  60. {siibra-1.0.1a1.dist-info → siibra-1.0.1a4.dist-info}/WHEEL +1 -1
  61. siibra/experimental/__init__.py +0 -19
  62. siibra/experimental/contour.py +0 -61
  63. siibra/experimental/cortical_profile_sampler.py +0 -57
  64. siibra/experimental/patch.py +0 -98
  65. siibra-1.0.1a1.dist-info/RECORD +0 -84
  66. {siibra-1.0.1a1.dist-info → siibra-1.0.1a4.dist-info/licenses}/LICENSE +0 -0
  67. {siibra-1.0.1a1.dist-info → siibra-1.0.1a4.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,34 @@ 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
- if isinstance(self._space_spec, dict):
55
+ if self._space_spec is None:
56
+ return None
57
+ elif isinstance(self._space_spec, _space.Space):
58
+ self._space_cached = self._space_spec
59
+ elif isinstance(self._space_spec, dict):
58
60
  spec = self._space_spec.get("@id") or self._space_spec.get("name")
59
- self._space_cached = Space.get_instance(spec)
61
+ self._space_cached = _space.Space.get_instance(spec)
62
+ elif isinstance(self._space_spec, str):
63
+ self._space_cached = _space.Space.get_instance(self._space_spec)
60
64
  else:
61
- self._space_cached = Space.get_instance(self._space_spec)
65
+ raise ValueError(f"Invalid space spec type: '{type(self._space_spec)}'")
62
66
  return self._space_cached
63
67
 
64
68
  @abstractmethod
65
- def warp(self, space):
69
+ def warp(self, space: Union[str, "_space.Space"]):
66
70
  """Generates a new location by warping the
67
71
  current one into another reference space."""
68
72
  pass
69
73
 
70
74
  @abstractmethod
71
- def transform(self, affine: np.ndarray, space=None):
75
+ def transform(self, affine: np.ndarray, space: Union[str, "_space.Space", None] = None):
72
76
  """Returns a new location obtained by transforming the
73
77
  reference coordinates of this one with the given affine matrix.
74
78
 
siibra/locations/point.py CHANGED
@@ -14,19 +14,22 @@
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
- from typing import Tuple, Union
22
+ from typing import Tuple, Union, Dict, TYPE_CHECKING
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
+ if TYPE_CHECKING:
32
+ from ..core.space import Space
30
33
 
31
34
 
32
35
  class Point(location.Location):
@@ -115,18 +118,17 @@ class Point(location.Location):
115
118
  def intersection(self, other: location.Location) -> "Point":
116
119
  if isinstance(other, Point):
117
120
  return self if self == other else None
118
- elif isinstance(other, pointcloud.PointCloud):
119
- return self if self in other else None
120
121
  else:
121
122
  return self if other.intersection(self) else None
122
123
 
123
- def warp(self, space):
124
+ def warp(self, space: Union[str, Dict, "Space"]):
124
125
  """
125
126
  Creates a new point by warping this point to another space
126
127
  TODO this needs to maintain the sigma parameter!
127
128
  """
128
129
  from ..core.space import Space
129
- spaceobj = Space.get_instance(space)
130
+
131
+ spaceobj = space if isinstance(space, Space) else Space.get_instance(space)
130
132
  if spaceobj == self.space:
131
133
  return self
132
134
  if any(_ not in location.Location.SPACEWARP_IDS for _ in [self.space.id, spaceobj.id]):
@@ -305,13 +307,6 @@ class Point(location.Location):
305
307
  assert 0 <= index < 3
306
308
  return self.coordinate[index]
307
309
 
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
310
  def bigbrain_section(self):
316
311
  """
317
312
  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
- from typing import List, Union, Tuple
17
+ from typing import List, Union, Tuple, Dict, TYPE_CHECKING
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,14 @@ 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
33
+
34
+ if TYPE_CHECKING:
35
+ from ..core.space import Space
37
36
 
38
37
 
39
38
  def from_points(points: List["point.Point"], newlabels: List[Union[int, float, tuple]] = None) -> "PointCloud":
@@ -122,6 +121,8 @@ class PointCloud(location.Location):
122
121
 
123
122
  if isinstance(other, PointCloud):
124
123
  intersecting_points = [p for p in self if p.coordinate in other.coordinates]
124
+ elif isinstance(other, point.Point):
125
+ return other if other in self else None
125
126
  else:
126
127
  intersecting_points = [p for p in self if p.intersects(other)]
127
128
  if len(intersecting_points) == 0:
@@ -142,9 +143,10 @@ class PointCloud(location.Location):
142
143
  def has_constant_sigma(self) -> bool:
143
144
  return len(set(self.sigma)) == 1
144
145
 
145
- def warp(self, space, chunksize=1000):
146
+ def warp(self, space: Union[str, Dict, "Space"], chunksize: int = 1000):
146
147
  """Creates a new point set by warping its points to another space"""
147
148
  from ..core.space import Space
149
+
148
150
  spaceobj = space if isinstance(space, Space) else Space.get_instance(space)
149
151
  if spaceobj == self.space:
150
152
  return self
@@ -342,3 +344,46 @@ class PointCloud(location.Location):
342
344
  logger.error("Matplotlib is not available. Label colors is disabled.")
343
345
  return None
344
346
  return colormaps.rainbow(np.linspace(0, 1, max(self.labels) + 1))
347
+
348
+
349
+ class Contour(PointCloud):
350
+ """
351
+ A PointCloud that represents a contour line.
352
+ The only difference is that the point order is relevant,
353
+ and consecutive points are thought as being connected by an edge.
354
+
355
+ In fact, PointCloud assumes order as well, but no connections between points.
356
+ """
357
+
358
+ def __init__(self, coordinates, space=None, sigma_mm=0, labels: list = None):
359
+ PointCloud.__init__(self, coordinates, space, sigma_mm, labels)
360
+
361
+ def crop(self, voi: "_boundingbox.BoundingBox") -> List["Contour"]:
362
+ """
363
+ Crop the contour with a volume of interest.
364
+ Since the contour might be split from the cropping,
365
+ returns a set of contour segments.
366
+ """
367
+ segments = []
368
+
369
+ # set the contour point labels to a linear numbering
370
+ # so we can use them after the intersection to detect splits.
371
+ old_labels = self.labels
372
+ self.labels = list(range(len(self)))
373
+ cropped = self.intersection(voi)
374
+
375
+ if cropped is not None and not isinstance(cropped, point.Point):
376
+ assert isinstance(cropped, PointCloud)
377
+ # Identify contour splits are by discontinuouities ("jumps")
378
+ # of their labels, which denote positions in the original contour
379
+ jumps = np.diff([self.labels.index(lb) for lb in cropped.labels])
380
+ splits = [0] + list(np.where(jumps > 1)[0] + 1) + [len(cropped)]
381
+ for i, j in zip(splits[:-1], splits[1:]):
382
+ segments.append(
383
+ self.__class__(cropped.coordinates[i:j, :], space=cropped.space)
384
+ )
385
+
386
+ # reset labels of the input contour points.
387
+ self.labels = old_labels
388
+
389
+ 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