Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: query BigBrainProfileIntensity with locations #637

Merged
merged 4 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 54 additions & 3 deletions e2e/features/test_get.py
Original file line number Diff line number Diff line change
@@ -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(
Expand Down Expand Up @@ -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
4 changes: 2 additions & 2 deletions siibra/core/region.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def __init__(

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
------
Expand Down Expand Up @@ -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
----------
Expand Down
17 changes: 13 additions & 4 deletions siibra/livequeries/bigbrain.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
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
Expand Down Expand Up @@ -123,11 +123,16 @@ def query(self, concept: structure.BrainStructure, **kwargs) -> List[bigbrain_in
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),
Expand Down Expand Up @@ -160,12 +165,16 @@ def query(self, concept: structure.BrainStructure, **kwargs) -> List[layerwise_b

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
Expand Down
6 changes: 1 addition & 5 deletions siibra/locations/boundingbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,7 @@ def intersection(self, other: 'BrainStructure', dims=[0, 1, 2]):
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)
Expand Down
17 changes: 6 additions & 11 deletions siibra/locations/pointcloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,18 +120,13 @@ def intersection(self, other: 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
Expand Down