Repository: FZJ-INM1-BDA/siibra-python

Issue:
Querying BigBrainIntesityProfiles with locations does not work as the intersection of locations do not return indices
as of v1.0a11 (Traces back to v1.0a refactor)

Discussion:

Solution patch:
diff --git a/siibra/core/region.py b/siibra/core/region.py
index d0c5739f..df003c6b 100644
--- a/siibra/core/region.py
+++ b/siibra/core/region.py
@@ -124,7 +124,7 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
 
     def get_related_regions(self) -> Iterable["RegionRelationAssessments"]:
         """
-        Get assements on relations of this region to others defined on EBRAINS.
+        Get assessments on relations of this region to others defined on EBRAINS.
 
         Yields
         ------
@@ -918,7 +918,7 @@ def get_peid_from_region(region: Region) -> str:
 
 def get_related_regions(region: Region) -> Iterable["RegionRelationAssessments"]:
     """
-    Get assements on relations of a region to others defined on EBRAINS.
+    Get assessments on relations of a region to others defined on EBRAINS.
 
     Parameters
     ----------
diff --git a/siibra/livequeries/bigbrain.py b/siibra/livequeries/bigbrain.py
index e5fe25cc..a5f11b66 100644
--- a/siibra/livequeries/bigbrain.py
+++ b/siibra/livequeries/bigbrain.py
@@ -19,7 +19,7 @@ from . import query
 from ..features.tabular import bigbrain_intensity_profile, layerwise_bigbrain_intensities
 from ..features import anchor as _anchor
 from ..commons import logger
-from ..locations import point, pointcloud
+from ..locations import point, pointcloud, location
 from ..core import structure
 from ..retrieval import requests, cache
 from ..retrieval.datasets import GenericDataset
@@ -123,11 +123,16 @@ class BigBrainProfileQuery(query.LiveQuery, args=[], FeatureType=bigbrain_intens
         matched = concept.intersection(mesh_vertices)  # returns a reduced PointCloud with og indices as labels
         if matched is None:
             return []
+        if isinstance(matched, point.Point):
+            matched = pointcloud.from_points([matched])
         assert isinstance(matched, pointcloud.PointCloud)
+        if isinstance(concept, location.Location):
+            mesh_as_list = mesh_vertices.as_list()
+            matched.labels = [mesh_as_list.index(v.coordinate) for v in matched]
         indices = matched.labels
         assert indices is not None
         features = []
-        for i in matched.labels:
+        for i in indices:
             anchor = _anchor.AnatomicalAnchor(
                 location=point.Point(loader._vertices[i], space='bigbrain'),
                 region=str(concept),
@@ -160,12 +165,16 @@ class LayerwiseBigBrainIntensityQuery(query.LiveQuery, args=[], FeatureType=laye
 
         loader = WagstylProfileLoader()
         mesh_vertices = pointcloud.PointCloud(loader._vertices, space='bigbrain')
-        matched = concept.intersection(mesh_vertices)  # returns a reduced PointCloud with og indices as labels
+        matched = concept.intersection(mesh_vertices)  # returns a reduced PointCloud with og indices as labels if the concept is a region
         if matched is None:
             return []
+        if isinstance(matched, point.Point):
+            matched = pointcloud.from_points([matched])
         assert isinstance(matched, pointcloud.PointCloud)
+        if isinstance(concept, location.Location):
+            mesh_as_list = mesh_vertices.as_list()
+            matched.labels = [mesh_as_list.index(v.coordinate) for v in matched]
         indices = matched.labels
-        assert indices is not None
         matched_profiles = loader._profiles[indices, :]
         boundary_depths = loader._boundary_depths[indices, :]
         # compute array of layer labels for all coefficients in profiles_left
diff --git a/siibra/locations/boundingbox.py b/siibra/locations/boundingbox.py
index c1299f1c..019aeb0e 100644
--- a/siibra/locations/boundingbox.py
+++ b/siibra/locations/boundingbox.py
@@ -148,11 +148,7 @@ class BoundingBox(location.Location):
             points_inside = [p for p in other if self.intersects(p)]
             if len(points_inside) == 0:
                 return None
-            result = pointcloud.PointCloud(
-                points_inside,
-                space=other.space,
-                sigma_mm=[p.sigma for p in points_inside]
-            )
+            result = pointcloud.from_points(points_inside)
             return result[0] if len(result) == 1 else result  # if PointCloud has single point return as a Point
 
         return other.intersection(self)
diff --git a/siibra/locations/pointcloud.py b/siibra/locations/pointcloud.py
index e2ffa9f6..f4583608 100644
--- a/siibra/locations/pointcloud.py
+++ b/siibra/locations/pointcloud.py
@@ -120,18 +120,13 @@ class PointCloud(location.Location):
         if not isinstance(other, (point.Point, PointCloud, _boundingbox.BoundingBox)):
             return other.intersection(self)
 
-        intersections = [(i, p) for i, p in enumerate(self) if p.intersects(other)]
-        if len(intersections) == 0:
+        if isinstance(other, PointCloud):
+            intersecting_points = [p for p in self if p.coordinate in other.coordinates]
+        else:
+            intersecting_points = [p for p in self if p.intersects(other)]
+        if len(intersecting_points) == 0:
             return None
-        ids, points = zip(*intersections)
-        labels = None if self.labels is None else [self.labels[i] for i in ids]
-        sigma = [p.sigma for p in points]
-        intersection = PointCloud(
-            points,
-            space=self.space,
-            sigma_mm=sigma,
-            labels=labels
-        )
+        intersection = from_points(intersecting_points)
         return intersection[0] if len(intersection) == 1 else intersection
 
     @property


Test patch:
diff --git a/e2e/features/test_get.py b/e2e/features/test_get.py
index a7d89214..8e5b0ca3 100644
--- a/e2e/features/test_get.py
+++ b/e2e/features/test_get.py
@@ -1,7 +1,13 @@
 import pytest
-import siibra
 from e2e.util import check_duplicate
 
+import siibra
+from siibra.locations import Location, PointCloud
+from siibra.livequeries.bigbrain import WagstylProfileLoader
+from siibra.features.feature import CompoundFeature
+
+import numpy as np
+
 
 # We get all registered subclasses of Feature
 @pytest.mark.parametrize(
@@ -67,8 +73,53 @@ def test_querying_with_volume():
     # Use features with location anchors only. Because hybrid ones will also
     # employ semantic links between regions, potentially changing the result.
     region = siibra.get_region("julich 2.9", "ca1")
-    volume = region.get_regional_mask('mni152')
+    volume = region.get_regional_mask("mni152")
     profiles_region = siibra.features.get(region, "BigBrainIntensityProfile")[0]
     profiles_volume = siibra.features.get(volume, "BigBrainIntensityProfile")[0]
     # the ids will be different but the content has to be the same. Even the order.
-    assert [p.location for p in profiles_region] == [p.location for p in profiles_volume]
+    assert [p.location for p in profiles_region] == [
+        p.location for p in profiles_volume
+    ]
+
+
+WagstylProfileLoader._load()
+verts = WagstylProfileLoader._vertices
+np.random.seed(10)
+loc_queries = [
+    (
+        siibra.get_map("julich 2.9", "bigbrain")
+        .get_volume("hoc1 left")
+        .get_boundingbox(clip=True),
+        "BigBrainIntensityProfile",
+        13968,
+    ),
+    (
+        PointCloud(verts[np.random.randint(0, len(verts) - 1, 100)], space="bigbrain"),
+        "BigBrainIntensityProfile",
+        100,
+    ),
+    (
+        PointCloud(
+            np.concatenate(
+                [
+                    verts[np.random.randint(0, len(verts) - 1, 100)] + 1000,
+                    verts[np.random.randint(0, len(verts) - 1, 10)],
+                ]
+            ),
+            space="bigbrain",
+        ),
+        "BigBrainIntensityProfile",
+        10,
+    ),
+]
+
+
+@pytest.mark.parametrize("loc, feat_type, res_len", loc_queries)
+def test_query_w_locations(loc: Location, feat_type: str, res_len: int):
+    results = siibra.features.get(loc, feat_type)
+    assert len(results) > 0
+    assert results[0].data is not None
+    if len(results) == 1 and isinstance(results[0], CompoundFeature):
+        assert len(results[0]) == res_len
+    else:
+        assert len(results) == res_len