Skip to content

Commit 7027e74

Browse files
Add LaVision plotting function
1 parent 478bc0f commit 7027e74

File tree

1 file changed

+398
-0
lines changed

1 file changed

+398
-0
lines changed

scripts/figures/plot_lavision.py

Lines changed: 398 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,398 @@
1+
import os
2+
from typing import List
3+
from concurrent import futures
4+
5+
import imageio.v3 as imageio
6+
import napari
7+
import numpy as np
8+
import pandas as pd
9+
import zarr
10+
11+
from elf.parallel import isin
12+
from flamingo_tools.s3_utils import get_s3_path, BUCKET_NAME, SERVICE_ENDPOINT
13+
from magicgui import magicgui
14+
from nifty.tools import blocking
15+
from scipy.ndimage import binary_dilation
16+
from tqdm import tqdm
17+
18+
IHC_SETTINGS = {
19+
"LaVision-M02": {
20+
"components": [1, 3, 6, 8, 22, 24],
21+
"min_size": 500
22+
},
23+
"LaVision-M03": {
24+
"components": [1, 2],
25+
"min_size": 500
26+
},
27+
"LaVision-M04": {
28+
"components": [1],
29+
"min_size": 500
30+
},
31+
"LaVision-Mar05": {
32+
"components": [1, 2, 3, 5, 7, 9, 11, 13], # 100
33+
"min_size": 250
34+
},
35+
"LaVision-OTOF23R": {
36+
"components": [4, 7, 18],
37+
"min_size": 250
38+
},
39+
"LaVision-OTOF25R": {
40+
"components": [1, 23, 40, 51, 86, 112],
41+
"min_size": 250
42+
},
43+
# "LaVision-OTOF36R": {
44+
# "components": [1],
45+
# "min_size": 250
46+
# },
47+
}
48+
49+
50+
def _mask_in_parallel(seg, mask):
51+
blocks = blocking([0, 0, 0], mask.shape, [32, 128, 128])
52+
53+
def apply_mask(block_id):
54+
block = blocks.getBlock(block_id)
55+
bb = tuple(slice(begin, end) for begin, end in zip(block.begin, block.end))
56+
mask_bb = mask[bb]
57+
if mask_bb.sum() == 0:
58+
return
59+
seg_bb = seg[bb]
60+
if seg_bb.sum() == 0:
61+
return
62+
seg_bb[mask_bb] = 0
63+
seg[bb] = seg_bb
64+
65+
n_blocks = blocks.numberOfBlocks
66+
with futures.ThreadPoolExecutor(8) as tp:
67+
list(tqdm(tp.map(apply_mask, range(n_blocks)), total=n_blocks))
68+
69+
return seg
70+
71+
72+
def download_seg(ds, seg_channel, output_folder, apply_filter=True, scale=1):
73+
if ds == "LaVision-Mar05" and "SGN" in seg_channel:
74+
input_key = f"s{scale + 1}"
75+
else:
76+
input_key = f"s{scale}"
77+
78+
if ds == "LaVision-Mar05" and scale == 1:
79+
output_path = os.path.join(output_folder, f"{seg_channel}_s1.tif")
80+
else:
81+
output_path = os.path.join(output_folder, f"{seg_channel}.tif")
82+
83+
if os.path.exists(output_path):
84+
return
85+
86+
internal_path = os.path.join(ds, "images", "ome-zarr", f"{seg_channel}.ome.zarr")
87+
s3_store, fs = get_s3_path(internal_path, bucket_name=BUCKET_NAME, service_endpoint=SERVICE_ENDPOINT)
88+
89+
table_path = os.path.join(BUCKET_NAME, ds, "tables", seg_channel, "default.tsv")
90+
with fs.open(table_path, "r") as f:
91+
table = pd.read_csv(f, sep="\t")
92+
93+
with zarr.open(s3_store, mode="r") as f:
94+
data = f[input_key][:]
95+
96+
if apply_filter:
97+
valid_ids = table[table.component_labels == 1].label_id
98+
print(seg_channel, ":", len(valid_ids))
99+
mask = np.zeros_like(data, dtype="bool")
100+
mask = ~isin(data, valid_ids, out=mask, verbose=True, block_shape=(32, 256, 256))
101+
data = _mask_in_parallel(data, mask)
102+
v = napari.Viewer()
103+
v.add_labels(data)
104+
napari.run()
105+
else:
106+
table_out = os.path.join(output_folder, f"{seg_channel}.tsv")
107+
table.to_csv(table_out, sep="\t", index=False)
108+
109+
imageio.imwrite(output_path, data, compression="zlib")
110+
111+
112+
def download_dataset(
113+
ds,
114+
channels=["PV", "MYO"],
115+
seg_channels=["SGN_LOWRES-v5c", "IHC_LOWRES-v3"],
116+
apply_filters=[True, False],
117+
scale=1,
118+
):
119+
print(ds)
120+
121+
output_folder = os.path.join("data", ds)
122+
os.makedirs(output_folder, exist_ok=True)
123+
input_key = f"s{scale}"
124+
125+
for channel in channels:
126+
if ds == "LaVision-Mar05" and scale == 1:
127+
output_path = os.path.join(output_folder, f"{channel}_s1.tif")
128+
else:
129+
output_path = os.path.join(output_folder, f"{channel}.tif")
130+
if os.path.exists(output_path):
131+
continue
132+
133+
internal_path = os.path.join(ds, "images", "ome-zarr", f"{channel}.ome.zarr")
134+
s3_store, fs = get_s3_path(internal_path, bucket_name=BUCKET_NAME, service_endpoint=SERVICE_ENDPOINT)
135+
with zarr.open(s3_store, mode="r") as f:
136+
data = f[input_key][:]
137+
imageio.imwrite(output_path, data, compression="zlib")
138+
139+
for seg_channel, apply_filter in zip(seg_channels, apply_filters):
140+
print(seg_channel, scale)
141+
download_seg(ds, seg_channel, output_folder, apply_filter=apply_filter, scale=scale)
142+
143+
144+
def download_data():
145+
# datasets = ["LaVision-M02", "LaVision-M03"]
146+
# for ds in datasets:
147+
# download_dataset(ds)
148+
149+
# datasets = ["LaVision-Mar05"]
150+
# for ds in datasets:
151+
# for scale in (1, 2):
152+
# download_dataset(
153+
# ds, scale=scale,
154+
# seg_channels=["SGN_LOWRES-v5", "IHC_LOWRES-v3", "IHC_LOWRES-v4"],
155+
# apply_filters=[True, False, False],
156+
# )
157+
158+
channels = ["CR", "rbOtof"]
159+
datasets = ["LaVision-OTOF23R", "LaVision-OTOF25R"]
160+
for ds in datasets:
161+
download_dataset(ds, channels=channels, seg_channels=["IHC_LOWRES-v3"], apply_filters=[True], scale=1)
162+
163+
164+
def _filter_ihcs(ihcs, table, components, min_size):
165+
keep_ids = table[
166+
table.component_labels.isin(components) & (table.n_pixels > min_size)
167+
].label_id
168+
print("Ihc components:", sorted(components))
169+
print("Min-size:", min_size)
170+
print("Number of IHCs:", len(keep_ids))
171+
ihc_filtered = ihcs.copy()
172+
ihc_filtered[~np.isin(ihcs, keep_ids)] = 0
173+
return ihc_filtered
174+
175+
176+
def check_ihcs_ds(ds, channel="MYO", seg_name="IHC_LOWRES-v3"):
177+
from nifty.tools import takeDict
178+
179+
myo = imageio.imread(f"data/{ds}/{channel}.tif")
180+
ihcs = imageio.imread(f"data/{ds}/{seg_name}.tif")
181+
182+
table = pd.read_csv(f"data/{ds}/{seg_name}.tsv", sep="\t")
183+
relabel_dict = {0: 0}
184+
relabel_dict.update({int(k): int(v) for k, v in zip(table.label_id.values, table.component_labels.values)})
185+
ihc_components = takeDict(relabel_dict, ihcs)
186+
187+
@magicgui()
188+
def filter_component(v: napari.Viewer, component_list: List[int] = [1], min_size=500):
189+
ihc_filtered = _filter_ihcs(ihcs, table, component_list, min_size)
190+
v.layers["ihc_filtered"].data = ihc_filtered
191+
192+
if ds in IHC_SETTINGS:
193+
settings = IHC_SETTINGS[ds]
194+
components, min_size = settings["components"], settings["min_size"]
195+
ihc_filtered = _filter_ihcs(ihcs, table, components, min_size)
196+
else:
197+
ihc_filtered = np.zeros_like(ihcs)
198+
199+
v = napari.Viewer()
200+
v.add_image(myo)
201+
v.add_labels(ihcs, name="ihcs")
202+
v.add_labels(ihc_components)
203+
v.add_labels(ihc_filtered)
204+
v.title = ds
205+
206+
v.window.add_dock_widget(filter_component)
207+
208+
napari.run()
209+
210+
211+
def check_ihcs():
212+
# datasets = ["LaVision-M02", "LaVision-M03", "LaVision-M04"]
213+
# datasets = ["LaVision-Mar05"]
214+
# for ds in datasets:
215+
# check_ihcs_ds(ds, channel="MYO", seg_name="IHC_LOWRES-v3")
216+
217+
# datasets = ["LaVision-OTOF23R", "LaVision-OTOF25R", "LaVision-OTOF36R"]
218+
datasets = ["LaVision-OTOF23R"]
219+
for ds in datasets:
220+
check_ihcs_ds(ds, channel="CR")
221+
222+
223+
def create_rendering_m03(export_for_rendering):
224+
ds = "LaVision-M03"
225+
out_folder = f"data/{ds}/for_rendering"
226+
227+
pv = imageio.imread(f"data/{ds}/PV.tif").astype("float32")
228+
myo = imageio.imread(f"data/{ds}/MYO.tif").astype("float32")
229+
sgn = imageio.imread(f"data/{ds}/SGN_LOWRES-v5c.tif")
230+
231+
ihc_path = f"data/{ds}/IHC_LOWRES-v3-filtered.tif"
232+
if os.path.exists(ihc_path):
233+
ihc = imageio.imread(ihc_path)
234+
else:
235+
ihc = imageio.imread(f"data/{ds}/IHC_LOWRES-v3.tif")
236+
table = pd.read_csv(f"data/{ds}/IHC_LOWRES-v3.tsv", sep="\t")
237+
settings = IHC_SETTINGS[ds]
238+
components, min_size = settings["components"], settings["min_size"]
239+
ihc = _filter_ihcs(ihc, table, components, min_size)
240+
imageio.imwrite(ihc_path, ihc, compression="zlib")
241+
242+
pv_mask = binary_dilation(sgn > 0, iterations=3)
243+
myo_mask = binary_dilation(ihc > 0, iterations=2)
244+
if export_for_rendering:
245+
os.makedirs(out_folder, exist_ok=True)
246+
imageio.imwrite(os.path.join(out_folder, "PV.tif"), pv, compression="zlib")
247+
imageio.imwrite(os.path.join(out_folder, "MYO.tif"), myo, compression="zlib")
248+
imageio.imwrite(os.path.join(out_folder, "SGN.tif"), sgn.astype("float32"), compression="zlib")
249+
imageio.imwrite(os.path.join(out_folder, "IHC.tif"), ihc.astype("float32"), compression="zlib")
250+
251+
factor = 10
252+
253+
myo = myo / myo.max()
254+
myo[myo_mask] *= factor
255+
256+
pv = pv / pv.max()
257+
pv[pv_mask] *= factor
258+
259+
if export_for_rendering:
260+
imageio.imwrite(os.path.join(out_folder, "PV_filtered.tif"), pv, compression="zlib")
261+
imageio.imwrite(os.path.join(out_folder, "MYO_filtered.tif"), myo, compression="zlib")
262+
return
263+
264+
scale = (6, 3.9, 3.9)
265+
266+
v = napari.Viewer()
267+
v.add_image(pv, colormap="red", scale=scale, blending="additive")
268+
v.add_image(myo, colormap="cyan", scale=scale, blending="additive")
269+
v.add_labels(sgn, scale=scale)
270+
v.add_labels(ihc, scale=scale)
271+
272+
v.scale_bar.visible = True
273+
v.scale_bar.unit = "µm" # unit to display
274+
275+
napari.run()
276+
277+
278+
def create_rendering_mar05(apply_mask=True):
279+
pv = imageio.imread("data/LaVision-Mar05/PV.tif")
280+
sgn = imageio.imread("data/LaVision-Mar05/SGN_LOWRES-v5.tif")
281+
282+
myo = imageio.imread("data/LaVision-Mar05/MYO.tif")
283+
ihc = imageio.imread("data/LaVision-Mar05/IHC_LOWRES-combined.tif")
284+
# ihc = imageio.imread("data/LaVision-Mar05/IHC_LOWRES-combined2.tif")
285+
n_ihcs = len(np.unique(ihc)) - 1
286+
print(n_ihcs)
287+
288+
factor = 5
289+
290+
myo = myo / myo.max()
291+
if apply_mask:
292+
myo_mask = imageio.imread("data/ihc_mask.tif").astype("bool")
293+
myo[myo_mask] *= factor
294+
295+
pv = pv / pv.max()
296+
if apply_mask:
297+
pv_mask = binary_dilation(sgn > 0, iterations=4)
298+
pv[pv_mask] *= factor
299+
300+
scale = (12, 7.8, 7.8)
301+
302+
v = napari.Viewer()
303+
v.add_image(pv, colormap="red", scale=scale, blending="additive")
304+
v.add_image(myo, colormap="cyan", scale=scale, blending="additive")
305+
v.add_labels(sgn, scale=scale)
306+
v.add_labels(ihc, scale=scale)
307+
308+
v.scale_bar.visible = True
309+
v.scale_bar.unit = "µm" # unit to display
310+
311+
napari.run()
312+
313+
314+
def create_rendering_otof(ds="LaVision-OTOF25R", apply_mask=True, for_rendering=False):
315+
cr = imageio.imread(f"data/{ds}/CR.tif")
316+
rb_otof = imageio.imread(f"data/{ds}/rbOtof.tif")
317+
318+
ihc_path = f"data/{ds}/IHC_LOWRES-v3_filtered.tif"
319+
if os.path.exists(ihc_path):
320+
ihc = imageio.imread(ihc_path)
321+
else:
322+
ihc = imageio.imread(f"data/{ds}/IHC_LOWRES-v3.tif")
323+
324+
out_folder = f"data/{ds}/for_rendering"
325+
os.makedirs(out_folder, exist_ok=True)
326+
if for_rendering:
327+
imageio.imwrite(os.path.join(out_folder, "CR.tif"), cr.astype("float32"), compression="zlib")
328+
imageio.imwrite(os.path.join(out_folder, "rbOtof.tif"), rb_otof.astype("float32"), compression="zlib")
329+
imageio.imwrite(os.path.join(out_folder, "IHC-v3.tif"), ihc.astype("float32"), compression="zlib")
330+
331+
factor = 5
332+
333+
ihc_mask = binary_dilation(ihc > 0, iterations=3)
334+
cr = cr / cr.max()
335+
if apply_mask:
336+
cr[ihc_mask] *= factor
337+
338+
rb_otof = rb_otof / rb_otof.max()
339+
if apply_mask:
340+
rb_otof[ihc_mask] *= factor
341+
342+
if for_rendering and apply_mask:
343+
imageio.imwrite(os.path.join(out_folder, "CR_filtered.tif").astype("float32"), cr, compression="zlib")
344+
imageio.imwrite(os.path.join(out_folder, "rbOtof_filtered.tif").astype("float32"), rb_otof, compression="zlib")
345+
return
346+
347+
scale = (6, 3.8, 3.8)
348+
v = napari.Viewer()
349+
v.add_image(cr, colormap="red", scale=scale, blending="additive")
350+
v.add_image(rb_otof, colormap="cyan", scale=scale, blending="additive")
351+
v.add_labels(ihc, scale=scale)
352+
353+
v.scale_bar.visible = True
354+
v.scale_bar.unit = "µm" # unit to display
355+
356+
napari.run()
357+
358+
359+
# LaVision-M02
360+
# SGN_detect-v5b : 9009
361+
# IHC_LOWRES-v3 : 583
362+
363+
# LaVision-M03
364+
# SGN_detect-v5b : 9687
365+
366+
# LaVision-OTOF23R
367+
# IHC_LOWRES-v3: 623
368+
369+
# LaVision-OTOF25R
370+
# IHC_LOWRES-v3: 551
371+
def main():
372+
# download_data()
373+
# check_ihcs()
374+
375+
# create_rendering_m03(export_for_rendering=True)
376+
# create_rendering_mar05()
377+
378+
create_rendering_otof(ds="LaVision-OTOF23R", for_rendering=True)
379+
380+
381+
# Merges and missing IDS:
382+
383+
# LaVision-M02: Looks Good
384+
# LaVision-M03: Looks Good
385+
386+
# LaVision-M04:
387+
# Merged / Missing: 428, 650, 1128, 2121, 4653, 121, 122, 123, 735, 745, 725, 925
388+
# 4285, 4286, 4282, 4412, 4413, 4405, 4402, 4464, 4513, 4511, 4512, 4510
389+
390+
# LaVision-OTOF 23R: Looks good
391+
392+
# LaVision-OTOF 25R:
393+
# Merged: 4822, 4836, 4835, 4834, 4833
394+
# Missing: 4838, 4837, 4832
395+
# Merged: 4813, 4803
396+
# Missing: 4801, 4802, 4804, 4805, 4799, 4800, 4805
397+
if __name__ == "__main__":
398+
main()

0 commit comments

Comments
 (0)