@@ -182,16 +182,19 @@ def post(self):
182182class GaiaPublicationFiguresByGene (Resource ):
183183 @gaia .param ("identifier" , _in = "path" , default = "ABI3" )
184184 def get (self , identifier = "" ):
185- # Escape input and validate
185+
186+ # Escape input
186187 identifier = escape (identifier )
188+
189+ # Is it valid
187190 if not BARUtils .is_gaia_alias (identifier ):
188191 return BARUtils .error_exit ("Invalid identifier" ), 400
189192
190- # --- Resolve identifier -> gene id(s) -> the gene's full alias set ---
193+ # Resolve to gene ids: try alias first, then locus / ncbi id
191194 rows = db .session .execute (db .select (Aliases .genes_id ).filter (Aliases .alias == identifier )).fetchall ()
192195 gene_ids = [r .genes_id for r in rows ]
193196
194- if not gene_ids : # not an alias - try locus / ncbi geneid
197+ if not gene_ids :
195198 rows = db .session .execute (
196199 db .select (Genes .id ).filter (or_ (Genes .locus == identifier , Genes .geneid == identifier ))
197200 ).fetchall ()
@@ -200,22 +203,20 @@ def get(self, identifier=""):
200203 if not gene_ids :
201204 return BARUtils .error_exit ("Nothing found" ), 404
202205
206+ # Get the gene's full alias set
203207 aliases = [
204208 r .alias .lower ()
205209 for r in db .session .execute (db .select (Aliases .alias ).filter (Aliases .genes_id .in_ (gene_ids ))).fetchall ()
206210 ]
207211
208- # --- Build the OCR word-boundary match (V1-resolved policy, no gene:true filter) ---
209- # Aliases >= 4 chars: whole-token regex match (catches "abi3/vp1", rejects "gabi390_r").
210- # Aliases <= 3 chars: exact IN(...) - a boundary match on a short token over-matches.
212+ # Match OCR words: word-boundary regex for long aliases, exact match for short ones
211213 long_aliases = sorted ({re .escape (a ) for a in aliases if len (a ) >= 4 })
212214 short_aliases = sorted ({a for a in aliases if len (a ) < 4 })
213215
214- # Gene resolved but no usable aliases -> empty payload (frontend falls back to PubMed feed).
216+ # No usable aliases, nothing to match on
215217 if not long_aliases and not short_aliases :
216218 return BARUtils .success_exit ({"figures" : {}, "allImageWords" : {}})
217219
218- # --- Step 1: match on the scalar OCR word (pure Core; no array explosion) ---
219220 word_expr = func .lower (func .json_unquote (func .json_extract (FigureModels .data , "$.word" )))
220221 match_conds = []
221222 if long_aliases :
@@ -228,26 +229,31 @@ def get(self, identifier=""):
228229 if not matched_rows :
229230 return BARUtils .success_exit ({"figures" : {}, "allImageWords" : {}})
230231
231- # --- Step 2: unpack matched rows' image[] in Python -> name -> [bbox] (data: dict or str) ---
232+ # Collect each matched image and its boxes (keep the image even if a box is missing)
232233 bbox_by_name = {}
233234 for row in matched_rows :
234235 d = row .data if isinstance (row .data , dict ) else json .loads (row .data )
235236 for img in d .get ("image" , []):
236237 name = (img .get ("imageName" ) or "" ).lstrip ("/" )
237- if name :
238- bbox_by_name .setdefault (name , []).append (img .get ("bbox" ))
238+ if not name :
239+ continue
240+ bbox_list = bbox_by_name .setdefault (name , [])
241+ bbox = img .get ("bbox" )
242+ if bbox is not None :
243+ bbox_list .append (bbox )
239244
240245 stripped_names = list (bbox_by_name .keys ())
241246 if not stripped_names :
242247 return BARUtils .success_exit ({"figures" : {}, "allImageWords" : {}})
243248
244- # --- Step 3: figures + publication metadata (pure Core; bbox now comes from Python) ---
245- # Bare-name guard: exclude any img_name mapping to >1 publication (un-attributable detections).
249+ # Drop image names used by more than one publication, we can't attribute those
246250 collision = (
247251 db .select (Figures .img_name )
248252 .group_by (Figures .img_name )
249253 .having (func .count (func .distinct (Figures .publication_figures_id )) > 1 )
250254 )
255+
256+ # Pull the figures and their publication info, skip null urls, newest pubmed first
251257 core_stmt = (
252258 db .select (
253259 PubIds .pmc ,
@@ -269,21 +275,16 @@ def get(self, identifier=""):
269275 )
270276 fig_rows = db .session .execute (core_stmt ).fetchall ()
271277
272- # Valid gene, but no OCR-matched figures -> empty payload (NOT 404).
273278 if not fig_rows :
274279 return BARUtils .success_exit ({"figures" : {}, "allImageWords" : {}})
275280
276- # --- Step 4: group by PMC; dedupe figures by img_name; attach the full accumulated bbox list ---
277- # Dedup guard: the collision guard only excludes names spanning >1 publication, so two figures
278- # rows with the same name under one pf_id could still duplicate. Surviving img_name -> pmc is
279- # 1:1, so a seen-set on img_name is sufficient; bbox_by_name[name] already holds the full list.
281+ # Group figures by PMC, one entry per image name
280282 figures_by_pmc , pmc_to_pf , pf_ids , seen_names = {}, {}, set (), set ()
281283 for r in fig_rows :
282- pmc = r .pmc
283284 pf_ids .add (r .pf_id )
284- pmc_to_pf [pmc ] = r .pf_id
285- if pmc not in figures_by_pmc :
286- figures_by_pmc [pmc ] = {
285+ pmc_to_pf [r . pmc ] = r .pf_id
286+ if r . pmc not in figures_by_pmc :
287+ figures_by_pmc [r . pmc ] = {
287288 "title" : r .title ,
288289 "abstract" : r .abstract ,
289290 "authors" : [],
@@ -293,7 +294,7 @@ def get(self, identifier=""):
293294 if r .img_name in seen_names :
294295 continue
295296 seen_names .add (r .img_name )
296- figures_by_pmc [pmc ]["figures" ].append (
297+ figures_by_pmc [r . pmc ]["figures" ].append (
297298 {
298299 "img_name" : r .img_name ,
299300 "img_url" : r .img_url ,
@@ -302,7 +303,7 @@ def get(self, identifier=""):
302303 }
303304 )
304305
305- # --- Step 5: authors per publication (one bulk query; db.select infers the gaia bind) ---
306+ # Attach authors to each publication
306307 authors_by_pf = {}
307308 for r in db .session .execute (
308309 db .select (AuthorList .publication_figures_id , AuthorList .author ).filter (
@@ -313,13 +314,11 @@ def get(self, identifier=""):
313314 for pmc , pf_id in pmc_to_pf .items ():
314315 figures_by_pmc [pmc ]["authors" ] = authors_by_pf .get (pf_id , [])
315316
316- # --- Step 6: allImageWords (pure Core JSON_OVERLAPS pre-filter + Python unpack) ---
317- # gene:true words detected on the displayed figures. JSON_OVERLAPS pre-filters rows that share
318- # any displayed image; the Python pass keeps only the displayed names. All gaia-bound -> bind inferred.
317+ # allImageWords: gene words detected on the shown figures, for the gene-name filter
319318 displayed_names = list ({r .img_name for r in fig_rows })
320319 all_image_words = {}
321320 if displayed_names :
322- displayed_slashed = json .dumps (["/" + n for n in displayed_names ]) # re-add slash to match stored
321+ displayed_slashed = json .dumps (["/" + n for n in displayed_names ]) # stored names keep a leading /
323322 words_rows = db .session .execute (
324323 db .select (FigureModels .data )
325324 .where (func .json_unquote (func .json_extract (FigureModels .data , "$.gene" )) == "true" )
@@ -337,6 +336,8 @@ def get(self, identifier=""):
337336 for img in d .get ("image" , []):
338337 name = (img .get ("imageName" ) or "" ).lstrip ("/" )
339338 if name in displayed_set :
340- all_image_words .setdefault (word , {})[name ] = img .get ("bbox" )
339+ bbox = img .get ("bbox" )
340+ all_image_words .setdefault (word , {})[name ] = bbox if bbox is not None else []
341341
342+ # Return final data
342343 return BARUtils .success_exit ({"figures" : figures_by_pmc , "allImageWords" : all_image_words })
0 commit comments