Skip to content

Commit

Permalink
maint: clarify attribute qualifcation register
Browse files Browse the repository at this point in the history
feat: attribute_collection now exposes categorization as a human readable str
feat: searchresult indexes categorizations when building summary table
  • Loading branch information
xgui3783 committed Oct 10, 2024
1 parent 16ecfa5 commit 1ddb25c
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 26 deletions.
34 changes: 26 additions & 8 deletions siibra/assignment/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from ..factory.livequery import LiveQuery
from ..factory.configuration import iter_preconfigured
from ..attributes import AttributeCollection
from ..attributes.descriptions import ID, Name
from ..attributes.descriptions import ID, Name, Categorization
from ..cache import fn_call_cache

T = TypeVar("T", bound=AttributeCollection)
Expand Down Expand Up @@ -82,28 +82,46 @@ def str_search_criteria(input: str):
query = AttributeCollection(attributes=[id_attr, name_attr])
return [query]

def build_summary_table(self):
@staticmethod
def build_summary_table(items: List[T]):
"""
Returns a dataframe where user can inspect/evaluate how to further narrow down
the search result. Similar to AttributeCollection.data_recipe_table
"""
list_of_dict = [
{
"ID": item.ID,
"name": item.name,
"modalities": item.modalities,
# In case key is one of ID, name etc, prepend to avoid name collision
"categorizations": item.categorizations,
**{
f"category_{key}": value
for key, value in item._find(Categorization)
},
"ID": item.ID,
"instance": item,
}
for item in self.find()
for item in items
]
return pd.DataFrame(list_of_dict)

def get_instance(self, expr=None, index=None, **kwargs) -> "SearchResult":
@staticmethod
def pick_instance(items: List[T], expr=None, index=None) -> T:
"""
Allow user to apply what was learnt from get_summary_table and get a subset of the search.
"""
if index is not None:
return self.build_summary_table().iloc[index]["instance"]
df = SearchResult.build_summary_table(items)
if expr is not None:
return self.build_summary_table().query(expr).iloc[0]["instance"]
return df.query(expr=expr).iloc[0]["instance"]
if index is not None:
return df.iloc[index]["instance"]
raise NotImplementedError

def get_summary_table(self):
return self.build_summary_table(self.find())

def get_instance(self, expr=None, index=None):
"""
Allow user to apply what was learnt from get_summary_table and get a subset of the search.
"""
return self.pick_instance(self.find(), expr=expr, index=index)
36 changes: 18 additions & 18 deletions siibra/assignment/attribute_qualification.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@
from ..cache import fn_call_cache


_attr_qual: BinaryOpRegistry[Attribute, Union[Qualification, None]] = BinaryOpRegistry()

register_attr_qualifier = _attr_qual.register
_attribute_qualification_register: BinaryOpRegistry[
Attribute, Union[Qualification, None]
] = BinaryOpRegistry()


def is_qualifiable(t: Type):
return _attr_qual.is_registered(t)
return _attribute_qualification_register.is_registered(t)


def qualify(attra: Attribute, attrb: Attribute):
Expand All @@ -66,7 +66,7 @@ def qualify(attra: Attribute, attrb: Attribute):
If the comparison of type(attra) and type(attrb) has been registered, but the their
value could not directly be compared (e.g. locations in different spaces)
"""
val = _attr_qual.get(attra, attrb)
val = _attribute_qualification_register.get(attra, attrb)
if val is None:
logger.debug(
f"{type(attra)} and {type(attrb)} comparison has not been registered"
Expand All @@ -85,13 +85,13 @@ def qualify(attra: Attribute, attrb: Attribute):
return result


@register_attr_qualifier(ID, ID)
@_attribute_qualification_register.register(ID, ID)
def qualify_id(id0: ID, id1: ID):
if id0.value == id1.value:
return Qualification.EXACT


@register_attr_qualifier(Name, Name)
@_attribute_qualification_register.register(Name, Name)
def qualify_name(name1: Name, name2: Name):
if name1.value == name2.value:
return Qualification.EXACT
Expand All @@ -107,7 +107,7 @@ def qualify_name(name1: Name, name2: Name):
return Qualification.APPROXIMATE


@register_attr_qualifier(Modality, Modality)
@_attribute_qualification_register.register(Modality, Modality)
def qualify_modality(mod1: Modality, mod2: Modality):
if mod1.value == mod2.value:
return Qualification.EXACT
Expand All @@ -116,7 +116,7 @@ def qualify_modality(mod1: Modality, mod2: Modality):
return Qualification.APPROXIMATE


@register_attr_qualifier(RegionSpec, RegionSpec)
@_attribute_qualification_register.register(RegionSpec, RegionSpec)
@fn_call_cache
def qualify_regionspec(regspec1: RegionSpec, regspec2: RegionSpec):
# if both parcellation_ids are present, short curcuit if parc id do not match
Expand All @@ -143,7 +143,7 @@ def qualify_regionspec(regspec1: RegionSpec, regspec2: RegionSpec):
return Qualification.APPROXIMATE


@register_attr_qualifier(RegionSpec, AttributeMapping)
@_attribute_qualification_register.register(RegionSpec, AttributeMapping)
def qualify_regionspec_attributemapping(
regionspec: RegionSpec, attribute_mapping: AttributeMapping
):
Expand All @@ -166,20 +166,20 @@ def qualify_regionspec_attributemapping(
return qualification


@register_attr_qualifier(Point, Point)
@_attribute_qualification_register.register(Point, Point)
def qualify_pt_to_pt(pt1: Point, pt2: Point):
if intersect(pt1, pt2):
return Qualification.EXACT


@register_attr_qualifier(Point, PointCloud)
@register_attr_qualifier(Point, BoundingBox)
@_attribute_qualification_register.register(Point, PointCloud)
@_attribute_qualification_register.register(Point, BoundingBox)
def qualify_pt_ptcld_bbox(pt: Point, ptcld_bbox: Union[PointCloud, BoundingBox]):
if intersect(pt, ptcld_bbox):
return Qualification.CONTAINED


@register_attr_qualifier(PointCloud, BoundingBox)
@_attribute_qualification_register.register(PointCloud, BoundingBox)
def qualify_ptcld_bbox(ptcld: PointCloud, bbox: BoundingBox):
intersected = intersect(ptcld, bbox)
if not intersected:
Expand All @@ -204,7 +204,7 @@ def qualify_ptcld_bbox(ptcld: PointCloud, bbox: BoundingBox):
)


@register_attr_qualifier(PointCloud, PointCloud)
@_attribute_qualification_register.register(PointCloud, PointCloud)
def qualify_ptcld_ptcld(ptclda: PointCloud, ptcldb: PointCloud):
intersected = intersect(ptclda, ptcldb)
if intersected is None:
Expand Down Expand Up @@ -238,7 +238,7 @@ def qualify_ptcld_ptcld(ptclda: PointCloud, ptcldb: PointCloud):
return Qualification.OVERLAPS


@register_attr_qualifier(BoundingBox, BoundingBox)
@_attribute_qualification_register.register(BoundingBox, BoundingBox)
def qualify_bbox_bbox(bboxa: BoundingBox, bboxb: BoundingBox):
intersected = intersect(bboxa, bboxb)
if intersected is None:
Expand Down Expand Up @@ -267,7 +267,7 @@ def qualify_bbox_bbox(bboxa: BoundingBox, bboxb: BoundingBox):
return Qualification.OVERLAPS


@register_attr_qualifier(PointCloud, ImageRecipe)
@_attribute_qualification_register.register(PointCloud, ImageRecipe)
def qualify_ptcld_image(ptcld: PointCloud, image: ImageRecipe):
intersected = intersect(ptcld, image)
if isinstance(intersected, PointCloud):
Expand All @@ -278,7 +278,7 @@ def qualify_ptcld_image(ptcld: PointCloud, image: ImageRecipe):
return Qualification.OVERLAPS


@register_attr_qualifier(RegionSpec, ImageRecipe)
@_attribute_qualification_register.register(RegionSpec, ImageRecipe)
def qualify_regionspec_image(regionspec: RegionSpec, image: ImageRecipe):
logger.debug(
"RegionSpec and Image comparison is disabled on purpose. This comparison turns out to be "
Expand Down
13 changes: 13 additions & 0 deletions siibra/attributes/attribute_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
TextDescription,
EbrainsRef,
AttributeMapping,
Categorization,
)
from ..operations.tabular import RemapColRowDict, RenameColumnsAndOrRows
from ..commons.iterable import assert_ooo
Expand Down Expand Up @@ -153,6 +154,8 @@ def volume_recipes(self):

return [attr for attr in self.attributes if isinstance(attr, VolumeRecipe)]

# TODO (2.0) update configuration to remove the usage of MATRIX_INDEX_ENTITY_KEY and update
# with region_mapping. then remove this functionality
def filter(self, filter_fn: Callable[[Attribute], bool]):
"""
Return a new `AttributeCollection` that is a copy of this one where the
Expand Down Expand Up @@ -188,6 +191,16 @@ def ID(self):
def modalities(self):
return [m.value for m in self._find(Modality)]

@property
def categorizations(self):
"""
Returns human readable categorizations, a (semi)-freeform description
of the attribute_collection.
"""
return "\n".join(
[f"{key}: {value}" for key, value in self._find(Categorization)]
)

@property
def publications(self):
from ..operations.doi_fetcher import get_citation
Expand Down

0 comments on commit 1ddb25c

Please sign in to comment.