Skip to content

Commit 9dd9fa3

Browse files
committed
_owlery_query_to_results: handle instance-shape channel_image
ImagesNeurons et al call _owlery_query_to_results with solr_field=anat_image_query and query_instances=True. That SOLR field uses a flat top-level channel_image: [{image, channel, imaging_technique}] with no anatomy_channel_image wrapper, while class queries (anat_query) nest as anatomy_channel_image: [{anatomy, channel_image: {...}}] The v1.12.0 image-extractor only handled the class shape, so every instance-routed owlery query returned empty Template_Space / Imaging_Technique / Images on the live API even though the SOLR doc carries all three. Confirmed by curl'ing solr/vfb_json/select?q=id:VFB_fw075305&fl=anat_image_query — top-level keys are ['term','query','version','channel_image','parents'], channel_image[0] keys are ['image','channel','imaging_technique']. Normalise both shapes to a single `channel_image` dict before extracting template / technique / thumbnail. For the instance case fall back to the row's own label_text for the thumbnail alt (no outer anatomy field).
1 parent dad1aba commit 9dd9fa3

1 file changed

Lines changed: 42 additions & 13 deletions

File tree

src/vfbquery/vfb_queries.py

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3896,25 +3896,55 @@ def _owlery_query_to_results(owl_query_string: str, short_form: str, return_data
38963896
# Extract tags from unique_facets
38973897
tags = '|'.join(term_core.get('unique_facets', []))
38983898

3899-
# Extract thumbnail + template + technique from
3900-
# anatomy_channel_image[0].channel_image. v2 prod's SOLR-backed
3901-
# processor surfaces all three as separate columns
3902-
# (Template_Space, Imaging_Technique, Images); v2-dev was
3903-
# previously only getting Images because we only built the
3904-
# thumbnail markdown.
3899+
# Extract thumbnail + template + technique from SOLR.
3900+
#
3901+
# Class queries (anat_query) wrap each image as
3902+
# anatomy_channel_image: [{anatomy, channel_image: {...}}]
3903+
# Individual / instance queries (anat_image_query, used by
3904+
# query_instances=True callers like ImagesNeurons and epFrag)
3905+
# use a flat top-level
3906+
# channel_image: [{image, channel, imaging_technique}]
3907+
# with no `anatomy_channel_image` wrapper at all.
3908+
#
3909+
# Normalise both shapes to a single `channel_image` dict
3910+
# before extracting template / technique / thumbnail so the
3911+
# instance queries (ImagesNeurons et al) actually get
3912+
# populated Template_Space / Imaging_Technique / Images
3913+
# columns instead of empty strings.
39053914
thumbnail = ''
39063915
template = ''
39073916
technique = ''
3917+
3918+
channel_image = {}
3919+
anatomy_label_for_alt = label_text
39083920
anatomy_images = field_data.get('anatomy_channel_image', [])
39093921
if anatomy_images and len(anatomy_images) > 0:
3910-
first_img = anatomy_images[0]
3911-
channel_image = first_img.get('channel_image', {})
3912-
image_info = channel_image.get('image', {})
3922+
# Class shape: pick the first wrapper, dig into channel_image.
3923+
first_wrapper = anatomy_images[0]
3924+
channel_image = first_wrapper.get('channel_image', {}) or {}
3925+
wrapper_anat = first_wrapper.get('anatomy') or {}
3926+
if isinstance(wrapper_anat, dict) and wrapper_anat.get('label'):
3927+
anatomy_label_for_alt = wrapper_anat['label']
3928+
else:
3929+
# Instance shape: top-level channel_image is already the
3930+
# list of channel-image entries.
3931+
ci_list = field_data.get('channel_image', [])
3932+
if ci_list and len(ci_list) > 0:
3933+
first_entry = ci_list[0] or {}
3934+
# Some emitters nest again as {channel_image: {...}},
3935+
# most are flat — handle both defensively.
3936+
if isinstance(first_entry, dict) and 'image' in first_entry:
3937+
channel_image = first_entry
3938+
else:
3939+
channel_image = first_entry.get('channel_image', {}) or {}
3940+
3941+
if channel_image:
3942+
image_info = channel_image.get('image', {}) or {}
39133943

39143944
# Template — `[label](short_form)` markdown so the
39153945
# VFBqueryJsonProcessor's stripMarkdownLink renders a
39163946
# clickable link in the V2 Template_Space column.
3917-
template_anatomy = image_info.get('template_anatomy', {})
3947+
template_anatomy = image_info.get('template_anatomy', {}) or {}
39183948
template_short_form = template_anatomy.get('short_form', '') if template_anatomy else ''
39193949
template_label_raw = ''
39203950
if template_anatomy:
@@ -3926,7 +3956,7 @@ def _owlery_query_to_results(owl_query_string: str, short_form: str, return_data
39263956
# Imaging technique — plain label (V2 Imaging_Technique
39273957
# column renders as text; matches how
39283958
# get_similar_morphology_part_of et al. emit it).
3929-
technique_info = channel_image.get('imaging_technique', {})
3959+
technique_info = channel_image.get('imaging_technique', {}) or {}
39303960
if technique_info:
39313961
technique_label_raw = technique_info.get('label', '')
39323962
technique = unquote(technique_label_raw) if technique_label_raw else ''
@@ -3939,8 +3969,7 @@ def _owlery_query_to_results(owl_query_string: str, short_form: str, return_data
39393969

39403970
# Format thumbnail with proper markdown link (matching Neo4j behavior)
39413971
if template_label:
3942-
anatomy_label = first_img.get('anatomy', {}).get('label', label_text)
3943-
anatomy_label = unquote(anatomy_label)
3972+
anatomy_label = unquote(anatomy_label_for_alt)
39443973
alt_text = f"{anatomy_label} aligned to {template_label}"
39453974
thumbnail = f"[![{alt_text}]({thumbnail_url} '{alt_text}')]({class_short_form})"
39463975

0 commit comments

Comments
 (0)