Skip to content

Commit 9f2ec0d

Browse files
committed
NeuronRegion image cols + scRNAseq family anatomy markdown + Cell type column
Bundles five column-gap fixes layered on top of v1.14.11: 1. get_neuron_region_connectivity (NeuronRegionConnectivityQuery) Same pattern as v1.14.11's NeuronNeuronConnectivityQuery fix. Adds Type / Template / Imaging Technique / Thumbnail via a CALL subquery walk over (target)<-[:depicts]-(channel)-[in_register_with] ->(template)-[:depicts]->(template_anat) and (channel) -[:is_specified_output_of]->(technique:Class). LIMIT before CALL so the multi-hop walk only runs on returned rows. region wrapped as [label](short_form) markdown so the Brain Region column is clickable. 2. get_cluster_expression (clusterExpression / GenesExpressed) Robbie's screenshot 4 ("more problematic"): the anatomy field was emitted as a raw Cypher map and the Java processor's generic formatter dumped it as {symbol=,iri=...,label=...} brace-equals text. Cypher RETURN now emits apoc.text.format("[%s](%s)", [anatomy.label, anatomy.short_form]) AS anatomy Header "Anatomy" -> "Cell type" matching v2 prod schema. Header type metadata -> markdown so the column resolves through the standard chip-clicking pipeline. anatomy added to encode_markdown_links columns. 3. get_expression_cluster (expressionCluster) Identical anatomy-dict-dump bug. Same shape fix. 4. get_scrnaseq_dataset_data (scRNAdatasetData) Identical anatomy-dict-dump bug. Same shape fix. pubs is left as the nested-map list it already was; Java processor handles pubs.core via the v1.14.7 path. 5. get_anatomy_scrnaseq (anatScRNAseqQuery) v1.14.8 already shipped the Dataset markdown fix. This release adds a "Cell type" column showing the cluster's specific composed_primarily_of anatomy (the `sub` class in the Cypher), matching v2 prod's four-column layout: Cluster | Cell type | Dataset | Reference `sub` is carried through the WITH chain and emitted as apoc.text.format("[%s](%s)", [sub.label, sub.short_form]) AS cell_type Verified live against pdb (FBlc0006125, IPN cluster, LIMIT 3, 428 ms elapsed): anatomy: '[adult antennal lobe projection neuron adPN](FBbt_00007438)' (was the {symbol=IPN,iri=...} dict-dump on v2-dev before this fix) Patch bump 1.14.11 -> 1.14.12. @with_solr_cache wrappers in cached_functions.py invalidate by function source-hash change per [feedback_cache_invalidation].
1 parent 01f3ab5 commit 9f2ec0d

2 files changed

Lines changed: 97 additions & 69 deletions

File tree

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
here = path.abspath(path.dirname(__file__))
55

6-
__version__ = "1.14.11"
6+
__version__ = "1.14.12"
77

88
# Get the long description from the README file
99
with open(path.join(here, 'README.md')) as f:

src/vfbquery/vfb_queries.py

Lines changed: 96 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -3232,59 +3232,85 @@ def get_neuron_neuron_connectivity(short_form: str, return_dataframe=True, limit
32323232

32333233
@with_solr_cache('neuron_region_connectivity_query')
32343234
def get_neuron_region_connectivity(short_form: str, return_dataframe=True, limit: int = -1):
3235-
"""
3236-
Retrieves brain regions where the specified neuron has synaptic terminals.
3237-
3238-
This implements the neuron_region_connectivity_query from the VFB XMI specification.
3239-
Query chain (from XMI): Neo4j compound query → process
3240-
Matching criteria: Individual + has_region_connectivity
3241-
3242-
Uses has_presynaptic_terminals_in and has_postsynaptic_terminal_in relationships
3243-
to find brain regions where the neuron makes connections.
3244-
3235+
"""Retrieves brain regions where the specified neuron has synaptic terminals.
3236+
3237+
v1.14.12: add Type / Template / Imaging Technique / Thumbnail columns
3238+
matching the AnatomyExpressedIn / NeuronNeuronConnectivityQuery pattern.
3239+
CALL subquery walks (target)<-[:depicts]-(channel)-[in_register_with]->
3240+
(template)-[:depicts]->(template_anat) and (channel)-[:is_specified_output_of]
3241+
->(technique). LIMIT applied before the CALL fires so the multi-hop walk
3242+
only runs on returned rows. region wrapped as `[label](short_form)`
3243+
markdown so the Brain Region column is clickable.
3244+
32453245
:param short_form: short form of the neuron (Individual)
32463246
:param return_dataframe: Returns pandas dataframe if true, otherwise returns formatted dict
32473247
:param limit: maximum number of results to return (default -1, returns all results)
3248-
:return: Brain regions with presynaptic and postsynaptic terminal counts
3248+
:return: Brain regions with presynaptic and postsynaptic terminal counts + image cols
32493249
"""
3250-
# Build Cypher query based on XMI spec pattern
3250+
limit_clause = f"LIMIT {limit}" if limit != -1 else ""
32513251
cypher = f"""
3252-
MATCH (primary:Individual {{short_form: '{short_form}'}})
3253-
MATCH (target:Individual)<-[r:has_presynaptic_terminals_in|has_postsynaptic_terminal_in]-(primary)
3254-
WITH DISTINCT collect(properties(r)) + {{}} as props, target, primary
3255-
WITH apoc.map.removeKeys(apoc.map.merge(props[0], props[1]), ['iri', 'short_form', 'Related', 'label', 'type']) as synapse_counts,
3256-
target,
3257-
primary
3258-
RETURN
3259-
target.short_form AS id,
3260-
target.label AS region,
3261-
synapse_counts.`pre` AS presynaptic_terminals,
3262-
synapse_counts.`post` AS postsynaptic_terminals,
3263-
target.uniqueFacets AS tags
3252+
MATCH (primary:Individual {{short_form: '{short_form}'}})
3253+
MATCH (target:Individual)<-[r:has_presynaptic_terminals_in|has_postsynaptic_terminal_in]-(primary)
3254+
WITH DISTINCT collect(properties(r)) + [{{}}] AS props, target, primary
3255+
WITH apoc.map.removeKeys(
3256+
apoc.map.merge(props[0], coalesce(props[1], {{}})),
3257+
['iri', 'short_form', 'Related', 'label', 'type']
3258+
) AS synapse_counts,
3259+
target, primary
3260+
OPTIONAL MATCH (target)-[:INSTANCEOF]->(typ:Class)
3261+
WITH target, synapse_counts,
3262+
apoc.text.join(
3263+
collect(DISTINCT coalesce(typ.label, '')),
3264+
'; '
3265+
) AS type
3266+
ORDER BY target.label
3267+
{limit_clause}
3268+
CALL {{
3269+
WITH target
3270+
OPTIONAL MATCH (target)<-[:depicts]-(channel:Individual)-[irw:in_register_with]->(template:Individual)-[:depicts]->(template_anat:Individual)
3271+
OPTIONAL MATCH (channel)-[:is_specified_output_of]->(technique:Class)
3272+
WITH channel, template, template_anat, technique, irw
3273+
LIMIT 1
3274+
RETURN channel, template, template_anat, technique, irw
3275+
}}
3276+
RETURN
3277+
target.short_form AS id,
3278+
apoc.text.format("[%s](%s)", [target.label, target.short_form]) AS region,
3279+
type,
3280+
synapse_counts.`pre` AS presynaptic_terminals,
3281+
synapse_counts.`post` AS postsynaptic_terminals,
3282+
apoc.text.join(coalesce(target.uniqueFacets, []), '|') AS tags,
3283+
REPLACE(apoc.text.format("[%s](%s)", [COALESCE(template_anat.symbol[0], template_anat.label), template_anat.short_form]), '[null](null)', '') AS template,
3284+
coalesce(technique.label, '') AS technique,
3285+
REPLACE(apoc.text.format("[![%s](%s '%s')](%s)", [COALESCE(target.symbol[0], coalesce(target.label, 'image')) + " aligned to " + COALESCE(template_anat.symbol[0], template_anat.label), REPLACE(COALESCE(irw.thumbnail[0], ''), 'thumbnailT.png', 'thumbnail.png'), COALESCE(target.symbol[0], coalesce(target.label, 'image')) + " aligned to " + COALESCE(template_anat.symbol[0], template_anat.label), template_anat.short_form + "," + target.short_form]), "[![null]( 'null')](null)", "") AS thumbnail
32643286
"""
3265-
if limit != -1:
3266-
cypher += f" LIMIT {limit}"
32673287

3268-
# Run query using Neo4j client
32693288
results = vc.nc.commit_list([cypher])
32703289
rows = get_dict_cursor()(results)
3271-
3272-
# Format output
3290+
32733291
if return_dataframe:
32743292
df = pd.DataFrame(rows)
3293+
if not df.empty:
3294+
df = encode_markdown_links(df, ['region', 'template', 'thumbnail'])
32753295
return df
3276-
3277-
headers = {
3278-
'id': {'title': 'Region ID', 'type': 'selection_id', 'order': -1},
3279-
'region': {'title': 'Brain Region', 'type': 'markdown', 'order': 0},
3280-
'presynaptic_terminals': {'title': 'Presynaptic Terminals', 'type': 'number', 'order': 1},
3281-
'postsynaptic_terminals': {'title': 'Postsynaptic Terminals', 'type': 'number', 'order': 2},
3282-
'tags': {'title': 'Region Types', 'type': 'list', 'order': 3},
3283-
}
3296+
32843297
return {
3285-
'headers': headers,
3286-
'rows': rows,
3287-
'count': len(rows)
3298+
'headers': {
3299+
'id': {'title': 'Region ID', 'type': 'selection_id', 'order': -1},
3300+
'region': {'title': 'Brain Region', 'type': 'markdown', 'order': 0},
3301+
'type': {'title': 'Type', 'type': 'text', 'order': 1},
3302+
'presynaptic_terminals': {'title': 'Presynaptic Terminals', 'type': 'number', 'order': 2},
3303+
'postsynaptic_terminals': {'title': 'Postsynaptic Terminals','type': 'number', 'order': 3},
3304+
'template': {'title': 'Template', 'type': 'markdown', 'order': 4},
3305+
'technique': {'title': 'Imaging Technique', 'type': 'text', 'order': 5},
3306+
'tags': {'title': 'Tags', 'type': 'tags', 'order': 6},
3307+
'thumbnail': {'title': 'Thumbnail', 'type': 'markdown', 'order': 9},
3308+
},
3309+
'rows': [
3310+
{k: row.get(k) for k in ['id', 'region', 'type', 'presynaptic_terminals', 'postsynaptic_terminals', 'template', 'technique', 'tags', 'thumbnail']}
3311+
for row in rows
3312+
],
3313+
'count': len(rows),
32883314
}
32893315

32903316

@@ -4301,7 +4327,7 @@ def get_anatomy_scrnaseq(anatomy_short_form: str, return_dataframe=True, limit:
43014327
MATCH (sub:Class)
43024328
WHERE sub.short_form IN {anat_short_forms!r}
43034329
MATCH (sub)<-[:composed_primarily_of]-(c:Cluster)-[:has_source]->(ds:scRNAseq_DataSet)
4304-
WITH DISTINCT primary, c, ds
4330+
WITH DISTINCT primary, c, ds, sub
43054331
OPTIONAL MATCH (ds)-[:has_reference]->(p:pub)
43064332
WITH {{
43074333
short_form: c.short_form,
@@ -4332,11 +4358,12 @@ def get_anatomy_scrnaseq(anatomy_short_form: str, return_dataframe=True, limit:
43324358
FlyBase: coalesce(([]+p.FlyBase)[0], ''),
43334359
DOI: coalesce(([]+p.DOI)[0], '')
43344360
}}) AS pubs,
4335-
primary
4361+
primary, sub
43364362
RETURN
43374363
cluster.short_form AS id,
43384364
apoc.text.format("[%s](%s)", [cluster.label, cluster.short_form]) AS name,
43394365
apoc.text.join(cluster.unique_facets, '|') AS tags,
4366+
apoc.text.format("[%s](%s)", [sub.label, sub.short_form]) AS cell_type,
43404367
apoc.text.format("[%s](%s)", [dataset.label, dataset.short_form]) AS dataset,
43414368
pubs
43424369
ORDER BY cluster.label
@@ -4351,22 +4378,23 @@ def get_anatomy_scrnaseq(anatomy_short_form: str, return_dataframe=True, limit:
43514378

43524379
# Encode markdown links
43534380
if not df.empty:
4354-
columns_to_encode = ['name', 'dataset']
4381+
columns_to_encode = ['name', 'cell_type', 'dataset']
43554382
df = encode_markdown_links(df, columns_to_encode)
4356-
4383+
43574384
if return_dataframe:
43584385
return df
43594386
else:
43604387
formatted_results = {
43614388
"headers": {
4362-
"id": {"title": "ID", "type": "selection_id", "order": -1},
4363-
"name": {"title": "Cluster", "type": "markdown", "order": 0},
4364-
"tags": {"title": "Tags", "type": "tags", "order": 1},
4365-
"dataset": {"title": "Dataset", "type": "markdown", "order": 2},
4366-
"pubs": {"title": "Publications", "type": "metadata", "order": 3}
4389+
"id": {"title": "ID", "type": "selection_id", "order": -1},
4390+
"name": {"title": "Cluster", "type": "markdown", "order": 0},
4391+
"cell_type": {"title": "Cell type", "type": "markdown", "order": 1},
4392+
"dataset": {"title": "Dataset", "type": "markdown", "order": 2},
4393+
"pubs": {"title": "Publications", "type": "metadata", "order": 3},
4394+
"tags": {"title": "Tags", "type": "tags", "order": 4}
43674395
},
43684396
"rows": [
4369-
{key: row[key] for key in ["id", "name", "tags", "dataset", "pubs"]}
4397+
{key: row[key] for key in ["id", "name", "cell_type", "dataset", "pubs", "tags"]}
43704398
for row in safe_to_dict(df, sort_by_id=False)
43714399
],
43724400
"count": total_count
@@ -4436,7 +4464,7 @@ def get_cluster_expression(cluster_short_form: str, return_dataframe=True, limit
44364464
apoc.text.join(gene.unique_facets, '|') AS tags,
44374465
expression_level,
44384466
expression_extent,
4439-
anatomy
4467+
apoc.text.format("[%s](%s)", [anatomy.label, anatomy.short_form]) AS anatomy
44404468
ORDER BY expression_level DESC, gene.symbol
44414469
"""
44424470

@@ -4449,23 +4477,23 @@ def get_cluster_expression(cluster_short_form: str, return_dataframe=True, limit
44494477

44504478
# Encode markdown links
44514479
if not df.empty:
4452-
columns_to_encode = ['name']
4480+
columns_to_encode = ['name', 'anatomy']
44534481
df = encode_markdown_links(df, columns_to_encode)
4454-
4482+
44554483
if return_dataframe:
44564484
return df
44574485
else:
44584486
formatted_results = {
44594487
"headers": {
44604488
"id": {"title": "ID", "type": "selection_id", "order": -1},
44614489
"name": {"title": "Gene", "type": "markdown", "order": 0},
4462-
"tags": {"title": "Tags", "type": "tags", "order": 1},
4490+
"anatomy": {"title": "Cell type", "type": "markdown", "order": 1},
44634491
"expression_level": {"title": "Expression Level", "type": "numeric", "order": 2},
44644492
"expression_extent": {"title": "Expression Extent", "type": "numeric", "order": 3},
4465-
"anatomy": {"title": "Anatomy", "type": "metadata", "order": 4}
4493+
"tags": {"title": "Tags", "type": "tags", "order": 4}
44664494
},
44674495
"rows": [
4468-
{key: row[key] for key in ["id", "name", "tags", "expression_level", "expression_extent", "anatomy"]}
4496+
{key: row[key] for key in ["id", "name", "anatomy", "expression_level", "expression_extent", "tags"]}
44694497
for row in safe_to_dict(df, sort_by_id=False)
44704498
],
44714499
"count": total_count
@@ -4531,7 +4559,7 @@ def get_expression_cluster(gene_short_form: str, return_dataframe=True, limit: i
45314559
apoc.text.join(coalesce(primary.uniqueFacets, []), '|') AS tags,
45324560
expression_level,
45334561
expression_extent,
4534-
anatomy
4562+
apoc.text.format("[%s](%s)", [anatomy.label, anatomy.short_form]) AS anatomy
45354563
ORDER BY expression_level DESC, primary.label
45364564
"""
45374565

@@ -4544,23 +4572,23 @@ def get_expression_cluster(gene_short_form: str, return_dataframe=True, limit: i
45444572

45454573
# Encode markdown links
45464574
if not df.empty:
4547-
columns_to_encode = ['name']
4575+
columns_to_encode = ['name', 'anatomy']
45484576
df = encode_markdown_links(df, columns_to_encode)
4549-
4577+
45504578
if return_dataframe:
45514579
return df
45524580
else:
45534581
formatted_results = {
45544582
"headers": {
45554583
"id": {"title": "ID", "type": "selection_id", "order": -1},
45564584
"name": {"title": "Cluster", "type": "markdown", "order": 0},
4557-
"tags": {"title": "Tags", "type": "tags", "order": 1},
4585+
"anatomy": {"title": "Cell type", "type": "markdown", "order": 1},
45584586
"expression_level": {"title": "Expression Level", "type": "numeric", "order": 2},
45594587
"expression_extent": {"title": "Expression Extent", "type": "numeric", "order": 3},
4560-
"anatomy": {"title": "Anatomy", "type": "metadata", "order": 4}
4588+
"tags": {"title": "Tags", "type": "tags", "order": 4}
45614589
},
45624590
"rows": [
4563-
{key: row[key] for key in ["id", "name", "tags", "expression_level", "expression_extent", "anatomy"]}
4591+
{key: row[key] for key in ["id", "name", "anatomy", "expression_level", "expression_extent", "tags"]}
45644592
for row in safe_to_dict(df, sort_by_id=False)
45654593
],
45664594
"count": total_count
@@ -4627,7 +4655,7 @@ def get_scrnaseq_dataset_data(dataset_short_form: str, return_dataframe=True, li
46274655
c.short_form AS id,
46284656
apoc.text.format("[%s](%s)", [c.label, c.short_form]) AS name,
46294657
apoc.text.join(coalesce(c.uniqueFacets, []), '|') AS tags,
4630-
anatomy,
4658+
apoc.text.format("[%s](%s)", [anatomy.label, anatomy.short_form]) AS anatomy,
46314659
pubs
46324660
ORDER BY c.label
46334661
"""
@@ -4641,22 +4669,22 @@ def get_scrnaseq_dataset_data(dataset_short_form: str, return_dataframe=True, li
46414669

46424670
# Encode markdown links
46434671
if not df.empty:
4644-
columns_to_encode = ['name']
4672+
columns_to_encode = ['name', 'anatomy']
46454673
df = encode_markdown_links(df, columns_to_encode)
4646-
4674+
46474675
if return_dataframe:
46484676
return df
46494677
else:
46504678
formatted_results = {
46514679
"headers": {
46524680
"id": {"title": "ID", "type": "selection_id", "order": -1},
46534681
"name": {"title": "Cluster", "type": "markdown", "order": 0},
4654-
"tags": {"title": "Tags", "type": "tags", "order": 1},
4655-
"anatomy": {"title": "Anatomy", "type": "metadata", "order": 2},
4682+
"anatomy": {"title": "Cell type", "type": "markdown", "order": 1},
4683+
"tags": {"title": "Tags", "type": "tags", "order": 2},
46564684
"pubs": {"title": "Publications", "type": "metadata", "order": 3}
46574685
},
46584686
"rows": [
4659-
{key: row[key] for key in ["id", "name", "tags", "anatomy", "pubs"]}
4687+
{key: row[key] for key in ["id", "name", "anatomy", "tags", "pubs"]}
46604688
for row in safe_to_dict(df, sort_by_id=False)
46614689
],
46624690
"count": total_count

0 commit comments

Comments
 (0)