siibra 1.0a14__py3-none-any.whl → 1.0.1a0__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 (80) hide show
  1. siibra/VERSION +1 -1
  2. siibra/__init__.py +15 -5
  3. siibra/commons.py +3 -48
  4. siibra/configuration/__init__.py +1 -1
  5. siibra/configuration/configuration.py +1 -1
  6. siibra/configuration/factory.py +164 -127
  7. siibra/core/__init__.py +1 -1
  8. siibra/core/assignment.py +1 -1
  9. siibra/core/atlas.py +24 -17
  10. siibra/core/concept.py +18 -9
  11. siibra/core/parcellation.py +76 -55
  12. siibra/core/region.py +163 -183
  13. siibra/core/space.py +3 -1
  14. siibra/core/structure.py +1 -2
  15. siibra/exceptions.py +17 -1
  16. siibra/experimental/contour.py +6 -6
  17. siibra/experimental/patch.py +2 -2
  18. siibra/experimental/plane3d.py +8 -8
  19. siibra/explorer/__init__.py +1 -1
  20. siibra/explorer/url.py +15 -0
  21. siibra/explorer/util.py +1 -1
  22. siibra/features/__init__.py +1 -1
  23. siibra/features/anchor.py +13 -14
  24. siibra/features/connectivity/__init__.py +1 -1
  25. siibra/features/connectivity/functional_connectivity.py +1 -1
  26. siibra/features/connectivity/regional_connectivity.py +7 -5
  27. siibra/features/connectivity/streamline_counts.py +1 -1
  28. siibra/features/connectivity/streamline_lengths.py +1 -1
  29. siibra/features/connectivity/tracing_connectivity.py +1 -1
  30. siibra/features/dataset/__init__.py +1 -1
  31. siibra/features/dataset/ebrains.py +1 -1
  32. siibra/features/feature.py +50 -28
  33. siibra/features/image/__init__.py +1 -1
  34. siibra/features/image/image.py +18 -13
  35. siibra/features/image/sections.py +1 -1
  36. siibra/features/image/volume_of_interest.py +1 -1
  37. siibra/features/tabular/__init__.py +1 -1
  38. siibra/features/tabular/bigbrain_intensity_profile.py +2 -2
  39. siibra/features/tabular/cell_density_profile.py +102 -66
  40. siibra/features/tabular/cortical_profile.py +5 -3
  41. siibra/features/tabular/gene_expression.py +1 -1
  42. siibra/features/tabular/layerwise_bigbrain_intensities.py +1 -1
  43. siibra/features/tabular/layerwise_cell_density.py +8 -25
  44. siibra/features/tabular/receptor_density_fingerprint.py +5 -3
  45. siibra/features/tabular/receptor_density_profile.py +5 -3
  46. siibra/features/tabular/regional_timeseries_activity.py +7 -5
  47. siibra/features/tabular/tabular.py +5 -3
  48. siibra/livequeries/__init__.py +1 -1
  49. siibra/livequeries/allen.py +46 -20
  50. siibra/livequeries/bigbrain.py +9 -9
  51. siibra/livequeries/ebrains.py +1 -1
  52. siibra/livequeries/query.py +1 -2
  53. siibra/locations/__init__.py +10 -10
  54. siibra/locations/boundingbox.py +77 -38
  55. siibra/locations/location.py +12 -4
  56. siibra/locations/point.py +14 -9
  57. siibra/locations/{pointset.py → pointcloud.py} +69 -27
  58. siibra/retrieval/__init__.py +1 -1
  59. siibra/retrieval/cache.py +1 -1
  60. siibra/retrieval/datasets.py +1 -1
  61. siibra/retrieval/exceptions/__init__.py +1 -1
  62. siibra/retrieval/repositories.py +10 -27
  63. siibra/retrieval/requests.py +20 -3
  64. siibra/vocabularies/__init__.py +1 -1
  65. siibra/volumes/__init__.py +2 -2
  66. siibra/volumes/parcellationmap.py +121 -94
  67. siibra/volumes/providers/__init__.py +1 -1
  68. siibra/volumes/providers/freesurfer.py +1 -1
  69. siibra/volumes/providers/gifti.py +1 -1
  70. siibra/volumes/providers/neuroglancer.py +68 -42
  71. siibra/volumes/providers/nifti.py +18 -28
  72. siibra/volumes/providers/provider.py +2 -2
  73. siibra/volumes/sparsemap.py +128 -247
  74. siibra/volumes/volume.py +252 -65
  75. {siibra-1.0a14.dist-info → siibra-1.0.1a0.dist-info}/METADATA +17 -4
  76. siibra-1.0.1a0.dist-info/RECORD +84 -0
  77. {siibra-1.0a14.dist-info → siibra-1.0.1a0.dist-info}/WHEEL +1 -1
  78. siibra-1.0a14.dist-info/RECORD +0 -84
  79. {siibra-1.0a14.dist-info → siibra-1.0.1a0.dist-info}/LICENSE +0 -0
  80. {siibra-1.0a14.dist-info → siibra-1.0.1a0.dist-info}/top_level.txt +0 -0
@@ -13,22 +13,22 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
- from ..locations import point, pointset, boundingbox
16
+ from ..locations import point, pointcloud, boundingbox
17
17
 
18
18
  import numpy as np
19
19
 
20
20
 
21
- class Contour(pointset.PointSet):
21
+ class Contour(pointcloud.PointCloud):
22
22
  """
23
- A PointSet that represents a contour line.
23
+ A PointCloud that represents a contour line.
24
24
  The only difference is that the point order is relevant,
25
25
  and consecutive points are thought as being connected by an edge.
26
26
 
27
- In fact, PointSet assumes order as well, but no connections between points.
27
+ In fact, PointCloud assumes order as well, but no connections between points.
28
28
  """
29
29
 
30
30
  def __init__(self, coordinates, space=None, sigma_mm=0, labels: list = None):
31
- pointset.PointSet.__init__(self, coordinates, space, sigma_mm, labels)
31
+ pointcloud.PointCloud.__init__(self, coordinates, space, sigma_mm, labels)
32
32
 
33
33
  def crop(self, voi: boundingbox.BoundingBox):
34
34
  """
@@ -45,7 +45,7 @@ class Contour(pointset.PointSet):
45
45
  cropped = self.intersection(voi)
46
46
 
47
47
  if cropped is not None and not isinstance(cropped, point.Point):
48
- assert isinstance(cropped, pointset.PointSet)
48
+ assert isinstance(cropped, pointcloud.PointCloud)
49
49
  # Identifiy contour splits are by discontinuouities ("jumps")
50
50
  # of their labels, which denote positions in the original contour
51
51
  jumps = np.diff([self.labels.index(lb) for lb in cropped.labels])
@@ -14,7 +14,7 @@
14
14
  # limitations under the License.
15
15
 
16
16
  from ..volumes import volume
17
- from ..locations import pointset, boundingbox
17
+ from ..locations import pointcloud, boundingbox
18
18
  from ..commons import translation_matrix, y_rotation_matrix
19
19
 
20
20
  import numpy as np
@@ -24,7 +24,7 @@ from nilearn import image
24
24
 
25
25
  class Patch:
26
26
 
27
- def __init__(self, corners: pointset.PointSet):
27
+ def __init__(self, corners: pointcloud.PointCloud):
28
28
  """Construct a patch in physical coordinates.
29
29
  As of now, only patches aligned in the y plane of the physical space
30
30
  are supported."""
@@ -15,7 +15,7 @@
15
15
 
16
16
  from . import contour
17
17
  from . import patch
18
- from ..locations import point, pointset
18
+ from ..locations import point, pointcloud
19
19
  from ..volumes import volume
20
20
 
21
21
  import numpy as np
@@ -93,9 +93,9 @@ class Plane3D:
93
93
  and an Mx3 array "faces" of face definitions.
94
94
  Each row in the face array corresponds to the three indices of vertices making up the
95
95
  triangle.
96
- The result is a list of contour segments, each represented as a PointSet
96
+ The result is a list of contour segments, each represented as a PointCloud
97
97
  holding the ordered list of contour points.
98
- The point labels in each "contour" PointSet hold the index of the face in the
98
+ The point labels in each "contour" PointCloud hold the index of the face in the
99
99
  mesh which made up each contour point.
100
100
  """
101
101
 
@@ -185,17 +185,17 @@ class Plane3D:
185
185
 
186
186
  return result
187
187
 
188
- def project_points(self, points: pointset.PointSet):
188
+ def project_points(self, points: pointcloud.PointCloud):
189
189
  """projects the given points onto the plane."""
190
190
  assert self.space == points.space
191
191
  XYZ = points.coordinates
192
192
  N = XYZ.shape[0]
193
193
  dists = np.dot(self._n, XYZ.T) - self._d
194
- return pointset.PointSet(
194
+ return pointcloud.PointCloud(
195
195
  XYZ - np.tile(self._n, (N, 1)) * dists[:, np.newaxis], space=self.space
196
196
  )
197
197
 
198
- def get_enclosing_patch(self, points: pointset.PointSet, margin=[0.5, 0.5]):
198
+ def get_enclosing_patch(self, points: pointcloud.PointCloud, margin=[0.5, 0.5]):
199
199
  """
200
200
  Computes the enclosing patch in the given plane
201
201
  which contains the projections of the given points.
@@ -225,7 +225,7 @@ class Plane3D:
225
225
 
226
226
  m0, m1 = margin
227
227
  w = np.linalg.norm(p3 - p2)
228
- corners = pointset.PointSet(
228
+ corners = pointcloud.PointCloud(
229
229
  [
230
230
  p1 + (w / 2 + m1) * v2 + m0 * v1,
231
231
  p0 + (w / 2 + m1) * v2 - m0 * v1,
@@ -249,7 +249,7 @@ class Plane3D:
249
249
  assert isinstance(image, volume.Volume)
250
250
  im_lowres = image.fetch(resolution_mm=1)
251
251
  plane_dims = np.where(np.argsort(im_lowres.shape) < 2)[0]
252
- voxels = pointset.PointSet(
252
+ voxels = pointcloud.PointCloud(
253
253
  np.vstack(([0, 0, 0], np.identity(3)[plane_dims])), space=None
254
254
  )
255
255
  points = voxels.transform(im_lowres.affine, space=image.space)
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2023
1
+ # Copyright 2018-2024
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
siibra/explorer/url.py CHANGED
@@ -1,3 +1,18 @@
1
+ # Copyright 2018-2024
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
+
1
16
  from typing import Optional, TYPE_CHECKING
2
17
  from urllib.parse import quote_plus
3
18
  from numpy import int32
siibra/explorer/util.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2023
1
+ # Copyright 2018-2024
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2021
1
+ # Copyright 2018-2024
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
siibra/features/anchor.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2023
1
+ # Copyright 2018-2024
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,7 +19,7 @@ from ..commons import Species, logger
19
19
  from ..core.structure import BrainStructure
20
20
  from ..core.assignment import AnatomicalAssignment, Qualification
21
21
  from ..locations.location import Location
22
- from ..core.parcellation import Parcellation
22
+ from ..core.parcellation import Parcellation, find_regions
23
23
  from ..core.region import Region
24
24
  from ..core.space import Space
25
25
  from ..exceptions import SpaceWarpingFailedError
@@ -126,13 +126,13 @@ class AnatomicalAnchor:
126
126
  # decode the region specification into a dict of region objects and assignment qualifications
127
127
  regions = {
128
128
  region: Qualification.EXACT
129
- for region in Parcellation.find_regions(self._regionspec)
129
+ for region in find_regions(self._regionspec, filter_children=True, find_topmost=False)
130
130
  if region.species in self.species
131
131
  }
132
132
  # add more regions from possible aliases of the region spec
133
133
  for alt_species, aliases in self.region_aliases.items():
134
134
  for alias_regionspec, qualificationspec in aliases.items():
135
- for r in Parcellation.find_regions(alias_regionspec):
135
+ for r in find_regions(alias_regionspec, filter_children=True, find_topmost=False):
136
136
  if r.species != alt_species:
137
137
  continue
138
138
  if r not in regions:
@@ -156,18 +156,17 @@ class AnatomicalAnchor:
156
156
  else:
157
157
  return region + separator + location
158
158
 
159
- def assign(self, concept: BrainStructure, restrict_space: bool = False) -> AnatomicalAssignment:
159
+ def assign(self, concept: Union[BrainStructure, Space]) -> AnatomicalAssignment:
160
160
  """
161
161
  Match this anchor to a query concept. Assignments are cached at runtime,
162
162
  so repeated assignment with the same concept will be cheap.
163
163
  """
164
- if (
165
- restrict_space
166
- and self.location is not None
167
- and isinstance(concept, Location)
168
- and not self.location.space.matches(concept.space)
169
- ):
170
- return []
164
+ if isinstance(concept, Space):
165
+ if self.location is not None and self.location.space.matches(concept):
166
+ return [AnatomicalAssignment(concept, self.location, Qualification.CONTAINED)]
167
+ else:
168
+ return []
169
+
171
170
  if concept not in self._assignments:
172
171
  assignments: List[AnatomicalAssignment] = []
173
172
  if self.location is not None:
@@ -184,8 +183,8 @@ class AnatomicalAnchor:
184
183
  else None
185
184
  return self._assignments[concept]
186
185
 
187
- def matches(self, concept: BrainStructure, restrict_space: bool = False) -> bool:
188
- return len(self.assign(concept, restrict_space)) > 0
186
+ def matches(self, concept: Union[BrainStructure, Space]) -> bool:
187
+ return len(self.assign(concept)) > 0
189
188
 
190
189
  def represented_parcellations(self) -> List[Parcellation]:
191
190
  """
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2021
1
+ # Copyright 2018-2024
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2021
1
+ # Copyright 2018-2024
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2021
1
+ # Copyright 2018-2024
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,7 +21,7 @@ from .. import anchor as _anchor
21
21
 
22
22
  from ...commons import logger, QUIET, siibra_tqdm
23
23
  from ...core import region as _region
24
- from ...locations import pointset
24
+ from ...locations import pointcloud
25
25
  from ...retrieval.repositories import RepositoryConnector
26
26
  from ...retrieval.requests import HttpRequest
27
27
 
@@ -58,7 +58,8 @@ class RegionalConnectivity(Feature, Compoundable):
58
58
  datasets: list = [],
59
59
  subject: str = "average",
60
60
  feature: str = None,
61
- id: str = None
61
+ id: str = None,
62
+ prerelease: bool = False,
62
63
  ):
63
64
  """
64
65
  Construct a parcellation-averaged connectivity matrix.
@@ -92,7 +93,8 @@ class RegionalConnectivity(Feature, Compoundable):
92
93
  description=description,
93
94
  anchor=anchor,
94
95
  datasets=datasets,
95
- id=id
96
+ id=id,
97
+ prerelease=prerelease
96
98
  )
97
99
  self.cohort = cohort.upper()
98
100
  if isinstance(connector, str) and connector:
@@ -438,7 +440,7 @@ class RegionalConnectivity(Feature, Compoundable):
438
440
  found = [r for r in region if r.name in all_centroids]
439
441
  assert len(found) > 0
440
442
  result.append(
441
- tuple(pointset.PointSet(
443
+ tuple(pointcloud.PointCloud(
442
444
  [all_centroids[r.name] for r in found], space=space
443
445
  ).centroid)
444
446
  )
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2021
1
+ # Copyright 2018-2024
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2021
1
+ # Copyright 2018-2024
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2021
1
+ # Copyright 2018-2024
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2021
1
+ # Copyright 2018-2024
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2021
1
+ # Copyright 2018-2024
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2023
1
+ # Copyright 2018-2024
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -99,7 +99,8 @@ class Feature:
99
99
  description: str,
100
100
  anchor: _anchor.AnatomicalAnchor,
101
101
  datasets: List['TypeDataset'] = [],
102
- id: str = None
102
+ id: str = None,
103
+ prerelease: bool = False,
103
104
  ):
104
105
  """
105
106
  Parameters
@@ -117,6 +118,7 @@ class Feature:
117
118
  self._anchor_cached = anchor
118
119
  self.datasets = datasets
119
120
  self._id = id
121
+ self._prerelease = prerelease
120
122
 
121
123
  @property
122
124
  def modality(self):
@@ -170,12 +172,19 @@ class Feature:
170
172
 
171
173
  @property
172
174
  def LICENSE(self) -> str:
173
- licenses = {ds.LICENSE for ds in self.datasets if ds.LICENSE}
174
- if not licenses:
175
- return "No license information is found."
176
- if len(licenses) == 1:
177
- return next(iter(licenses))
178
- logger.info("Found multiple licenses corresponding to datasets.")
175
+ licenses = []
176
+ for ds in self.datasets:
177
+ if ds.LICENSE is None or ds.LICENSE == "No license information is found.":
178
+ continue
179
+ if isinstance(ds.LICENSE, str):
180
+ licenses.append(ds.LICENSE)
181
+ if isinstance(ds.LICENSE, list):
182
+ licenses.extend(ds.LICENSE)
183
+ if len(licenses) == 0:
184
+ logger.warning("No license information is found.")
185
+ return ""
186
+ if len(licenses) > 1:
187
+ logger.info("Found multiple licenses corresponding to datasets.")
179
188
  return '\n'.join(licenses)
180
189
 
181
190
  @property
@@ -199,7 +208,8 @@ class Feature:
199
208
  def name(self):
200
209
  """Returns a short human-readable name of this feature."""
201
210
  readable_class_name = sub("([a-z])([A-Z])", r"\g<1> \g<2>", self.__class__.__name__)
202
- return sub("([b,B]ig [b,B]rain)", "BigBrain", readable_class_name)
211
+ name_ = sub("([b,B]ig [b,B]rain)", "BigBrain", readable_class_name)
212
+ return name_ if not self._prerelease else f"[PRERELEASE] {name_}"
203
213
 
204
214
  @classmethod
205
215
  def _get_instances(cls, **kwargs) -> List['Feature']:
@@ -241,13 +251,16 @@ class Feature:
241
251
  """ Removes all instantiated object instances"""
242
252
  cls._preconfigured_instances = None
243
253
 
244
- def matches(self, concept: structure.BrainStructure, restrict_space: bool = False) -> bool:
254
+ def matches(
255
+ self,
256
+ concept: Union[structure.BrainStructure, space.Space],
257
+ ) -> bool:
245
258
  """
246
259
  Match the features anatomical anchor agains the given query concept.
247
260
  Record the most recently matched concept for inspection by the caller.
248
261
  """
249
262
  # TODO: storing the last matched concept. It is not ideal, might cause problems in multithreading
250
- if self.anchor and self.anchor.matches(concept, restrict_space):
263
+ if self.anchor and self.anchor.matches(concept):
251
264
  self.anchor._last_matched_concept = concept
252
265
  return True
253
266
  self.anchor._last_matched_concept = None
@@ -268,13 +281,18 @@ class Feature:
268
281
  if self._id:
269
282
  return self._id
270
283
 
284
+ if self._prerelease:
285
+ name_ = self.name.replace("[PRERELEASE] ", "")
286
+ else:
287
+ name_ = self.name
288
+
271
289
  prefix = ''
272
290
  for ds in self.datasets:
273
291
  if hasattr(ds, "id"):
274
292
  prefix = ds.id + '--'
275
293
  break
276
294
  return prefix + md5(
277
- f"{self.name} - {self.anchor}".encode("utf-8")
295
+ f"{name_} - {self.anchor}".encode("utf-8")
278
296
  ).hexdigest()
279
297
 
280
298
  def _to_zip(self, fh: ZipFile):
@@ -468,7 +486,10 @@ class Feature:
468
486
  f"objects linked to {str(concept)}{argstr}"
469
487
  )
470
488
  q = QueryType(**kwargs)
471
- features = q.query(concept)
489
+ if isinstance(concept, space.Space):
490
+ features = q.query(concept.get_template())
491
+ else:
492
+ features = q.query(concept)
472
493
  live_instances.extend(
473
494
  Feature._wrap_livequery_feature(f, Feature._serialize_query_context(f, concept))
474
495
  for f in features
@@ -479,9 +500,8 @@ class Feature:
479
500
  @classmethod
480
501
  def _match(
481
502
  cls,
482
- concept: structure.BrainStructure,
503
+ concept: Union[structure.BrainStructure, space.Space],
483
504
  feature_type: Union[str, Type['Feature'], list],
484
- restrict_space: bool = False,
485
505
  **kwargs
486
506
  ) -> List['Feature']:
487
507
  """
@@ -500,10 +520,6 @@ class Feature:
500
520
  An anatomical concept, typically a brain region or parcellation.
501
521
  feature_type: subclass of Feature, str
502
522
  specififies the type of features ("modality")
503
- restrict_space: bool: default: False
504
- If true, will skip features anchored at spatial locations of
505
- different spaces than the concept. Requires concept to be a
506
- Location.
507
523
  """
508
524
  if isinstance(feature_type, list):
509
525
  # a list of feature types is given, collect match results on those
@@ -513,7 +529,7 @@ class Feature:
513
529
  )
514
530
  return list(dict.fromkeys(
515
531
  sum((
516
- cls._match(concept, t, restrict_space, **kwargs) for t in feature_type
532
+ cls._match(concept, t, **kwargs) for t in feature_type
517
533
  ), [])
518
534
  ))
519
535
 
@@ -530,15 +546,15 @@ class Feature:
530
546
  f"'{feature_type}' decoded as feature type/s: "
531
547
  f"{[c.__name__ for c in ftype_candidates]}."
532
548
  )
533
- return cls._match(concept, ftype_candidates, restrict_space, **kwargs)
549
+ return cls._match(concept, ftype_candidates, **kwargs)
534
550
 
535
551
  assert issubclass(feature_type, Feature)
536
552
 
537
553
  # At this stage, no recursion is needed.
538
554
  # We expect a specific supported feature type is to be matched now.
539
- if not isinstance(concept, structure.BrainStructure):
555
+ if not isinstance(concept, (structure.BrainStructure, space.Space)):
540
556
  raise ValueError(
541
- f"{concept.__class__.__name__} cannot be used for feature queries as it is not a BrainStructure type."
557
+ f"{concept.__class__.__name__} cannot be used for feature queries as it is not a `BrainStructure` or a `Space` type."
542
558
  )
543
559
 
544
560
  # Collect any preconfigured instances of the requested feature type
@@ -556,7 +572,7 @@ class Feature:
556
572
  total=len(instances),
557
573
  disable=(not instances)
558
574
  )
559
- if f.matches(concept, restrict_space)
575
+ if f.matches(concept)
560
576
  ]
561
577
 
562
578
  # Then run any registered live queries for the requested feature type
@@ -721,7 +737,7 @@ class CompoundFeature(Feature):
721
737
  def __init__(
722
738
  self,
723
739
  elements: List['Feature'],
724
- queryconcept: Union[region.Region, parcellation.Parcellation, space.Space]
740
+ queryconcept: Union[region.Region, parcellation.Parcellation, space.Space],
725
741
  ):
726
742
  """
727
743
  A compound of several features of the same type with an anchor created
@@ -754,7 +770,8 @@ class CompoundFeature(Feature):
754
770
  modality=modality,
755
771
  description="\n".join({f.description for f in elements}),
756
772
  anchor=self._feature_type._merge_anchors([f.anchor for f in elements]),
757
- datasets=list(dict.fromkeys([ds for f in elements for ds in f.datasets]))
773
+ datasets=list(dict.fromkeys([ds for f in elements for ds in f.datasets])),
774
+ prerelease=all(f._prerelease for f in elements),
758
775
  )
759
776
  self._queryconcept = queryconcept
760
777
  self._merged_feature_cached = None
@@ -830,16 +847,21 @@ class CompoundFeature(Feature):
830
847
  for k, v in self._compounding_attributes.items()
831
848
  if k != 'modality'
832
849
  ])
833
- return f"{len(self)} {readable_feature_type} features{f' {groupby}' if groupby else ''}"
850
+ cf_name = f"{len(self)} {readable_feature_type} features{f' {groupby}' if groupby else ''}"
851
+ return cf_name if not self._prerelease else f"[PRERELEASE] {cf_name}"
834
852
 
835
853
  @property
836
854
  def id(self) -> str:
855
+ if self._prerelease:
856
+ name_ = self.name.replace("[PRERELEASE] ", "")
857
+ else:
858
+ name_ = self.name
837
859
  return "::".join((
838
860
  "cf0",
839
861
  f"{self._feature_type.__name__}",
840
862
  self._encode_concept(self._queryconcept),
841
863
  self.datasets[0].id if self.datasets else "nodsid",
842
- md5(self.name.encode("utf-8")).hexdigest()
864
+ md5(name_.encode("utf-8")).hexdigest()
843
865
  ))
844
866
 
845
867
  def __iter__(self) -> Iterator['Feature']:
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2021
1
+ # Copyright 2018-2024
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2021
1
+ # Copyright 2018-2024
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,19 +20,19 @@ from .. import feature
20
20
  from .. import anchor as _anchor
21
21
 
22
22
  from ...volumes import volume as _volume
23
- from ...volumes.providers import provider
24
23
 
25
- from typing import List
24
+ from typing import List, TYPE_CHECKING
25
+
26
+ if TYPE_CHECKING:
27
+ from ...locations.boundingbox import BoundingBox
28
+ from ...volumes.providers import provider
26
29
 
27
30
 
28
31
  class ImageAnchor(_anchor.AnatomicalAnchor):
29
32
 
30
33
  def __init__(self, volume: _volume.Volume, region: str = None):
31
34
  _anchor.AnatomicalAnchor.__init__(
32
- self,
33
- species=volume.space.species,
34
- location=None,
35
- region=region
35
+ self, species=volume.space.species, location=None, region=region
36
36
  )
37
37
  self.volume = volume
38
38
 
@@ -42,7 +42,9 @@ class ImageAnchor(_anchor.AnatomicalAnchor):
42
42
  Loads the bounding box only if required, since it demands image data access.
43
43
  """
44
44
  if self._location_cached is None:
45
- self._location_cached = self.volume.get_boundingbox(clip=False) # use unclipped to preseve exisiting behaviour
45
+ self._location_cached = self.volume.get_boundingbox(
46
+ clip=False
47
+ ) # use unclipped to preseve exisiting behaviour
46
48
  return self._location_cached
47
49
 
48
50
  @property
@@ -60,10 +62,12 @@ class Image(feature.Feature, _volume.Volume):
60
62
  name: str,
61
63
  modality: str,
62
64
  space_spec: dict,
63
- providers: List[provider.VolumeProvider],
65
+ providers: List["provider.VolumeProvider"],
64
66
  region: str = None,
65
67
  datasets: List = [],
66
- id: str = None
68
+ bbox: "BoundingBox" = None,
69
+ id: str = None,
70
+ prerelease: bool = False,
67
71
  ):
68
72
  feature.Feature.__init__(
69
73
  self,
@@ -71,7 +75,8 @@ class Image(feature.Feature, _volume.Volume):
71
75
  description=None, # lazy implementation below!
72
76
  anchor=None, # lazy implementation below!
73
77
  datasets=datasets,
74
- id=id
78
+ id=id,
79
+ prerelease=prerelease,
75
80
  )
76
81
 
77
82
  _volume.Volume.__init__(
@@ -80,6 +85,7 @@ class Image(feature.Feature, _volume.Volume):
80
85
  providers=providers,
81
86
  name=name,
82
87
  datasets=datasets,
88
+ bbox=bbox,
83
89
  )
84
90
 
85
91
  self._anchor_cached = ImageAnchor(self, region=region)
@@ -104,7 +110,6 @@ class Image(feature.Feature, _volume.Volume):
104
110
  def description(self):
105
111
  if self._description_cached is None:
106
112
  self._description_cached = (
107
- f"Image feature with modality {self.modality} "
108
- f"at {self.anchor}"
113
+ f"Image feature with modality {self.modality} " f"at {self.anchor}"
109
114
  )
110
115
  return self._description_cached
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2021
1
+ # Copyright 2018-2024
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2021
1
+ # Copyright 2018-2024
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2021
1
+ # Copyright 2018-2024
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2021
1
+ # Copyright 2018-2024
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -65,7 +65,7 @@ class BigBrainIntensityProfile(
65
65
 
66
66
  @classmethod
67
67
  def _merge_anchors(cls, anchors: List['AnatomicalAnchor']):
68
- from ...locations.pointset import from_points
68
+ from ...locations.pointcloud import from_points
69
69
  from ...features.anchor import AnatomicalAnchor
70
70
 
71
71
  location = from_points([anchor.location for anchor in anchors])