Skip to content

Commit fffb637

Browse files
committed
Implement neuron region connectivity query and update documentation
1 parent 9b7d8ec commit fffb637

3 files changed

Lines changed: 120 additions & 12 deletions

File tree

VFB_QUERIES_REFERENCE.md

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -770,15 +770,61 @@ When implementing a new query, ensure:
770770
- **Query Chain**: Neo4j → Neo4j Pass → SOLR → Process
771771
- **Status**: ❌ **NOT IMPLEMENTED**
772772

773-
#### 22. **neuron_region_connectivity_query**
773+
#### 22. **neuron_region_connectivity_query**
774774
- **ID**: `ref_neuron_region_connectivity_query` / `compound_neuron_region_connectivity_query`
775775
- **Name**: "Show connectivity to regions from Neuron X"
776776
- **Description**: "Show connectivity per region for $NAME"
777777
- **Matching Criteria**: Region_connectivity
778778
- **Query Chain**: Neo4j compound query → Process
779-
- **Status**: ❌ **NOT IMPLEMENTED**
779+
- **Status**: ✅ **FULLY IMPLEMENTED** (November 2025)
780+
781+
**Implementation**:
782+
- Schema: `NeuronRegionConnectivityQuery_to_schema()`
783+
- Execution: `get_neuron_region_connectivity(term_id, return_dataframe=True, limit=-1)`
784+
- Preview: 5 results
785+
- Preview Columns: id, region, presynaptic_terminals, postsynaptic_terminals, tags
786+
- **Relationships**: Uses `has_presynaptic_terminals_in` and `has_postsynaptic_terminal_in`
787+
- **Terminology**: Uses VFB site conventions - "Brain Region", "Presynaptic Terminals", "Postsynaptic Terminals"
780788

781-
#### 23. **neuron_neuron_connectivity_query**
789+
**Parameters**:
790+
- `term_id`: Short form of the neuron (Individual)
791+
- `return_dataframe`: Returns pandas DataFrame if True, otherwise returns formatted dict (default: True)
792+
- `limit`: Maximum number of results to return (default: -1 for all results)
793+
794+
**Cypher Query** (from XMI spec):
795+
```cypher
796+
MATCH (primary:Individual {short_form: $NAME})
797+
MATCH (target:Individual)<-[r:has_presynaptic_terminals_in|has_postsynaptic_terminal_in]-(primary)
798+
WITH DISTINCT collect(properties(r)) + {} as props, target, primary
799+
WITH apoc.map.removeKeys(apoc.map.merge(props[0], props[1]),
800+
['iri', 'short_form', 'Related', 'label', 'type']) as synapse_counts,
801+
target, primary
802+
RETURN
803+
target.short_form AS id,
804+
target.label AS region,
805+
synapse_counts.`pre` AS presynaptic_terminals,
806+
synapse_counts.`post` AS postsynaptic_terminals,
807+
target.uniqueFacets AS tags
808+
```
809+
810+
**Expected Output Structure**:
811+
```python
812+
{
813+
'headers': {
814+
'id': {'title': 'Region ID', 'type': 'selection_id', 'order': -1},
815+
'region': {'title': 'Brain Region', 'type': 'markdown', 'order': 0},
816+
'presynaptic_terminals': {'title': 'Presynaptic Terminals', 'type': 'number', 'order': 1},
817+
'postsynaptic_terminals': {'title': 'Postsynaptic Terminals', 'type': 'number', 'order': 2},
818+
'tags': {'title': 'Region Types', 'type': 'list', 'order': 3},
819+
},
820+
'data': [...],
821+
'count': <number>
822+
}
823+
```
824+
825+
---
826+
827+
#### 23. **neuron_neuron_connectivity_query**
782828

783829
---
784830

src/test/test_expression_pattern_fragments.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
This query uses Owlery instances endpoint to find individual expression pattern
55
fragment images that are part of a specified expression pattern.
66
7-
TODO: Query needs fixing - returns 0 results for known test cases.
8-
Example: VFBexp_FBtp0022557 should have image VFB_00008416 but query returns 0 results.
7+
FIXED: Query now works correctly with proper IRI resolution for VFBexp_* IDs.
8+
Example: VFBexp_FBtp0022557 returns image VFB_00008416 as expected.
99
"""
1010

1111
import unittest
@@ -28,9 +28,9 @@ class TestExpressionPatternFragments(unittest.TestCase):
2828

2929
def setUp(self):
3030
"""Set up test fixtures."""
31-
# TODO: Query needs fixing - VFBexp_FBtp0022557 has image VFB_00008416 but returns 0 results
32-
# epFrag should find individual fragments (Expression_pattern_fragment) that are part_of a Class Expression_pattern
33-
self.test_expression_pattern = "VFBexp_FBtp0022557" # P{VGlut-GAL4.D} expression pattern (should have VFB_00008416)
31+
# Expression pattern that has known fragments
32+
# epFrag finds individual fragments (Expression_pattern_fragment) that are part_of a Class Expression_pattern
33+
self.test_expression_pattern = "VFBexp_FBtp0022557" # P{VGlut-GAL4.D} expression pattern (has VFB_00008416)
3434

3535
def test_schema_generation(self):
3636
"""Test that the schema function generates correct Query object."""
@@ -44,19 +44,23 @@ def test_schema_generation(self):
4444
self.assertIn("thumbnail", schema.preview_columns)
4545

4646
def test_expression_pattern_fragments_execution(self):
47-
"""Test that expression pattern fragments query executes without errors."""
47+
"""Test that expression pattern fragments query executes and returns results."""
4848
result = get_expression_pattern_fragments(self.test_expression_pattern)
4949

5050
self.assertIsNotNone(result)
5151
# Result can be dict or DataFrame
5252
if isinstance(result, dict):
5353
self.assertIn('count', result)
54-
# TODO: Should return at least 1 result (VFB_00008416) but currently returns 0
55-
# Query implementation needs debugging
56-
self.assertGreaterEqual(result['count'], 0)
54+
# Should return at least 1 result (VFB_00008416)
55+
self.assertGreater(result['count'], 0,
56+
f"Expected at least 1 result for {self.test_expression_pattern}")
57+
print(f"\n✓ Query returned {result['count']} expression pattern fragments")
5758
else:
5859
# DataFrame
5960
self.assertIsInstance(result, pd.DataFrame)
61+
self.assertGreater(len(result), 0,
62+
f"Expected at least 1 result for {self.test_expression_pattern}")
63+
print(f"\n✓ Query returned {len(result)} expression pattern fragments")
6064

6165
def test_return_dataframe_parameter(self):
6266
"""Test that return_dataframe parameter works correctly."""

src/vfbquery/vfb_queries.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2287,6 +2287,64 @@ def get_neuron_neuron_connectivity(short_form: str, return_dataframe=True, limit
22872287
}
22882288

22892289

2290+
@with_solr_cache('neuron_region_connectivity_query')
2291+
def get_neuron_region_connectivity(short_form: str, return_dataframe=True, limit: int = -1):
2292+
"""
2293+
Retrieves brain regions where the specified neuron has synaptic terminals.
2294+
2295+
This implements the neuron_region_connectivity_query from the VFB XMI specification.
2296+
Query chain (from XMI): Neo4j compound query → process
2297+
Matching criteria: Individual + has_region_connectivity
2298+
2299+
Uses has_presynaptic_terminals_in and has_postsynaptic_terminal_in relationships
2300+
to find brain regions where the neuron makes connections.
2301+
2302+
:param short_form: short form of the neuron (Individual)
2303+
:param return_dataframe: Returns pandas dataframe if true, otherwise returns formatted dict
2304+
:param limit: maximum number of results to return (default -1, returns all results)
2305+
:return: Brain regions with presynaptic and postsynaptic terminal counts
2306+
"""
2307+
# Build Cypher query based on XMI spec pattern
2308+
cypher = f"""
2309+
MATCH (primary:Individual {{short_form: '{short_form}'}})
2310+
MATCH (target:Individual)<-[r:has_presynaptic_terminals_in|has_postsynaptic_terminal_in]-(primary)
2311+
WITH DISTINCT collect(properties(r)) + {{}} as props, target, primary
2312+
WITH apoc.map.removeKeys(apoc.map.merge(props[0], props[1]), ['iri', 'short_form', 'Related', 'label', 'type']) as synapse_counts,
2313+
target,
2314+
primary
2315+
RETURN
2316+
target.short_form AS id,
2317+
target.label AS region,
2318+
synapse_counts.`pre` AS presynaptic_terminals,
2319+
synapse_counts.`post` AS postsynaptic_terminals,
2320+
target.uniqueFacets AS tags
2321+
"""
2322+
if limit != -1:
2323+
cypher += f" LIMIT {limit}"
2324+
2325+
# Run query using Neo4j client
2326+
results = vc.nc.commit_list([cypher])
2327+
rows = get_dict_cursor()(results)
2328+
2329+
# Format output
2330+
if return_dataframe:
2331+
df = pd.DataFrame(rows)
2332+
return df
2333+
2334+
headers = {
2335+
'id': {'title': 'Region ID', 'type': 'selection_id', 'order': -1},
2336+
'region': {'title': 'Brain Region', 'type': 'markdown', 'order': 0},
2337+
'presynaptic_terminals': {'title': 'Presynaptic Terminals', 'type': 'number', 'order': 1},
2338+
'postsynaptic_terminals': {'title': 'Postsynaptic Terminals', 'type': 'number', 'order': 2},
2339+
'tags': {'title': 'Region Types', 'type': 'list', 'order': 3},
2340+
}
2341+
return {
2342+
'headers': headers,
2343+
'data': rows,
2344+
'count': len(rows)
2345+
}
2346+
2347+
22902348
@with_solr_cache('images_neurons')
22912349
def get_images_neurons(short_form: str, return_dataframe=True, limit: int = -1):
22922350
"""

0 commit comments

Comments
 (0)