Skip to content

Commit 103bdec

Browse files
committed
ROI tree: walk innervates + drop depth cap; fix painted_index label fallback
v1.13.2's get_template_roi_tree walked the painted-leaf paths through [:SUBCLASSOF|part_of*0..20] only. On the adult VNS template, 16 of the 22 painted Individuals are nerves (FBbt_00004019 / _00004055-63 / _00004094-99 / _00004105-08 / _00007657) whose part_of chain goes to adult peripheral nervous system, NOT to the central VNC root. They connect to the VNC neuromeres via the `innervates` relation instead. The legacy v2 Cypher had the same edge set and the same gap; with v2's visual tree typically loading JFRC2 by default it was easy to miss. Changes: - Cypher walk: extend to [:SUBCLASSOF|part_of|innervates*0..]. Adult VNS Court2018 now renders 26 nodes (was 8) with all 22 painted classes surfaced; JRC2018UnisexVNC gains its nerves; Hemibrain + Ito2014 gain their antennal / labellar / abdominal nerves. Templates that have no painted nerves (JFRC2, JRC2018Unisex, T1 Leg, larval CNS, Adult Head) are unchanged. - Depth cap *0..20 dropped to *0.. on Robbie's call: the cache absorbs the cold-cache delay (all ten test templates still complete cold in <2.4s on pdb), and the cap risked silently truncating future deep ontology paths. - painted_domain_index class_label fallback: when a painted class isn't reached by the tree walk (e.g. orphaned by relations we don't traverse), use the class_label captured in painted_rows. v1.13.2 left these as null and any tooltip/debug surface keying off the reverse lookup would have rendered "None". Cache bucket renamed template_roi_tree -> template_roi_tree_v2 so the v1.13.2 cache entries (built with the narrower rel-set) don't shadow the new walk. Surgical invalidation - no minor bump needed, other queries' caches are untouched. Verified against pdb for all 10 production templates: template nodes edges painted rel mix JRC2018Unisex 77 92 46 part_of=89, SUBCLASSOF=3 JFRC2 brain 70 85 58 part_of=82, SUBCLASSOF=3 JRC_FlyEM_Hemibrain 97 122 97 part_of=97, SUBCLASSOF=16, innervates=9 Ito2014 brain 110 168 75 part_of=118, SUBCLASSOF=10, innervates=40 JRC2018UnisexVNC 30 46 21 part_of=21, SUBCLASSOF=16, innervates=9 adult VNS Court2018 26 28 22 innervates=14, SUBCLASSOF=10, part_of=4 L1 larval CNS 0 0 0 (no painted data) L3 CNS Wood2018 140 328 255 part_of=199, SUBCLASSOF=129 Adult T1 Leg 3 2 4 part_of=2 Adult Head McKellar 30 46 18 SUBCLASSOF=29, part_of=17 All cold-cache runs sub-2.4s. No __version__ bump.
1 parent f025894 commit 103bdec

2 files changed

Lines changed: 35 additions & 9 deletions

File tree

src/vfbquery/cached_functions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ def get_painted_domains_cached(template_short_form: str, return_dataframe=True,
325325
"""
326326
return _original_get_painted_domains(template_short_form=template_short_form, return_dataframe=return_dataframe, limit=limit)
327327

328-
@with_solr_cache('template_roi_tree')
328+
@with_solr_cache('template_roi_tree_v2')
329329
def get_template_roi_tree_cached(template_short_form: str, return_dataframe: bool = False, force_refresh: bool = False):
330330
"""
331331
Enhanced get_template_roi_tree with SOLR caching.

src/vfbquery/vfb_queries.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4883,18 +4883,27 @@ def get_painted_domains(template_short_form: str, return_dataframe=True, limit:
48834883
_VFB_SHORT_FORM_RE = re.compile(r"^[A-Za-z0-9_]+$")
48844884

48854885

4886-
@with_solr_cache('template_roi_tree')
4886+
@with_solr_cache('template_roi_tree_v2')
48874887
def get_template_roi_tree(template_short_form: str, return_dataframe=False):
48884888
"""Build a hierarchical ROI tree for a template.
48894889
48904890
Anchors on the template's INSTANCEOF root Class (so this works for
48914891
every template regardless of anatomical character — adult brain
48924892
variants, adult VNC, larval CNS, the Adult T1 Leg with muscles +
4893-
neuropils, Adult Head). Walks down through ``part_of`` and
4894-
``SUBCLASSOF`` to every Class that has a painted-domain Individual on
4895-
this template, materialising the nodes and edges required to render
4896-
the tree. FBbt's DAG character is preserved (multi-parent classes
4897-
appear under each parent), matching the legacy VFBTree behaviour.
4893+
neuropils, Adult Head). Walks down through ``part_of``, ``SUBCLASSOF``
4894+
and ``innervates`` to every Class that has a painted-domain Individual
4895+
on this template, materialising the nodes and edges required to
4896+
render the tree. ``innervates`` is what connects e.g. the adult VNC
4897+
nerves (which are part_of the peripheral nervous system, NOT the
4898+
central VNC) to the neuromeres they innervate, so without it the
4899+
legacy walk dropped 16 of the 22 painted VNS nerves from the tree.
4900+
4901+
Cache bucket ``template_roi_tree_v2`` (was ``template_roi_tree``)
4902+
forces a clean re-populate so v1.13.2 entries with the narrower
4903+
rel-set don't shadow the new walk.
4904+
4905+
FBbt's DAG character is preserved (multi-parent classes appear under
4906+
each parent), matching the legacy VFBTree behaviour.
48984907
48994908
Returned shape (`return_dataframe` is accepted for symmetry; this
49004909
query is hierarchical and always returns the dict shape):
@@ -4950,7 +4959,7 @@ def get_template_roi_tree(template_short_form: str, return_dataframe=False):
49504959
f"WITH t, root, "
49514960
f"[r IN painted_rows WHERE r.class_id IS NOT NULL] AS painted_rows "
49524961
f"WITH t, root, painted_rows, [r IN painted_rows | r.class_id] AS leaf_ids "
4953-
f"OPTIONAL MATCH path = (root)<-[:SUBCLASSOF|part_of*0..20]-(leaf:Class) "
4962+
f"OPTIONAL MATCH path = (root)<-[:SUBCLASSOF|part_of|innervates*0..]-(leaf:Class) "
49544963
f"WHERE leaf.short_form IN leaf_ids "
49554964
f"WITH t, root, painted_rows, "
49564965
f"collect(distinct [n IN nodes(path) | "
@@ -5054,10 +5063,27 @@ def _build(node_id, ancestors, parent_label):
50545063
f"region to toggle its overlay on the 3D viewer."
50555064
)
50565065

5066+
# painted_class label fallback: any class found by the painted-collection
5067+
# half of the Cypher carries its own label, so use that when the
5068+
# tree-walk half didn't reach the class (this happens when the only
5069+
# path from root to the leaf relies on a relation we don't traverse,
5070+
# so the class is in painted_by_class but not in nodes_by_id).
5071+
class_label_by_id = {
5072+
cid: (plist[0].get('class_label') if plist else None)
5073+
for cid, plist in painted_by_class.items()
5074+
}
5075+
# painted_by_class doesn't store the class_label directly (we project
5076+
# only individual_id / individual_label into the per-class list), so
5077+
# rebuild from painted_rows where it does live.
5078+
for r in painted_rows:
5079+
cid = r.get('class_id')
5080+
if cid and not class_label_by_id.get(cid):
5081+
class_label_by_id[cid] = r.get('class_label')
5082+
50575083
painted_index = {
50585084
p['individual_id']: {
50595085
'class_id': cid,
5060-
'class_label': nodes_by_id.get(cid),
5086+
'class_label': nodes_by_id.get(cid) or class_label_by_id.get(cid),
50615087
}
50625088
for cid, plist in painted_by_class.items()
50635089
for p in plist

0 commit comments

Comments
 (0)