Skip to content

Commit 44eab23

Browse files
committed
AnatomyExpressedIn: add Stage/Template/Technique/Image columns
Restores the column shape v2 prod's legacy ExpressionOverlapsHere emitted (Name | Reference | Gross_Type | Stage | Template_Space | Imaging_Technique | Images). v2-dev was only emitting the first four; the migration to VFBquery had collapsed the legacy Cypher to id / name / tags / pubs. Mirrors the legacy XMI Cypher at geppetto-vfb@998cc28d9^:model/vfb.xmi dataSources[0]/@queries.9 which walks from the RESULT anatomy class (not the input EP): (anat)<-[:has_source|SUBCLASSOF|INSTANCEOF*]-(:Individual) <-[:depicts]-(channel:Individual) -[:in_register_with]->(template:Individual) -[:depicts]->(template_anat:Individual) (channel)-[:is_specified_output_of]->(technique:Class) (anoni)-[:Related]->(o:FBdv) -- stages get_expression_overlaps_here now wraps each of those in its own CALL subquery, applies LIMIT before they fire so EPs that overlap hundreds of anatomy classes stay bounded, and projects the new fields as plain string / markdown columns: pubs Reference (already in v1.14.0) stages Stage (text) template Template_Space (markdown) technique Imaging_Technique (text) thumbnail Images (markdown) The headers / preview_columns dict on AnatomyExpressedIn_to_schema is extended in matching order, and encode_markdown_links is given the three new markdown columns. No behaviour change for any other query. Stages will be empty for most current pdb data — the (:Individual) -[:Related]->(:FBdv) edges weren't preserved during a prior indexer refactor; the column is kept here for parity with the legacy XMI shape and will populate when the development-stage edges return. Verified locally on VFBexp_FBti0103609 (P{GSV6}GS10340): DN1 — Richier 2008 / JRC2018Unisex / SBFSEM DN2 — Richier 2008 / JRC2018Unisex / TEM DN3 — Richier 2008 / JRC2018Unisex / FIB-SEM matching v2 prod row-for-row. Patch bump 1.14.1 -> 1.14.2. New columns invalidate stale @with_solr_cache buckets implicitly via the result shape change; existing cached entries that don't carry the new keys will be treated as misses and refreshed naturally.
1 parent 5205afb commit 44eab23

2 files changed

Lines changed: 64 additions & 22 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.1"
6+
__version__ = "1.14.2"
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: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1779,7 +1779,11 @@ def AnatomyExpressedIn_to_schema(name, take_default):
17791779
"default": take_default,
17801780
}
17811781
preview = 5
1782-
preview_columns = ["id", "name", "tags", "pubs"]
1782+
# v1.14.2: schema gained Stage / Template / Imaging Technique /
1783+
# Thumbnail columns to match the legacy ExpressionOverlapsHere
1784+
# column shape (Name / Reference / Gross_Type / Stage /
1785+
# Template_Space / Imaging_Technique / Images).
1786+
preview_columns = ["id", "name", "pubs", "tags", "stages", "template", "technique", "thumbnail"]
17831787

17841788
return Query(query=query, label=label, function=function, takes=takes, preview=preview, preview_columns=preview_columns)
17851789

@@ -2809,47 +2813,85 @@ def get_expression_overlaps_here(expression_pattern_short_form: str, return_data
28092813
count_df = pd.DataFrame.from_records(get_dict_cursor()(count_results))
28102814
total_count = count_df['total_count'][0] if not count_df.empty else 0
28112815

2816+
# v1.14.2: mirror the legacy XMI Cypher for ExpressionOverlapsHere
2817+
# at geppetto-vfb@998cc28d9^:model/vfb.xmi dataSources[0]/@queries.9
2818+
# — add Stage / Template_Space / Imaging_Technique / Images columns
2819+
# by walking from the result anatomy class (not the input EP):
2820+
# - stages : (anoni)-[:Related]->(:FBdv) (development stage)
2821+
# - template : (anat)<-[:has_source|SUBCLASSOF|INSTANCEOF*]
2822+
# -(:Individual)
2823+
# <-[:depicts]-(channel:Individual)
2824+
# -[:in_register_with]->(template:Individual)
2825+
# -[:depicts]->(template_anat:Individual)
2826+
# - technique: (channel)-[:is_specified_output_of]->(technique:Class)
2827+
# - thumbnail: built from template_anat + i + irw.thumbnail[0]
2828+
# Apply LIMIT before the CALL subqueries to keep the multi-hop walks
2829+
# bounded on EPs with hundreds of overlapping anatomy classes.
2830+
limit_clause = f"LIMIT {limit}" if limit != -1 else ""
28122831
main_query = f"""
28132832
MATCH (ep:Class:Expression_pattern)<-[ar:overlaps|part_of]-(anoni:Individual)-[:INSTANCEOF]->(anat:Class:Anatomy)
28142833
WHERE ep.short_form = '{expression_pattern_short_form}'
2815-
WITH DISTINCT collect(DISTINCT ar.pub[0]) as pubs, anat, ep
2816-
UNWIND pubs as p
2817-
OPTIONAL MATCH (pub:pub {{ short_form: p}})
2818-
WITH anat, ep, collect({{
2819-
core: {{ short_form: pub.short_form, label: coalesce(pub.label,''), iri: pub.iri, types: labels(pub), symbol: coalesce(pub.symbol[0], '') }},
2820-
PubMed: coalesce(pub.PMID[0], ''),
2821-
FlyBase: coalesce(([]+pub.FlyBase)[0], ''),
2822-
DOI: coalesce(pub.DOI[0], '')
2823-
}}) as pubs
2834+
WITH anat, collect(DISTINCT ar.pub[0]) AS pub_shorts, collect(DISTINCT anoni) AS anonis
2835+
ORDER BY anat.label
2836+
{limit_clause}
2837+
CALL {{
2838+
WITH pub_shorts
2839+
UNWIND pub_shorts AS p_sf
2840+
OPTIONAL MATCH (p:pub {{ short_form: p_sf }})
2841+
WITH p WHERE p IS NOT NULL
2842+
AND coalesce(p.label, p.short_form) IS NOT NULL
2843+
AND coalesce(p.label, p.short_form) <> ''
2844+
AND coalesce(p.label, p.short_form) <> 'Unattributed'
2845+
RETURN apoc.text.join(collect(DISTINCT coalesce(p.label, p.short_form)), '; ') AS pubs
2846+
}}
2847+
CALL {{
2848+
WITH anonis
2849+
UNWIND anonis AS anoni
2850+
OPTIONAL MATCH (anoni)-[:Related]->(o:FBdv)
2851+
WITH o WHERE o IS NOT NULL
2852+
RETURN apoc.text.join(collect(DISTINCT coalesce(o.label, o.short_form)), '; ') AS stages
2853+
}}
2854+
CALL {{
2855+
WITH anat
2856+
OPTIONAL MATCH (anat)<-[:has_source|SUBCLASSOF|INSTANCEOF*]-(i:Individual)<-[:depicts]-(channel:Individual)-[irw:in_register_with]->(template:Individual)-[:depicts]->(template_anat:Individual)
2857+
OPTIONAL MATCH (channel)-[:is_specified_output_of]->(technique:Class)
2858+
WITH i, template, template_anat, channel, technique, irw
2859+
LIMIT 1
2860+
RETURN i, template, template_anat, channel, technique, irw
2861+
}}
28242862
RETURN
28252863
anat.short_form AS id,
28262864
apoc.text.format("[%s](%s)", [anat.label, anat.short_form]) AS name,
28272865
apoc.text.join(coalesce(anat.uniqueFacets, []), '|') AS tags,
2828-
pubs
2829-
ORDER BY anat.label
2866+
pubs,
2867+
stages,
2868+
REPLACE(apoc.text.format("[%s](%s)", [COALESCE(template_anat.symbol[0], template_anat.label), template_anat.short_form]), '[null](null)', '') AS template,
2869+
coalesce(technique.label, '') AS technique,
2870+
REPLACE(apoc.text.format("[![%s](%s '%s')](%s)", [COALESCE(i.symbol[0], coalesce(i.label, 'image')) + " aligned to " + COALESCE(template_anat.symbol[0], template_anat.label), REPLACE(COALESCE(irw.thumbnail[0], ''), 'thumbnailT.png', 'thumbnail.png'), COALESCE(i.symbol[0], coalesce(i.label, 'image')) + " aligned to " + COALESCE(template_anat.symbol[0], template_anat.label), template_anat.short_form + "," + coalesce(i.short_form, anat.short_form)]), "[![null]( 'null')](null)", "") AS thumbnail
28302871
"""
28312872

2832-
if limit != -1:
2833-
main_query += f" LIMIT {limit}"
2834-
28352873
results = vc.nc.commit_list([main_query])
28362874
df = pd.DataFrame.from_records(get_dict_cursor()(results))
28372875

28382876
if not df.empty:
2839-
df = encode_markdown_links(df, ['name'])
2877+
df = encode_markdown_links(df, ['name', 'template', 'thumbnail'])
28402878

28412879
if return_dataframe:
28422880
return df
28432881

28442882
return {
28452883
"headers": {
2846-
"id": {"title": "ID", "type": "selection_id", "order": -1},
2847-
"name": {"title": "Anatomy", "type": "markdown", "order": 0},
2848-
"tags": {"title": "Tags", "type": "tags", "order": 1},
2849-
"pubs": {"title": "Publications", "type": "metadata", "order": 2},
2884+
"id": {"title": "ID", "type": "selection_id", "order": -1},
2885+
"name": {"title": "Anatomy", "type": "markdown", "order": 0},
2886+
"pubs": {"title": "Publications", "type": "metadata", "order": 1},
2887+
"tags": {"title": "Tags", "type": "tags", "order": 2},
2888+
"stages": {"title": "Stage", "type": "text", "order": 3},
2889+
"template": {"title": "Template", "type": "markdown", "order": 4},
2890+
"technique": {"title": "Imaging Technique", "type": "text", "order": 5},
2891+
"thumbnail": {"title": "Thumbnail", "type": "markdown", "order": 9},
28502892
},
28512893
"rows": [
2852-
{key: row[key] for key in ["id", "name", "tags", "pubs"]}
2894+
{k: row[k] for k in ["id", "name", "pubs", "tags", "stages", "template", "technique", "thumbnail"]}
28532895
for row in safe_to_dict(df, sort_by_id=False)
28542896
],
28552897
"count": total_count,

0 commit comments

Comments
 (0)