diff --git a/src/vfbquery/vfb_queries.py b/src/vfbquery/vfb_queries.py
index 4f4de3a..ac1a889 100644
--- a/src/vfbquery/vfb_queries.py
+++ b/src/vfbquery/vfb_queries.py
@@ -1052,31 +1052,32 @@ def term_info_parse_object(results, short_form):
q = NeuronNeuronConnectivityQuery_to_schema(termInfo["Name"], {"short_form": vfbTerm.term.core.short_form})
queries.append(q)
- # NeuronsPartHere query - for Class+Anatomy terms (synaptic neuropils, etc.)
- # Matches XMI criteria: Class + Synaptic_neuropil, or other anatomical regions
- # Excluded for neuron classes: "neurons with some part in " is not a meaningful query
- if contains_all_tags(termInfo["SuperTypes"], ["Class"]) and "Neuron" not in termInfo["SuperTypes"] and (
+ # NeuronsPartHere query - for anatomical regions (neuropils, ganglia, etc.)
+ # Gate: Class + (Synaptic_neuropil OR Anatomy), but NOT Cell.
+ # Excluded for cell classes (neurons, glia, neuroblasts): "neurons with some
+ # part in " is not a meaningful query. Cell subsumes Neuron.
+ if contains_all_tags(termInfo["SuperTypes"], ["Class"]) and "Cell" not in termInfo["SuperTypes"] and (
"Synaptic_neuropil" in termInfo["SuperTypes"] or
"Anatomy" in termInfo["SuperTypes"]
):
q = NeuronsPartHere_to_schema(termInfo["Name"], {"short_form": vfbTerm.term.core.short_form})
queries.append(q)
- # NeuronsSynaptic query - for synaptic neuropils and visual systems
- # Matches XMI criteria: Class + (Synaptic_neuropil OR Visual_system OR Synaptic_neuropil_domain)
- if contains_all_tags(termInfo["SuperTypes"], ["Class"]) and "Neuron" not in termInfo["SuperTypes"] and "Nervous_system" in termInfo["SuperTypes"]:
+ # NeuronsSynaptic query - for neural regions (neuropils, ganglia, visual system, etc.)
+ # Gate: Class + Nervous_system, but NOT Cell (excludes neurons, glia, neuroblasts).
+ if contains_all_tags(termInfo["SuperTypes"], ["Class"]) and "Cell" not in termInfo["SuperTypes"] and "Nervous_system" in termInfo["SuperTypes"]:
q = NeuronsSynaptic_to_schema(termInfo["Name"], {"short_form": vfbTerm.term.core.short_form})
queries.append(q)
- # NeuronsPresynapticHere query - for synaptic neuropils and visual systems
- # Matches XMI criteria: Class + (Synaptic_neuropil OR Visual_system OR Synaptic_neuropil_domain)
- if contains_all_tags(termInfo["SuperTypes"], ["Class"]) and "Neuron" not in termInfo["SuperTypes"] and "Nervous_system" in termInfo["SuperTypes"]:
+ # NeuronsPresynapticHere query - for neural regions (neuropils, ganglia, visual system, etc.)
+ # Gate: Class + Nervous_system, but NOT Cell (excludes neurons, glia, neuroblasts).
+ if contains_all_tags(termInfo["SuperTypes"], ["Class"]) and "Cell" not in termInfo["SuperTypes"] and "Nervous_system" in termInfo["SuperTypes"]:
q = NeuronsPresynapticHere_to_schema(termInfo["Name"], {"short_form": vfbTerm.term.core.short_form})
queries.append(q)
- # NeuronsPostsynapticHere query - for synaptic neuropils and visual systems
- # Matches XMI criteria: Class + (Synaptic_neuropil OR Visual_system OR Synaptic_neuropil_domain)
- if contains_all_tags(termInfo["SuperTypes"], ["Class"]) and "Neuron" not in termInfo["SuperTypes"] and "Nervous_system" in termInfo["SuperTypes"]:
+ # NeuronsPostsynapticHere query - for neural regions (neuropils, ganglia, visual system, etc.)
+ # Gate: Class + Nervous_system, but NOT Cell (excludes neurons, glia, neuroblasts).
+ if contains_all_tags(termInfo["SuperTypes"], ["Class"]) and "Cell" not in termInfo["SuperTypes"] and "Nervous_system" in termInfo["SuperTypes"]:
q = NeuronsPostsynapticHere_to_schema(termInfo["Name"], {"short_form": vfbTerm.term.core.short_form})
queries.append(q)
@@ -1086,11 +1087,18 @@ def term_info_parse_object(results, short_form):
q = ComponentsOf_to_schema(termInfo["Name"], {"short_form": vfbTerm.term.core.short_form})
queries.append(q)
- # PartsOf query - for any Class except neuron classes
- # Matches XMI criteria: Class (any)
- # Excluded for neuron classes: anatomical sub-parts of a neuron type are not modelled
- # in the ontology in a way that makes this query useful at the class level.
- if contains_all_tags(termInfo["SuperTypes"], ["Class"]) and "Neuron" not in termInfo["SuperTypes"]:
+ # PartsOf query - for anatomical classes that are not individual cells
+ # Gate: Class + Anatomy, but NOT Cell.
+ # - Anatomy: "part of" is only meaningful for anatomical structures; it
+ # excludes non-anatomy classes (genes, features, GO process terms,
+ # deprecated/stage classes, and data-less expression patterns lacking
+ # the Anatomy tag).
+ # - NOT Cell: excludes individual cell types (neurons, glia, neuroblasts),
+ # whose anatomical sub-parts are not modelled usefully at the class
+ # level. Cell subsumes Neuron, so this also keeps neuron classes out.
+ # Retains neuropils, tracts/nerves, clones, ganglia, whole regions, and
+ # imaged expression patterns/splits (all Anatomy, not Cell).
+ if contains_all_tags(termInfo["SuperTypes"], ["Class", "Anatomy"]) and "Cell" not in termInfo["SuperTypes"]:
q = PartsOf_to_schema(termInfo["Name"], {"short_form": vfbTerm.term.core.short_form})
queries.append(q)
@@ -1135,10 +1143,10 @@ def term_info_parse_object(results, short_form):
q = LineageClonesIn_to_schema(termInfo["Name"], {"short_form": vfbTerm.term.core.short_form})
queries.append(q)
- # ImagesNeurons query - for synaptic neuropils
- # Matches XMI criteria: Class + (Synaptic_neuropil OR Synaptic_neuropil_domain)
+ # ImagesNeurons query - for anatomical regions (mirrors NeuronsPartHere)
+ # Gate: Class + (Synaptic_neuropil OR Anatomy), but NOT Cell.
# Returns individual neuron images (instances) rather than neuron classes
- if contains_all_tags(termInfo["SuperTypes"], ["Class"]) and "Neuron" not in termInfo["SuperTypes"] and (
+ if contains_all_tags(termInfo["SuperTypes"], ["Class"]) and "Cell" not in termInfo["SuperTypes"] and (
"Synaptic_neuropil" in termInfo["SuperTypes"] or
"Anatomy" in termInfo["SuperTypes"]
):
@@ -1357,12 +1365,29 @@ def term_info_parse_object(results, short_form):
)
instance_super_types = termInfo["SuperTypes"] or []
if termInfo["IsIndividual"] and any(t in instance_super_types for t in inherit_instance_types):
- # Legacy anchored on the first parent Class (classVariable = parents[0]).
- inherit_parent = next(
- (p for p in (vfbTerm.parents or []) if p.types and "Class" in p.types),
- None,
- )
- if inherit_parent is not None:
+ # Choose the parent class(es) that best represent the instance's
+ # inherited type. Exclude the generic preferred_root (e.g.
+ # 'anatomical entity'): it is a direct parent of many instances but
+ # would anchor broad queries like "Subclasses of anatomical entity"
+ # on the instance. Among the remaining direct Class parents, keep
+ # those whose type set overlaps inherit_instance_types the most (the
+ # "closest match"); ties are all kept so an instance typed by several
+ # equally-specific classes inherits each class's queries, anchored on
+ # that class. Every gated instance has such a parent in the data, so
+ # best_overlap is >= 1 (best_overlap == 0 -> inherit nothing, since no
+ # parent is a valid target for the facet-specific queries).
+ inherit_set = set(inherit_instance_types)
+ candidates = [
+ p for p in (vfbTerm.parents or [])
+ if p.types and "Class" in p.types and "preferred_root" not in p.types
+ ]
+ best_overlap = max((len(set(p.types) & inherit_set) for p in candidates), default=0)
+ inherit_parents = [
+ p for p in candidates
+ if best_overlap > 0 and len(set(p.types) & inherit_set) == best_overlap
+ ]
+ seen = set()
+ for inherit_parent in inherit_parents:
p_types = set(inherit_parent.types or [])
p_ref = {"short_form": inherit_parent.short_form}
p_label = inherit_parent.label if inherit_parent.label else inherit_parent.short_form
@@ -1371,11 +1396,11 @@ def term_info_parse_object(results, short_form):
# own menu above so an instance shows exactly what its class shows.
inheritable_class_queries = (
(ListAllAvailableImages_to_schema, lambda p: {"Class", "Anatomy"} <= p),
- (NeuronsPartHere_to_schema, lambda p: "Class" in p and "Neuron" not in p and ("Synaptic_neuropil" in p or "Anatomy" in p)),
- (NeuronsSynaptic_to_schema, lambda p: "Class" in p and ("Synaptic_neuropil" in p or "Visual_system" in p or "Synaptic_neuropil_domain" in p)),
- (NeuronsPresynapticHere_to_schema, lambda p: "Class" in p and ("Synaptic_neuropil" in p or "Visual_system" in p or "Synaptic_neuropil_domain" in p)),
- (NeuronsPostsynapticHere_to_schema, lambda p: "Class" in p and ("Synaptic_neuropil" in p or "Visual_system" in p or "Synaptic_neuropil_domain" in p)),
- (ImagesNeurons_to_schema, lambda p: "Class" in p and ("Synaptic_neuropil" in p or "Synaptic_neuropil_domain" in p)),
+ (NeuronsPartHere_to_schema, lambda p: "Class" in p and "Cell" not in p and ("Synaptic_neuropil" in p or "Anatomy" in p)),
+ (NeuronsSynaptic_to_schema, lambda p: "Class" in p and "Cell" not in p and "Nervous_system" in p),
+ (NeuronsPresynapticHere_to_schema, lambda p: "Class" in p and "Cell" not in p and "Nervous_system" in p),
+ (NeuronsPostsynapticHere_to_schema, lambda p: "Class" in p and "Cell" not in p and "Nervous_system" in p),
+ (ImagesNeurons_to_schema, lambda p: "Class" in p and "Cell" not in p and ("Synaptic_neuropil" in p or "Anatomy" in p)),
(TractsNervesInnervatingHere_to_schema, lambda p: "Class" in p and ("Synaptic_neuropil" in p or "Synaptic_neuropil_domain" in p)),
(LineageClonesIn_to_schema, lambda p: "Class" in p and ("Synaptic_neuropil" in p or "Synaptic_neuropil_domain" in p)),
(NeuronClassesFasciculatingHere_to_schema, lambda p: "Class" in p and "Neuron_projection_bundle" in p),
@@ -1388,12 +1413,16 @@ def term_info_parse_object(results, short_form):
(TargetNeurons_to_schema, lambda p: {"Class", "Split"} <= p),
(DownstreamClassConnectivity_to_schema, lambda p: {"Class", "Neuron"} <= p),
(UpstreamClassConnectivity_to_schema, lambda p: {"Class", "Neuron"} <= p),
- (PartsOf_to_schema, lambda p: "Class" in p and "Neuron" not in p),
+ (PartsOf_to_schema, lambda p: {"Class", "Anatomy"} <= p and "Cell" not in p),
(SubclassesOf_to_schema, lambda p: "Class" in p),
)
for schema_fn, predicate in inheritable_class_queries:
if predicate(p_types):
- queries.append(schema_fn(p_label, p_ref))
+ q = schema_fn(p_label, p_ref)
+ key = (q.query, inherit_parent.short_form)
+ if key not in seen:
+ seen.add(key)
+ queries.append(q)
# Add Publications to the termInfo object
if vfbTerm.pubs and len(vfbTerm.pubs) > 0:
@@ -1778,10 +1807,14 @@ def PartsOf_to_schema(name, take_default):
"""
Schema for PartsOf query.
Finds parts of the specified anatomical class.
-
- Matching criteria from XMI:
- - Class (any)
-
+
+ Matching criteria:
+ - Class + Anatomy, but NOT Cell (gated in term_info_parse_object).
+
+ Note: `takes` can only express positive facets (Class + Anatomy); the
+ "NOT Cell" exclusion lives in the Python gate, since the $and/$or grammar
+ has no negation operator.
+
Query chain: Owlery part_of query → process → SOLR
OWL query: "part_of some $ID"
"""
@@ -1789,7 +1822,7 @@ def PartsOf_to_schema(name, take_default):
label = f"Parts of {name}"
function = "get_parts_of"
takes = {
- "short_form": {"$and": ["Class"]},
+ "short_form": {"$and": ["Class", "Anatomy"]},
"default": take_default,
}
preview = 5