|
| 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