siibra 1.0a11__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 +4 -3
  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 +42 -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 +2 -2
  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 +7 -4
  71. siibra/volumes/volume.py +114 -16
  72. {siibra-1.0a11.dist-info → siibra-1.0a19.dist-info}/METADATA +3 -3
  73. siibra-1.0a19.dist-info/RECORD +84 -0
  74. {siibra-1.0a11.dist-info → siibra-1.0a19.dist-info}/WHEEL +1 -1
  75. siibra-1.0a11.dist-info/RECORD +0 -84
  76. {siibra-1.0a11.dist-info → siibra-1.0a19.dist-info}/LICENSE +0 -0
  77. {siibra-1.0a11.dist-info → siibra-1.0a19.dist-info}/top_level.txt +0 -0
@@ -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");
@@ -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");
@@ -70,7 +70,8 @@ class CellDensityProfile(
70
70
  url: str,
71
71
  anchor: _anchor.AnatomicalAnchor,
72
72
  datasets: list = [],
73
- id: str = None
73
+ id: str = None,
74
+ prerelease: bool = False,
74
75
  ):
75
76
  """
76
77
  Generate a cell density profile from a URL to a cloud folder
@@ -83,7 +84,8 @@ class CellDensityProfile(
83
84
  unit="cells / 0.1mm3",
84
85
  anchor=anchor,
85
86
  datasets=datasets,
86
- id=id
87
+ id=id,
88
+ prerelease=prerelease,
87
89
  )
88
90
  self._step = 0.01
89
91
  self._url = url
@@ -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");
@@ -57,7 +57,8 @@ class CorticalProfile(tabular.Tabular, Compoundable):
57
57
  unit: str = None,
58
58
  boundary_positions: Dict[Tuple[int, int], float] = None,
59
59
  datasets: list = [],
60
- id: str = None
60
+ id: str = None,
61
+ prerelease: bool = False,
61
62
  ):
62
63
  """Initialize profile.
63
64
 
@@ -98,7 +99,8 @@ class CorticalProfile(tabular.Tabular, Compoundable):
98
99
  anchor=anchor,
99
100
  data=None, # lazy loader below
100
101
  datasets=datasets,
101
- id=id
102
+ id=id,
103
+ prerelease=prerelease,
102
104
  )
103
105
 
104
106
  def _check_sanity(self):
@@ -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");
@@ -239,7 +239,7 @@ class GeneExpressions(
239
239
  """
240
240
  wrapwidth = kwargs.pop("textwrap") if "textwrap" in kwargs else 40
241
241
  kwargs["title"] = kwargs.pop("title", None) \
242
- or "\n".join(wrap(f"{self.modality} measured in {self.anchor._regionspec}", wrapwidth))
242
+ or "\n".join(wrap(f"{self.modality} measured in {self.anchor._regionspec or self.anchor.location}", wrapwidth))
243
243
  kwargs["kind"] = "box"
244
244
  if backend == "matplotlib":
245
245
  for arg in ['yerr', 'y', 'ylabel', 'xlabel', 'width']:
@@ -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");
@@ -56,7 +56,8 @@ class LayerwiseCellDensity(
56
56
  layerfiles: list,
57
57
  anchor: _anchor.AnatomicalAnchor,
58
58
  datasets: list = [],
59
- id: str = None
59
+ id: str = None,
60
+ prerelease: bool = False,
60
61
  ):
61
62
  tabular.Tabular.__init__(
62
63
  self,
@@ -65,7 +66,8 @@ class LayerwiseCellDensity(
65
66
  anchor=anchor,
66
67
  datasets=datasets,
67
68
  data=None, # lazy loading below
68
- id=id
69
+ id=id,
70
+ prerelease=prerelease,
69
71
  )
70
72
  self.unit = "# detected cells/0.1mm3"
71
73
  self._filepairs = list(zip(segmentfiles, layerfiles))
@@ -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");
@@ -43,7 +43,8 @@ class ReceptorDensityFingerprint(
43
43
  tsvfile: str,
44
44
  anchor: _anchor.AnatomicalAnchor,
45
45
  datasets: list = [],
46
- id: str = None
46
+ id: str = None,
47
+ prerelease: bool = False,
47
48
  ):
48
49
  """ Generate a receptor fingerprint from a URL to a .tsv file
49
50
  formatted according to the structure used by Palomero-Gallagher et al.
@@ -55,7 +56,8 @@ class ReceptorDensityFingerprint(
55
56
  anchor=anchor,
56
57
  data=None, # lazy loading below
57
58
  datasets=datasets,
58
- id=id
59
+ id=id,
60
+ prerelease=prerelease,
59
61
  )
60
62
  self._loader = requests.HttpRequest(tsvfile)
61
63
 
@@ -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");
@@ -42,7 +42,8 @@ class ReceptorDensityProfile(
42
42
  tsvfile: str,
43
43
  anchor: _anchor.AnatomicalAnchor,
44
44
  datasets: list = [],
45
- id: str = None
45
+ id: str = None,
46
+ prerelease: bool = False,
46
47
  ):
47
48
  """Generate a receptor density profile from a URL to a .tsv file
48
49
  formatted according to the structure used by Palomero-Gallagher et al.
@@ -53,7 +54,8 @@ class ReceptorDensityProfile(
53
54
  modality="Receptor density",
54
55
  anchor=anchor,
55
56
  datasets=datasets,
56
- id=id
57
+ id=id,
58
+ prerelease=prerelease
57
59
  )
58
60
  self.receptor = receptor
59
61
  self._data_cached = None
@@ -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");
@@ -49,7 +49,8 @@ class RegionalTimeseriesActivity(tabular.Tabular, Compoundable):
49
49
  description: str = "",
50
50
  datasets: list = [],
51
51
  subject: str = "average",
52
- id: str = None
52
+ id: str = None,
53
+ prerelease: bool = False,
53
54
  ):
54
55
  """
55
56
  """
@@ -60,7 +61,8 @@ class RegionalTimeseriesActivity(tabular.Tabular, Compoundable):
60
61
  anchor=anchor,
61
62
  datasets=datasets,
62
63
  data=None, # lazy loading below
63
- id=id
64
+ id=id,
65
+ prerelease=prerelease,
64
66
  )
65
67
  self.cohort = cohort.upper()
66
68
  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");
@@ -45,7 +45,8 @@ class Tabular(feature.Feature):
45
45
  anchor: _anchor.AnatomicalAnchor,
46
46
  data: pd.DataFrame, # sample x feature dimension
47
47
  datasets: list = [],
48
- id: str = None
48
+ id: str = None,
49
+ prerelease: bool = False,
49
50
  ):
50
51
  feature.Feature.__init__(
51
52
  self,
@@ -53,7 +54,8 @@ class Tabular(feature.Feature):
53
54
  description=description,
54
55
  anchor=anchor,
55
56
  datasets=datasets,
56
- id=id
57
+ id=id,
58
+ prerelease=prerelease
57
59
  )
58
60
  self._data_cached = data
59
61
 
@@ -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");
@@ -19,7 +19,7 @@ from .query import LiveQuery
19
19
  from ..core import space as _space, structure
20
20
  from ..features import anchor as _anchor
21
21
  from ..features.tabular.gene_expression import GeneExpressions
22
- from ..commons import logger, Species, MapType
22
+ from ..commons import logger, Species
23
23
  from ..locations import point, pointset
24
24
  from ..retrieval import HttpRequest
25
25
  from ..vocabularies import GENE_NAMES
@@ -101,9 +101,6 @@ class AllenBrainAtlasQuery(LiveQuery, args=['gene'], FeatureType=GeneExpressions
101
101
  """
102
102
  LiveQuery.__init__(self, **kwargs)
103
103
  gene = kwargs.get('gene')
104
- self.maptype = kwargs.get("maptype", None)
105
- if isinstance(self.maptype, str):
106
- self.maptype = MapType[self.maptype.upper()]
107
104
 
108
105
  def parse_gene(spec):
109
106
  if isinstance(spec, str):
@@ -137,6 +134,9 @@ class AllenBrainAtlasQuery(LiveQuery, args=['gene'], FeatureType=GeneExpressions
137
134
  measurements.append(measurement)
138
135
  coordinates.append(pt)
139
136
 
137
+ if len(points_inside) == 0:
138
+ raise StopIteration
139
+
140
140
  # Build the anatomical anchor and assignment to the query concept.
141
141
  # It will be attached to the returned feature, with the set of matched
142
142
  # MNI coordinates as anchor's location.
@@ -292,7 +292,10 @@ class AllenBrainAtlasQuery(LiveQuery, args=['gene'], FeatureType=GeneExpressions
292
292
  url = AllenBrainAtlasQuery._QUERY["microarray"].format(
293
293
  probe_ids=",".join([str(id) for id in probe_ids]), donor_id=donor_id
294
294
  )
295
- response = HttpRequest(url, json.loads).get()
295
+ try:
296
+ response = HttpRequest(url, json.loads).get()
297
+ except json.JSONDecodeError as e:
298
+ raise RuntimeError(f"Allen institute site produced an empty response - please try again later.\n{e}")
296
299
  if not response["success"]:
297
300
  raise Exception(
298
301
  "Invalid response when retrieving microarray data: {}".format(url)
@@ -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");
@@ -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,6 +19,7 @@ from . import point, pointset, location
19
19
  from ..commons import logger
20
20
  from ..exceptions import SpaceWarpingFailedError
21
21
 
22
+ from itertools import product
22
23
  import hashlib
23
24
  import numpy as np
24
25
  from typing import TYPE_CHECKING, Union
@@ -75,6 +76,7 @@ class BoundingBox(location.Location):
75
76
  s1, s2 = sigma_mm
76
77
  else:
77
78
  raise ValueError(f"Cannot interpret sigma_mm parameter value {sigma_mm} for bounding box")
79
+ self.sigma_mm = [s1, s2]
78
80
  self.minpoint = point.Point([min(xyz1[i], xyz2[i]) for i in range(3)], space, sigma_mm=s1)
79
81
  self.maxpoint = point.Point([max(xyz1[i], xyz2[i]) for i in range(3)], space, sigma_mm=s2)
80
82
  if minsize is not None:
@@ -82,6 +84,9 @@ class BoundingBox(location.Location):
82
84
  if self.shape[d] < minsize:
83
85
  self.maxpoint[d] = self.minpoint[d] + minsize
84
86
 
87
+ if self.volume == 0:
88
+ logger.warning("Created BoundedBox's have zero volume.")
89
+
85
90
  @property
86
91
  def id(self) -> str:
87
92
  return hashlib.md5(str(self).encode("utf-8")).hexdigest()
@@ -110,12 +115,12 @@ class BoundingBox(location.Location):
110
115
  def __str__(self):
111
116
  if self.space is None:
112
117
  return (
113
- f"Bounding box from ({','.join(f'{v:.2f}' for v in self.minpoint)}) mm "
114
- f"to ({','.join(f'{v:.2f}' for v in self.maxpoint)}) mm"
118
+ f"Bounding box from ({','.join(f'{v:.2f}' for v in self.minpoint)})mm "
119
+ f"to ({','.join(f'{v:.2f}' for v in self.maxpoint)})mm"
115
120
  )
116
121
  else:
117
122
  return (
118
- f"Bounding box from ({','.join(f'{v:.2f}' for v in self.minpoint)}) mm "
123
+ f"Bounding box from ({','.join(f'{v:.2f}' for v in self.minpoint)})mm "
119
124
  f"to ({','.join(f'{v:.2f}' for v in self.maxpoint)})mm in {self.space.name} space"
120
125
  )
121
126
 
@@ -183,12 +188,18 @@ class BoundingBox(location.Location):
183
188
  result_minpt.append(A[dim])
184
189
  result_maxpt.append(B[dim])
185
190
 
191
+ if result_minpt == result_maxpt:
192
+ return result_minpt
193
+
186
194
  bbox = BoundingBox(
187
195
  point1=point.Point(result_minpt, self.space),
188
196
  point2=point.Point(result_maxpt, self.space),
189
197
  space=self.space,
190
198
  )
191
- return bbox if bbox.volume > 0 else None
199
+
200
+ if bbox.volume == 0 and sum(cmin == cmax for cmin, cmax in zip(result_minpt, result_maxpt)) == 2:
201
+ return None
202
+ return bbox
192
203
 
193
204
  def _intersect_mask(self, mask: 'Nifti1Image', threshold=0):
194
205
  """Intersect this bounding box with an image mask. Returns None if they do not intersect.
@@ -245,6 +256,35 @@ class BoundingBox(location.Location):
245
256
  sigma_mm=[self.minpoint.sigma, self.maxpoint.sigma]
246
257
  )
247
258
 
259
+ @property
260
+ def corners(self):
261
+ """
262
+ Returns all 8 corners of the box as a pointset.
263
+
264
+ Note
265
+ ----
266
+ x0, y0, z0 = self.minpoint
267
+ x1, y1, z1 = self.maxpoint
268
+ all_corners = [
269
+ (x0, y0, z0),
270
+ (x1, y0, z0),
271
+ (x0, y1, z0),
272
+ (x1, y1, z0),
273
+ (x0, y0, z1),
274
+ (x1, y0, z1),
275
+ (x0, y1, z1),
276
+ (x1, y1, z1)
277
+ ]
278
+
279
+ TODO: deal with sigma. Currently, returns the mean of min and max point.
280
+ """
281
+ xs, ys, zs = zip(self.minpoint, self.maxpoint)
282
+ return pointset.PointSet(
283
+ coordinates=[[x, y, z] for x, y, z in product(xs, ys, zs)],
284
+ space=self.space,
285
+ sigma_mm=np.mean([self.minpoint.sigma, self.maxpoint.sigma])
286
+ )
287
+
248
288
  def warp(self, space):
249
289
  """Returns a new bounding box obtained by warping the
250
290
  min- and maxpoint of this one into the new target space.
@@ -257,13 +297,10 @@ class BoundingBox(location.Location):
257
297
  return self
258
298
  else:
259
299
  try:
260
- return self.__class__(
261
- point1=self.minpoint.warp(spaceobj),
262
- point2=self.maxpoint.warp(spaceobj),
263
- space=spaceobj,
264
- )
265
- except ValueError:
300
+ warped_corners = self.corners.warp(spaceobj)
301
+ except SpaceWarpingFailedError:
266
302
  raise SpaceWarpingFailedError(f"Warping {str(self)} to {spaceobj.name} not successful.")
303
+ return warped_corners.boundingbox
267
304
 
268
305
  def transform(self, affine: np.ndarray, space=None):
269
306
  """Returns a new bounding box obtained by transforming the
@@ -282,12 +319,9 @@ class BoundingBox(location.Location):
282
319
  """
283
320
  from ..core.space import Space
284
321
  spaceobj = Space.get_instance(space)
285
- return self.__class__(
286
- point1=self.minpoint.transform(affine, spaceobj),
287
- point2=self.maxpoint.transform(affine, spaceobj),
288
- space=space,
289
- sigma_mm=[self.minpoint.sigma, self.maxpoint.sigma] # TODO: error propagation
290
- )
322
+ result = self.corners.transform(affine, spaceobj).boundingbox
323
+ result.sigma_mm = [self.minpoint.sigma, self.maxpoint.sigma] # TODO: error propagation
324
+ return result
291
325
 
292
326
  def shift(self, offset):
293
327
  return self.__class__(
@@ -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");
@@ -21,6 +21,10 @@ from ..core.structure import BrainStructure
21
21
  import numpy as np
22
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
27
+
24
28
 
25
29
  class Location(BrainStructure):
26
30
  """
@@ -42,15 +46,19 @@ class Location(BrainStructure):
42
46
  _MASK_MEMO = {} # cache region masks for Location._assign_region()
43
47
  _ASSIGNMENT_CACHE = {} # caches assignment results, see Region.assign()
44
48
 
45
- def __init__(self, space):
46
- self._space_spec = space
49
+ def __init__(self, spacespec: Union[str, Dict[str, str], "Space"]):
50
+ self._space_spec = spacespec
47
51
  self._space_cached = None
48
52
 
49
53
  @property
50
54
  def space(self):
51
55
  if self._space_cached is None:
52
56
  from ..core.space import Space
53
- self._space_cached = Space.get_instance(self._space_spec)
57
+ if isinstance(self._space_spec, dict):
58
+ spec = self._space_spec.get("@id") or self._space_spec.get("name")
59
+ self._space_cached = Space.get_instance(spec)
60
+ else:
61
+ self._space_cached = Space.get_instance(self._space_spec)
54
62
  return self._space_cached
55
63
 
56
64
  @abstractmethod
siibra/locations/point.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");
@@ -18,6 +18,7 @@ from . import location, boundingbox, pointset
18
18
 
19
19
  from ..commons import logger
20
20
  from ..retrieval.requests import HttpRequest
21
+ from ..exceptions import SpaceWarpingFailedError, NoneCoordinateSuppliedError
21
22
 
22
23
  from urllib.parse import quote
23
24
  import re
@@ -55,10 +56,14 @@ class Point(location.Location):
55
56
  if len(digits) == 3:
56
57
  return tuple(float(d) for d in digits)
57
58
  elif isinstance(spec, (tuple, list)) and len(spec) in [3, 4]:
59
+ if any(v is None for v in spec):
60
+ raise NoneCoordinateSuppliedError("Cannot parse cooridantes containing None values.")
58
61
  if len(spec) == 4:
59
62
  assert spec[3] == 1
60
63
  return tuple(float(v.item()) if isinstance(v, np.ndarray) else float(v) for v in spec[:3])
61
64
  elif isinstance(spec, np.ndarray) and spec.size == 3:
65
+ if any(np.isnan(v) for v in spec):
66
+ raise NoneCoordinateSuppliedError("Cannot parse cooridantes containing NaN values.")
62
67
  return tuple(float(v.item()) if isinstance(v, np.ndarray) else float(v) for v in spec[:3])
63
68
  elif isinstance(spec, Point):
64
69
  return spec.coordinate
@@ -125,7 +130,7 @@ class Point(location.Location):
125
130
  if spaceobj == self.space:
126
131
  return self
127
132
  if any(_ not in location.Location.SPACEWARP_IDS for _ in [self.space.id, spaceobj.id]):
128
- raise ValueError(
133
+ raise SpaceWarpingFailedError(
129
134
  f"Cannot convert coordinates between {self.space.id} and {spaceobj.id}"
130
135
  )
131
136
  url = "{server}/transform-point?source_space={src}&target_space={tgt}&x={x}&y={y}&z={z}".format(
@@ -137,9 +142,9 @@ class Point(location.Location):
137
142
  z=self.coordinate[2],
138
143
  )
139
144
  response = HttpRequest(url, lambda b: json.loads(b.decode())).get()
140
- if any(map(np.isnan, response['target_point'])):
141
- logger.debug(f'Warping {str(self)} to {spaceobj.name} resulted in NaN')
142
- return None
145
+ if np.any(np.isnan(response['target_point'])):
146
+ raise SpaceWarpingFailedError(f'Warping {str(self)} to {spaceobj.name} resulted in NaN')
147
+
143
148
  return self.__class__(
144
149
  coordinatespec=tuple(response["target_point"]),
145
150
  space=spaceobj.id,