Skip to content

Commit 3a96130

Browse files
committed
seperate colors to by motif and by type in two trees
1 parent 9d817be commit 3a96130

1 file changed

Lines changed: 59 additions & 63 deletions

File tree

src/vitessce/widget_plugins/spatial_query.py

Lines changed: 59 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)