siibra 1.0a14__py3-none-any.whl → 1.0a19__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 (77) hide show
  1. siibra/VERSION +1 -1
  2. siibra/__init__.py +12 -2
  3. siibra/commons.py +3 -2
  4. siibra/configuration/__init__.py +1 -1
  5. siibra/configuration/configuration.py +1 -1
  6. siibra/configuration/factory.py +164 -117
  7. siibra/core/__init__.py +1 -1
  8. siibra/core/assignment.py +1 -1
  9. siibra/core/atlas.py +4 -3
  10. siibra/core/concept.py +18 -9
  11. siibra/core/parcellation.py +9 -3
  12. siibra/core/region.py +35 -65
  13. siibra/core/space.py +3 -1
  14. siibra/core/structure.py +1 -2
  15. siibra/exceptions.py +9 -1
  16. siibra/explorer/__init__.py +1 -1
  17. siibra/explorer/url.py +15 -0
  18. siibra/explorer/util.py +1 -1
  19. siibra/features/__init__.py +1 -1
  20. siibra/features/anchor.py +1 -1
  21. siibra/features/connectivity/__init__.py +1 -1
  22. siibra/features/connectivity/functional_connectivity.py +1 -1
  23. siibra/features/connectivity/regional_connectivity.py +5 -3
  24. siibra/features/connectivity/streamline_counts.py +1 -1
  25. siibra/features/connectivity/streamline_lengths.py +1 -1
  26. siibra/features/connectivity/tracing_connectivity.py +1 -1
  27. siibra/features/dataset/__init__.py +1 -1
  28. siibra/features/dataset/ebrains.py +1 -1
  29. siibra/features/feature.py +39 -15
  30. siibra/features/image/__init__.py +1 -1
  31. siibra/features/image/image.py +18 -13
  32. siibra/features/image/sections.py +1 -1
  33. siibra/features/image/volume_of_interest.py +1 -1
  34. siibra/features/tabular/__init__.py +1 -1
  35. siibra/features/tabular/bigbrain_intensity_profile.py +1 -1
  36. siibra/features/tabular/cell_density_profile.py +5 -3
  37. siibra/features/tabular/cortical_profile.py +5 -3
  38. siibra/features/tabular/gene_expression.py +1 -1
  39. siibra/features/tabular/layerwise_bigbrain_intensities.py +1 -1
  40. siibra/features/tabular/layerwise_cell_density.py +5 -3
  41. siibra/features/tabular/receptor_density_fingerprint.py +5 -3
  42. siibra/features/tabular/receptor_density_profile.py +5 -3
  43. siibra/features/tabular/regional_timeseries_activity.py +5 -3
  44. siibra/features/tabular/tabular.py +5 -3
  45. siibra/livequeries/__init__.py +1 -1
  46. siibra/livequeries/allen.py +9 -6
  47. siibra/livequeries/bigbrain.py +1 -1
  48. siibra/livequeries/ebrains.py +1 -1
  49. siibra/livequeries/query.py +1 -1
  50. siibra/locations/__init__.py +1 -1
  51. siibra/locations/boundingbox.py +51 -17
  52. siibra/locations/location.py +12 -4
  53. siibra/locations/point.py +10 -5
  54. siibra/locations/pointset.py +45 -11
  55. siibra/retrieval/__init__.py +1 -1
  56. siibra/retrieval/cache.py +1 -1
  57. siibra/retrieval/datasets.py +1 -1
  58. siibra/retrieval/exceptions/__init__.py +1 -1
  59. siibra/retrieval/repositories.py +1 -1
  60. siibra/retrieval/requests.py +1 -1
  61. siibra/vocabularies/__init__.py +1 -1
  62. siibra/volumes/__init__.py +1 -1
  63. siibra/volumes/parcellationmap.py +38 -18
  64. siibra/volumes/providers/__init__.py +1 -1
  65. siibra/volumes/providers/freesurfer.py +1 -1
  66. siibra/volumes/providers/gifti.py +1 -1
  67. siibra/volumes/providers/neuroglancer.py +7 -7
  68. siibra/volumes/providers/nifti.py +8 -4
  69. siibra/volumes/providers/provider.py +2 -2
  70. siibra/volumes/sparsemap.py +4 -2
  71. siibra/volumes/volume.py +114 -16
  72. {siibra-1.0a14.dist-info → siibra-1.0a19.dist-info}/METADATA +3 -3
  73. siibra-1.0a19.dist-info/RECORD +84 -0
  74. {siibra-1.0a14.dist-info → siibra-1.0a19.dist-info}/WHEEL +1 -1
  75. siibra-1.0a14.dist-info/RECORD +0 -84
  76. {siibra-1.0a14.dist-info → siibra-1.0a19.dist-info}/LICENSE +0 -0
  77. {siibra-1.0a14.dist-info → siibra-1.0a19.dist-info}/top_level.txt +0 -0
siibra/core/assignment.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");
siibra/core/atlas.py CHANGED
@@ -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");
@@ -30,14 +30,15 @@ class Atlas(concept.AtlasConcept, configuration_folder="atlases"):
30
30
  spaces, as well as common functionalities of those.
31
31
  """
32
32
 
33
- def __init__(self, identifier: str, name: str, species: Species):
33
+ def __init__(self, identifier: str, name: str, species: Species, **kwargs):
34
34
  """Construct an empty atlas object with a name and identifier."""
35
35
 
36
36
  concept.AtlasConcept.__init__(
37
37
  self,
38
38
  identifier=identifier,
39
39
  name=name,
40
- species=species
40
+ species=species,
41
+ **kwargs
41
42
  )
42
43
  self._parcellation_ids: List[str] = []
43
44
  self._space_ids: List[str] = []
siibra/core/concept.py CHANGED
@@ -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");
@@ -67,7 +67,8 @@ class AtlasConcept:
67
67
  modality: str = "",
68
68
  publications: List[TypePublication] = [],
69
69
  datasets: List['TypeDataset'] = [],
70
- spec=None
70
+ spec=None,
71
+ prerelease: bool = False,
71
72
  ):
72
73
  """
73
74
  Construct a new atlas concept base object.
@@ -94,7 +95,7 @@ class AtlasConcept:
94
95
  The preconfigured specification.
95
96
  """
96
97
  self._id = identifier
97
- self.name = name
98
+ self.name = name if not prerelease else f"[PRERELEASE] {name}"
98
99
  self._species_cached = None if species is None \
99
100
  else Species.decode(species) # overwritable property implementation below
100
101
  self.shortname = shortname
@@ -104,6 +105,7 @@ class AtlasConcept:
104
105
  self.datasets = datasets
105
106
  self._spec = spec
106
107
  self._CACHED_MATCHES = {} # we cache match() function results
108
+ self._prerelease = prerelease
107
109
 
108
110
  @property
109
111
  def description(self):
@@ -116,12 +118,19 @@ class AtlasConcept:
116
118
 
117
119
  @property
118
120
  def LICENSE(self) -> str:
119
- licenses = {ds.LICENSE for ds in self.datasets if ds.LICENSE}
120
- if not licenses:
121
- return "No license information is found."
122
- if len(licenses) == 1:
123
- return next(iter(licenses))
124
- logger.info("Found multiple licenses corresponding to datasets.")
121
+ licenses = []
122
+ for ds in self.datasets:
123
+ if ds.LICENSE is None or ds.LICENSE == "No license information is found.":
124
+ continue
125
+ if isinstance(ds.LICENSE, str):
126
+ licenses.append(ds.LICENSE)
127
+ if isinstance(ds.LICENSE, list):
128
+ licenses.extend(ds.LICENSE)
129
+ if len(licenses) == 0:
130
+ logger.warning("No license information is found.")
131
+ return ""
132
+ if len(licenses) > 1:
133
+ logger.info("Found multiple licenses corresponding to datasets.")
125
134
  return '\n'.join(licenses)
126
135
 
127
136
  @property
@@ -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");
@@ -82,6 +82,7 @@ class Parcellation(region.Region, configuration_folder="parcellations"):
82
82
  modality: str = None,
83
83
  publications: list = [],
84
84
  datasets: list = [],
85
+ prerelease: bool = False,
85
86
  ):
86
87
  """
87
88
  Constructs a new parcellation object.
@@ -118,7 +119,8 @@ class Parcellation(region.Region, configuration_folder="parcellations"):
118
119
  description=description,
119
120
  publications=publications,
120
121
  datasets=datasets,
121
- modality=modality
122
+ modality=modality,
123
+ prerelease=prerelease,
122
124
  )
123
125
  self._species_cached = Species.decode(species)
124
126
  self._id = identifier
@@ -309,7 +311,11 @@ class Parcellation(region.Region, configuration_folder="parcellations"):
309
311
 
310
312
  # if there exist an exact match of region spec to region name, return
311
313
  if isinstance(regionspec, str):
312
- exact_match = [region for region in self if hasattr(region, "name") and region.name == regionspec]
314
+ exact_match = [
315
+ region
316
+ for region in self
317
+ if region.name == regionspec or region.key == regionspec
318
+ ]
313
319
  if len(exact_match) == 1:
314
320
  return exact_match[0]
315
321
  if len(exact_match) > 1:
siibra/core/region.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");
@@ -18,12 +18,11 @@ from . import concept, structure, space as _space, parcellation as _parcellation
18
18
  from .assignment import Qualification, AnatomicalAssignment
19
19
 
20
20
  from ..retrieval.cache import cache_user_fn
21
- from ..locations import location, point, pointset
21
+ from ..locations import location, pointset, boundingbox as _boundingbox
22
22
  from ..volumes import parcellationmap, volume
23
23
  from ..commons import (
24
24
  logger,
25
25
  MapType,
26
- affine_scaling,
27
26
  create_key,
28
27
  clear_name,
29
28
  InstanceTable,
@@ -37,7 +36,6 @@ import re
37
36
  import anytree
38
37
  from typing import List, Union, Iterable, Dict, Callable, Tuple
39
38
  from difflib import SequenceMatcher
40
- from dataclasses import dataclass, field
41
39
  from ebrains_drive import BucketApiClient
42
40
  import json
43
41
  from functools import wraps, reduce
@@ -49,19 +47,6 @@ REGEX_TYPE = type(re.compile("test"))
49
47
  THRESHOLD_STATISTICAL_MAPS = None
50
48
 
51
49
 
52
- @dataclass
53
- class SpatialPropCmpt:
54
- centroid: point.Point
55
- volume: int
56
-
57
-
58
- @dataclass
59
- class SpatialProp:
60
- cog: SpatialPropCmpt = None
61
- components: List[SpatialPropCmpt] = field(default_factory=list)
62
- space: _space.Space = None
63
-
64
-
65
50
  class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
66
51
  """
67
52
  Representation of a region with name and more optional attributes
@@ -85,6 +70,7 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
85
70
  datasets: list = [],
86
71
  rgb: str = None,
87
72
  spec=None,
73
+ prerelease: bool = False,
88
74
  ):
89
75
  """
90
76
  Constructs a new Region object.
@@ -122,7 +108,8 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
122
108
  modality=modality,
123
109
  publications=publications,
124
110
  datasets=datasets,
125
- spec=spec
111
+ spec=spec,
112
+ prerelease=prerelease,
126
113
  )
127
114
 
128
115
  # anytree node will take care to use this appropriately
@@ -646,17 +633,29 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
646
633
  self._ASSIGNMENT_CACHE[self, other] = regionmap.assign(other)
647
634
  return self._ASSIGNMENT_CACHE[self, other]
648
635
 
636
+ if isinstance(other, _boundingbox.BoundingBox): # volume.intersection(bbox) gets boundingbox anyway
637
+ try:
638
+ regionbbox_otherspace = self.get_boundingbox(other.space, restrict_space=False)
639
+ if regionbbox_otherspace is not None:
640
+ self._ASSIGNMENT_CACHE[self, other] = regionbbox_otherspace.assign(other)
641
+ return self._ASSIGNMENT_CACHE[self, other]
642
+ except Exception as e:
643
+ logger.debug(e)
644
+
649
645
  assignment_result = None
650
- for space in self.supported_spaces:
646
+ for targetspace in self.supported_spaces:
651
647
  try:
652
- other_warped = other.warp(space)
653
- regionmap = self.get_regional_map(space)
648
+ other_warped = other.warp(targetspace)
649
+ regionmap = self.get_regional_map(targetspace)
654
650
  assignment_result = regionmap.assign(other_warped)
655
651
  except SpaceWarpingFailedError:
656
652
  try:
657
- regionbbox_warped = self.get_boundingbox(
658
- space, restrict_space=True
659
- ).warp(other.space)
653
+ regionbbox_targetspace = self.get_boundingbox(
654
+ targetspace, restrict_space=True
655
+ )
656
+ if regionbbox_targetspace is None:
657
+ continue
658
+ regionbbox_warped = regionbbox_targetspace.warp(other.space)
660
659
  except SpaceWarpingFailedError:
661
660
  continue
662
661
  assignment_result = regionbbox_warped.assign(other)
@@ -698,7 +697,7 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
698
697
  space: _space.Space,
699
698
  maptype: MapType = MapType.LABELLED,
700
699
  threshold_statistical=None,
701
- restrict_space=False,
700
+ restrict_space=True,
702
701
  **fetch_kwargs
703
702
  ):
704
703
  """
@@ -745,7 +744,7 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
745
744
  bbox_warped = bbox.warp(spaceobj)
746
745
  except SpaceWarpingFailedError:
747
746
  continue
748
- logger.warning(
747
+ logger.debug(
749
748
  f"No bounding box for {self.name} defined in {spaceobj.name}, "
750
749
  f"warped the bounding box from {other_space.name} instead."
751
750
  )
@@ -776,7 +775,7 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
776
775
  """
777
776
  props = self.spatial_props(space)
778
777
  return pointset.PointSet(
779
- [c.centroid for c in props.components],
778
+ [c.centroid for c in props],
780
779
  space=space
781
780
  )
782
781
 
@@ -785,12 +784,10 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
785
784
  space: _space.Space,
786
785
  maptype: MapType = MapType.LABELLED,
787
786
  threshold_statistical=None,
788
- ) -> SpatialProp:
787
+ ):
789
788
  """
790
789
  Compute spatial properties for connected components of this region in the given space.
791
790
 
792
- TODO: this should go to the Volume class and just be called from here.
793
-
794
791
  Parameters
795
792
  ----------
796
793
  space: Space
@@ -808,49 +805,22 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
808
805
  Dict
809
806
  Dictionary of region's spatial properties
810
807
  """
811
- from skimage import measure
812
-
813
808
  if not isinstance(space, _space.Space):
814
809
  space = _space.Space.get_instance(space)
815
810
 
816
- result = SpatialProp(space=space)
817
-
818
- if not self.mapped_in_space(space):
819
- logger.warning(
811
+ # build binary mask of the image
812
+ try:
813
+ region_vol = self.get_regional_map(
814
+ space, maptype=maptype, threshold=threshold_statistical
815
+ )
816
+ except NoMapAvailableError:
817
+ raise ValueError(
820
818
  f"Spatial properties of {self.name} cannot be computed in {space.name}. "
821
819
  "This region is only mapped in these spaces: "
822
820
  f"{', '.join(s.name for s in self.supported_spaces)}"
823
821
  )
824
- return result
825
822
 
826
- # build binary mask of the image
827
- pimg = self.get_regional_map(
828
- space, maptype=maptype, threshold=threshold_statistical
829
- ).fetch()
830
-
831
- # determine scaling factor from voxels to cube mm
832
- scale = affine_scaling(pimg.affine)
833
-
834
- # compute properties of labelled volume
835
- A = np.asarray(pimg.get_fdata(), dtype=np.int32).squeeze()
836
- C = measure.label(A)
837
-
838
- # compute spatial properties of each connected component
839
- for label in range(1, C.max() + 1):
840
- nonzero = np.c_[np.nonzero(C == label)]
841
- result.components.append(
842
- SpatialPropCmpt(
843
- centroid=point.Point(
844
- np.dot(pimg.affine, np.r_[nonzero.mean(0), 1])[:3], space=space
845
- ),
846
- volume=nonzero.shape[0] * scale,
847
- )
848
- )
849
-
850
- # sort by volume
851
- result.components.sort(key=lambda cmp: cmp.volume, reverse=True)
852
-
853
- return result
823
+ return region_vol.compute_spatial_props()
854
824
 
855
825
  def __iter__(self):
856
826
  """
siibra/core/space.py CHANGED
@@ -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");
@@ -38,6 +38,7 @@ class Space(AtlasConcept, configuration_folder="spaces"):
38
38
  modality: str = "",
39
39
  publications: list = [],
40
40
  datasets: list = [],
41
+ prerelease: bool = False,
41
42
  ):
42
43
  """
43
44
  Constructs a new parcellation object.
@@ -75,6 +76,7 @@ class Space(AtlasConcept, configuration_folder="spaces"):
75
76
  modality=modality,
76
77
  publications=publications,
77
78
  datasets=datasets,
79
+ prerelease=prerelease,
78
80
  )
79
81
  self.volumes = volumes
80
82
  for v in self.volumes:
siibra/core/structure.py CHANGED
@@ -1,5 +1,4 @@
1
-
2
- # Copyright 2018-2023
1
+ # Copyright 2018-2024
3
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
4
3
 
5
4
  # Licensed under the Apache License, Version 2.0 (the "License");
siibra/exceptions.py CHANGED
@@ -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");
@@ -45,3 +45,11 @@ class NoVolumeFound(RuntimeError):
45
45
 
46
46
  class WarmupRegException(Exception):
47
47
  pass
48
+
49
+
50
+ class ZeroVolumeBoundingBox(Exception):
51
+ pass
52
+
53
+
54
+ class NoneCoordinateSuppliedError(ValueError):
55
+ pass
@@ -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");
@@ -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");
@@ -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:
@@ -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']:
@@ -268,13 +278,18 @@ class Feature:
268
278
  if self._id:
269
279
  return self._id
270
280
 
281
+ if self._prerelease:
282
+ name_ = self.name.replace("[PRERELEASE] ", "")
283
+ else:
284
+ name_ = self.name
285
+
271
286
  prefix = ''
272
287
  for ds in self.datasets:
273
288
  if hasattr(ds, "id"):
274
289
  prefix = ds.id + '--'
275
290
  break
276
291
  return prefix + md5(
277
- f"{self.name} - {self.anchor}".encode("utf-8")
292
+ f"{name_} - {self.anchor}".encode("utf-8")
278
293
  ).hexdigest()
279
294
 
280
295
  def _to_zip(self, fh: ZipFile):
@@ -468,7 +483,10 @@ class Feature:
468
483
  f"objects linked to {str(concept)}{argstr}"
469
484
  )
470
485
  q = QueryType(**kwargs)
471
- features = q.query(concept)
486
+ try:
487
+ features = q.query(concept)
488
+ except StopIteration:
489
+ continue
472
490
  live_instances.extend(
473
491
  Feature._wrap_livequery_feature(f, Feature._serialize_query_context(f, concept))
474
492
  for f in features
@@ -721,7 +739,7 @@ class CompoundFeature(Feature):
721
739
  def __init__(
722
740
  self,
723
741
  elements: List['Feature'],
724
- queryconcept: Union[region.Region, parcellation.Parcellation, space.Space]
742
+ queryconcept: Union[region.Region, parcellation.Parcellation, space.Space],
725
743
  ):
726
744
  """
727
745
  A compound of several features of the same type with an anchor created
@@ -754,7 +772,8 @@ class CompoundFeature(Feature):
754
772
  modality=modality,
755
773
  description="\n".join({f.description for f in elements}),
756
774
  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]))
775
+ datasets=list(dict.fromkeys([ds for f in elements for ds in f.datasets])),
776
+ prerelease=all(f._prerelease for f in elements),
758
777
  )
759
778
  self._queryconcept = queryconcept
760
779
  self._merged_feature_cached = None
@@ -830,16 +849,21 @@ class CompoundFeature(Feature):
830
849
  for k, v in self._compounding_attributes.items()
831
850
  if k != 'modality'
832
851
  ])
833
- return f"{len(self)} {readable_feature_type} features{f' {groupby}' if groupby else ''}"
852
+ cf_name = f"{len(self)} {readable_feature_type} features{f' {groupby}' if groupby else ''}"
853
+ return cf_name if not self._prerelease else f"[PRERELEASE] {cf_name}"
834
854
 
835
855
  @property
836
856
  def id(self) -> str:
857
+ if self._prerelease:
858
+ name_ = self.name.replace("[PRERELEASE] ", "")
859
+ else:
860
+ name_ = self.name
837
861
  return "::".join((
838
862
  "cf0",
839
863
  f"{self._feature_type.__name__}",
840
864
  self._encode_concept(self._queryconcept),
841
865
  self.datasets[0].id if self.datasets else "nodsid",
842
- md5(self.name.encode("utf-8")).hexdigest()
866
+ md5(name_.encode("utf-8")).hexdigest()
843
867
  ))
844
868
 
845
869
  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");