@@ -201,23 +201,14 @@ def __init__(self,
201201
202202 self .additional_obs_sets = {
203203 "version" : "0.1.3" ,
204- "tree" : [
205- {
206- "name" : "SpatialQuery Results" ,
207- "children" : []
208- }
209- ]
204+ "tree" : []
210205 }
211206
212207 self .obs_set_color = [
213208 {
214209 "color" : [255 , 255 , 255 ],
215210 "path" : ["Cell Type" ],
216211 },
217- {
218- "color" : [255 , 255 , 255 ],
219- "path" : ["SpatialQuery Results" ],
220- }
221212 ]
222213
223214 self .ct_to_color = dict ()
@@ -244,61 +235,65 @@ def get_matching_cell_ids(self, cell_type, cell_i):
244235 return matches
245236
246237 def fp_tree_to_obs_sets_tree (self , fp_tree , sq_id ):
247- additional_obs_sets = {
248- "version" : "0.1.3" ,
249- "tree" : [
250- {
251- "name" : f"SpatialQuery Results { sq_id } " ,
252- "children" : [
238+ sq_motif_name = f"SpatialQuery Results { sq_id } — By Motif"
239+ sq_ct_name = f"SpatialQuery Results { sq_id } — By Cell Type"
253240
254- ]
255- }
256- ]
257- }
258-
259- obs_set_color = []
260- n_motifs = len (fp_tree )
241+ # Pass 1: collect per-motif data and accumulate deduped cell ids per cell type
242+ motif_rows = []
243+ ct_to_cell_ids = {}
261244
262- for motif_i , ( row_i , row ) in enumerate ( fp_tree .iterrows () ):
245+ for _ , row in fp_tree .iterrows ():
263246 try :
264247 motif = row ["itemsets" ]
265248 except KeyError :
266249 motif = row ["motifs" ]
267- # anchor-type queries: use neighbor_id for motif cells, grid/rand use cell_id
268- if "neighbor_id" in row .index :
269- cell_i = row ["neighbor_id" ]
270- else :
271- cell_i = row ["cell_id" ]
272-
273- motif_name = str (list (motif ))
250+ cell_i = row ["neighbor_id" ] if "neighbor_id" in row .index else row ["cell_id" ]
251+ motif_rows .append ((motif , cell_i ))
252+ for cell_type in motif :
253+ matching = {i for i in cell_i if self .cell_id_to_cell_type .get (self .cell_i_to_cell_id .get (i )) == cell_type }
254+ ct_to_cell_ids .setdefault (cell_type , set ()).update (matching )
274255
275- additional_obs_sets ["tree" ][0 ]["children" ].append ({
276- "name" : motif_name ,
277- "children" : [
278- {
279- "name" : cell_type ,
280- "set" : self .get_matching_cell_ids (cell_type , cell_i )
281- }
282- for cell_type in motif
283- ]
284- })
256+ n_motifs = len (motif_rows )
257+ obs_set_color = []
285258
286- # Assign each motif a unique color by evenly spacing hues around the color wheel
259+ # Node 1: "By Motif" — each motif is a leaf, colored by motif index
260+ by_motif_children = []
261+ for motif_i , (motif , cell_i ) in enumerate (motif_rows ):
262+ motif_name = str (list (motif ))
287263 hue = motif_i / max (n_motifs , 1 )
288264 r , g , b = colorsys .hls_to_rgb (hue , 0.55 , 0.75 )
289265 motif_color = [int (r * 255 ), int (g * 255 ), int (b * 255 )]
290- obs_set_color .append ({
291- "color" : motif_color ,
292- "path" : [additional_obs_sets ["tree" ][0 ]["name" ], motif_name ]
266+ motif_cell_types = set (motif )
267+ motif_cell_ids = list ({
268+ i for i in cell_i
269+ if self .cell_id_to_cell_type .get (self .cell_i_to_cell_id .get (i )) in motif_cell_types
270+ })
271+ by_motif_children .append ({
272+ "name" : motif_name ,
273+ "set" : [[self .cell_i_to_cell_id [i ], None ] for i in motif_cell_ids if i in self .cell_i_to_cell_id ]
274+ })
275+ obs_set_color .append ({"color" : motif_color , "path" : [sq_motif_name , motif_name ]})
276+
277+ # Node 2: "By Cell Type" — each cell type is a leaf (union across motifs), colored by cell type
278+ by_ct_children = []
279+ for cell_type , cell_ids_set in ct_to_cell_ids .items ():
280+ by_ct_children .append ({
281+ "name" : cell_type ,
282+ "set" : [[self .cell_i_to_cell_id [i ], None ] for i in sorted (cell_ids_set ) if i in self .cell_i_to_cell_id ]
293283 })
284+ obs_set_color .append ({"color" : self .ct_to_color [cell_type ], "path" : [sq_ct_name , cell_type ]})
285+
286+ additional_obs_sets = {
287+ "version" : "0.1.3" ,
288+ "tree" : [
289+ {"name" : sq_motif_name , "children" : by_motif_children },
290+ {"name" : sq_ct_name , "children" : by_ct_children },
291+ ]
292+ }
293+
294+ obs_set_color .insert (0 , {"color" : [255 , 255 , 255 ], "path" : [sq_motif_name ]})
295+ obs_set_color .insert (0 , {"color" : [255 , 255 , 255 ], "path" : [sq_ct_name ]})
294296
295- for cell_type in motif :
296- color = self .ct_to_color [cell_type ]
297- path = [additional_obs_sets ["tree" ][0 ]["name" ], motif_name , cell_type ]
298- obs_set_color .append ({
299- "color" : color ,
300- "path" : path
301- })
302297 return (additional_obs_sets , obs_set_color )
303298
304299 def run_sq (self , prev_config ):
@@ -358,24 +353,25 @@ def run_sq(self, prev_config):
358353 # Perform query
359354 (new_additional_obs_sets , new_obs_set_color ) = self .fp_tree_to_obs_sets_tree (fp_tree , query_uuid )
360355
361- new_sq_node = new_additional_obs_sets ["tree" ][0 ]
362- sq_idx = next ((i for i , n in enumerate (additional_obs_sets ["tree" ]) if n ["name" ].startswith ("SpatialQuery Results" )), None )
363- if sq_idx is not None :
364- additional_obs_sets ["tree" ][sq_idx ] = new_sq_node
365- else :
366- additional_obs_sets ["tree" ].append (new_sq_node )
356+ # Replace any existing SpatialQuery Results nodes (both By Motif and By Cell Type)
357+ existing_tree = additional_obs_sets ["tree" ]
358+ existing_tree = [n for n in existing_tree if not n ["name" ].startswith ("SpatialQuery Results" )]
359+ existing_tree += new_additional_obs_sets ["tree" ]
360+ additional_obs_sets ["tree" ] = existing_tree
367361 prev_config ["coordinationSpace" ]["additionalObsSets" ]["A" ] = additional_obs_sets
368362
369363 obs_set_color += new_obs_set_color
370364 prev_config ["coordinationSpace" ]["obsSetColor" ]["A" ] = obs_set_color
371365
372- motif_to_select = new_additional_obs_sets ["tree" ][0 ]["children" ][0 ]["name" ]
373- new_obs_set_selection = [[new_additional_obs_sets ["tree" ][0 ]["name" ], motif_to_select , node ["name" ]] for node in new_additional_obs_sets ["tree" ][0 ]["children" ][0 ]["children" ]]
366+ # Default selection: all motif leaf nodes under "By Motif" node
367+ sq_motif_node = new_additional_obs_sets ["tree" ][0 ] # "...By Motif"
368+ new_obs_set_selection = [
369+ [sq_motif_node ["name" ], motif ["name" ]]
370+ for motif in sq_motif_node ["children" ]
371+ ]
374372 prev_config ["coordinationSpace" ]["obsSetSelection" ]["A" ] = new_obs_set_selection
375373
376- # TODO: need to fix bug that prevents this from working
377- # Reference: https://github.com/vitessce/vitessce/blob/774328ab5c4436576dd2e8e4fff0758d6c6cce89/packages/view-types/obs-sets-manager/src/ObsSetsManagerSubscriber.js#L104
378- prev_config ["coordinationSpace" ]["obsSetExpansion" ]["A" ] = [path [:- 1 ] for path in new_obs_set_selection ]
374+ prev_config ["coordinationSpace" ]["obsSetExpansion" ]["A" ] = [[sq_motif_node ["name" ]]]
379375
380376 return {** prev_config , "uid" : f"with_query_{ query_uuid } " }
381377
0 commit comments