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

Add docstring to livequery #337

Merged
merged 1 commit into from
Apr 20, 2023
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
2 changes: 1 addition & 1 deletion siibra/configuration/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ def build_map(cls, spec):
else:
max_z = max(
d.get('z', 0)
for _, l in spec.get("indices", {}).items()
for l in spec.get("indices", {}).values()
for d in l
) + 1
if max_z > MIN_VOLUMES_FOR_SPARSE_MAP:
Expand Down
47 changes: 43 additions & 4 deletions siibra/features/feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,33 @@ def id(self):
return prefix + md5(self.name.encode("utf-8")).hexdigest()

@staticmethod
def encode_livequery_id(feat: 'Feature', concept: concept.AtlasConcept) -> str:
def serialize_query_context(feat: 'Feature', concept: concept.AtlasConcept) -> str:
"""
Serialize feature from livequery and query context.

It is currently impossible to retrieve a livequery with a generic UUID.
As such, the query context (e.g. region, space or parcellation) needs to
be encoded in the id.

Whilst it is possible to (de)serialize *any* queries, the method is setup to only serialize
livequery features.

The serialized livequery id follows the following pattern:

<livequeryid_version>::<feature_cls_name>::<query_context>::<unserialized_id>

Where:

- livequeryid_version: version of the serialization. (e.g. lq0)
- feature_cls_name: class name to query. (e.g. BigBrainIntensityProfile)
- query_context: string to retrieve atlas concept in the query context. Can be one of the following:
- s:<space_id>
- p:<parcellation_id>
- p:<parcellation_id>::r:<region_id>
- unserialized_id: id prior to serialization

See test/features/test_feature.py for tests and usages.
"""
if not hasattr(feat.__class__, '_live_queries'):
raise EncodeLiveQueryIdException(f"generate_livequery_featureid can only be used on live queries, but {feat.__class__.__name__} is not.")

Expand All @@ -188,7 +214,12 @@ def encode_livequery_id(feat: 'Feature', concept: concept.AtlasConcept) -> str:
return f"lq0::{feat.__class__.__name__}::{'::'.join(encoded_c)}::{feat.id}"

@classmethod
def decode_livequery_id(Cls, feature_id: str) -> Tuple[Type['Feature'], concept.AtlasConcept, str]:
def deserialize_query_context(Cls, feature_id: str) -> Tuple[Type['Feature'], concept.AtlasConcept, str]:
"""
Deserialize id into query context.

See docstring of serialize_query_context for context.
"""
lq_version, *rest = feature_id.split("::")
if lq_version != "lq0":
raise ParseLiveQueryIdException(f"livequery id must start with lq0::")
Expand Down Expand Up @@ -246,7 +277,7 @@ def livequery(cls, concept: Union[region.Region, parcellation.Parcellation, spac
)
q = QueryType(**kwargs)
features = [
Feature.wrap_livequery_feature(feat, Feature.encode_livequery_id(feat, concept))
Feature.wrap_livequery_feature(feat, Feature.serialize_query_context(feat, concept))
for feat in q.query(concept)
]
live_instances.extend(features)
Expand Down Expand Up @@ -306,7 +337,7 @@ def match(cls, concept: Union[region.Region, parcellation.Parcellation, space.Sp
@classmethod
def get_instance_by_id(cls, feature_id: str, **kwargs):
try:
F, concept, fid = cls.decode_livequery_id(feature_id)
F, concept, fid = cls.deserialize_query_context(feature_id)
return [
f
for f in F.livequery(concept, **kwargs)
Expand Down Expand Up @@ -347,6 +378,14 @@ def create_treenode(feature_type):

@staticmethod
def wrap_livequery_feature(feature: 'Feature', fid: str):
"""
Wrap live query features, override only the id attribute.

Some features do not have setters for the id property. The ProxyFeature class
allow the id property to be overridden without touching the underlying class.

See docstring of serialize_query_context for further context.
"""
class ProxyFeature(feature.__class__):

# override __class__ property
Expand Down
2 changes: 1 addition & 1 deletion siibra/volumes/parcellationmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ def get_region(self, label: int = None, volume: int = 0, index: MapIndex = None)
return self.parcellation.get_region(matches[0])
else:
# this should not happen, already tested in constructor
raise RuntimeError(f"Index {index} is not unique in {self}")
raise RuntimeError(f"Index {index} is not unique in {self}")

@property
def space(self):
Expand Down
12 changes: 6 additions & 6 deletions test/features/test_feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ class FooFeature(FooFeatureBase):
]

@pytest.mark.parametrize("feature,concept,ExCls,expected_id", list_of_queries)
def test_encode_livequery_id(feature,concept,ExCls,expected_id):
def test_serialize_query_context(feature,concept,ExCls,expected_id):
if ExCls:
with pytest.raises(ExCls):
Feature.encode_livequery_id(feature, concept)
Feature.serialize_query_context(feature, concept)
return
actual_id = Feature.encode_livequery_id(feature, concept)
actual_id = Feature.serialize_query_context(feature, concept)
assert actual_id == expected_id

lq_prefix="lq0"
Expand Down Expand Up @@ -105,14 +105,14 @@ def test_mock_registry(mock_space_registry):
]

@pytest.mark.parametrize("fid,ExCls,mocks_called,args_used,return_concept,decoded_id", list_of_fids)
def test_decode_livequery_id(fid,ExCls,mocks_called,args_used,return_concept,decoded_id, mock_all):
def test_deserialize_query_context(fid,ExCls,mocks_called,args_used,return_concept,decoded_id, mock_all):
mock_parse_featuretype, mock_space_registry, mock_parcellation, mock_region = mock_all
if ExCls:
with pytest.raises(ExCls):
Feature.decode_livequery_id(fid)
Feature.deserialize_query_context(fid)
return

F, concept, fid = Feature.decode_livequery_id(fid)
F, concept, fid = Feature.deserialize_query_context(fid)

mock_parse_featuretype.assert_called_once()

Expand Down