๋ฒ์ญ: ๋ฐ์ฃผํ
Tip
์ด ํํ ๋ฆฌ์ผ์ ์ต๋ํ ํ์ฉํ๋ ค๋ฉด, ์ด Colab ๋ฒ์ ์ ์ฌ์ฉํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค. ์ด๋ฅผ ํตํด ์๋์ ์๋ฃ๋ฅผ ์คํํด ๋ณผ ์ ์์ต๋๋ค.
๋ณธ ํํ ๋ฆฌ์ผ์์๋ TIAToolbox๋ฅผ ์ฌ์ฉํ PyTorch ๋ชจ๋ธ์ ํตํด ์ ์ฒด ์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง๋ค(Whole Slide Images, WSIs)์ ๋ถ๋ฅํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด๊ฒ ์ต๋๋ค. WSI๋ ์์ ์ด๋ ์๊ฒ์ ํตํด ์ฑ์ทจ๋ ์ธ๊ฐ ์กฐ์ง ์ํ์ ์ด๋ฏธ์ง์ด๋ฉฐ, ์ด๋ฌํ ์ด๋ฏธ์ง๋ ์ ๋ฌธ ์ค์บ๋๋ฅผ ์ด์ฉํด ์ค์บ ๋ฉ๋๋ค. ์ด ๋ฐ์ดํฐ๋ ๋ณ๋ฆฌํ์์ ์ ์ฐ ๋ณ๋ฆฌํ์๋ค์ด ์ข ์ ์ฑ์ฅ์ ๋ํ ์ดํด๋ฅผ ๋์ด๊ณ ํ์ ์น๋ฃ๋ฅผ ๊ฐ์ ํ๊ธฐ ์ํด ์๊ณผ ๊ฐ์ ์ง๋ณ์ ๋ฏธ์์ ์์ค์์ ์ฐ๊ตฌ ํ๋๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
WSIs๋ฅผ ์ฒ๋ฆฌํ๋ ๊ฒ์ด ์ด๋ ค์ด ์ด์ ๋ ์์ฒญ๋ ํฌ๊ธฐ ๋๋ฌธ์ ๋๋ค. ์์ปจ๋, ์ผ๋ฐ์ ์ธ ์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง๊ฐ 100,000x100,000 ํฝ์ ์ ๋์ ํฌ๊ธฐ๋ฅผ ๊ฐ์ง๋ฉฐ, ๊ฐ ํฝ์ ์ ์ฌ๋ผ์ด๋ ์์์ ์ฝ 0.25x0.25 ๋ง์ดํฌ๋ก๋ฏธํฐ์ ํด๋นํฉ๋๋ค. ์ด ๋๋ฌธ์ ์ด๋ฏธ์ง๋ฅผ ๋ก๋ํ๊ณ ์ฒ๋ฆฌํ๋ ๋ฐ ์ด๋ ค์์ ์ผ๊ธฐํ๋ฉฐ, ํ ์ฐ๊ตฌ์์ ์๋ฐฑ ๊ฐ๋ ์์ฒ ๊ฐ์ WSIs๊ฐ ํฌํจ๋๋ ๊ฒฝ์ฐ๋ ๋งํ ๊ฒ๋ ์์ต๋๋ค ๋ ํฐ ์ฐ๊ตฌ๊ฐ ๋ ๋์ ๊ฒฐ๊ณผ๋ฅผ ์ ๊ณตํฉ๋๋ค!
์ ํต์ ์ธ ์ด๋ฏธ์ง ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ์ WSIs ์ฒ๋ฆฌ์ ์ ํฉํ์ง ์์ผ๋ฏ๋ก ๋ ๋์ ๋๊ตฌ๊ฐ ํ์ํฉ๋๋ค. ์ด๋, TIAToolbox ๊ฐ ๋์์ด ๋ ์ ์๋๋ฐ, ์ด๋ ์กฐ์ง ์ฌ๋ผ์ด๋(tissue slides)๋ฅผ ๋น ๋ฅด๊ณ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋ ์ ์ฉํ ๋๊ตฌ๋ค์ ์ ๊ณตํฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก WSIs๋ ์๊ฐํ์ ์ต์ ํ๋ ๋ค์ํ ๋ฐฐ์จ์์ ๋์ผํ ์ด๋ฏธ์ง์ ์ฌ๋ฌ ๋ณต์ฌ๋ณธ์ด ํผ๋ผ๋ฏธ๋ ๊ตฌ์กฐ๋ก ์ ์ฅ๋ฉ๋๋ค. ํผ๋ผ๋ฏธ๋์ ๋ ๋ฒจ 0(๋๋ ๊ฐ์ฅ ์๋ ๋จ๊ณ)์๋ ๊ฐ์ฅ ๋์ ๋ฐฐ์จ ๋๋ ์ค ์์ค์ ์ด๋ฏธ์ง๋ฅผ ํฌํจํ๋ฉฐ, ํผ๋ผ๋ฏธ๋์ ์์ ๋จ๊ณ๋ก ๊ฐ์๋ก ๊ธฐ๋ณธ ์ด๋ฏธ์ง์ ์ ํด์๋ ์ฌ๋ณธ์ด ์์ต๋๋ค. ํ๋จ์ ํผ๋ผ๋ฏธ๋์ ๊ตฌ์กฐ๊ฐ ๊ทธ๋ ค์ ธ ์์ต๋๋ค.
WSI ํผ๋ผ๋ฏธ๋ stack
(source)
TIAToolbox๋ฅผ ์ฌ์ฉํ๋ฉด ์กฐ์ง
๋ถ๋ฅ ์ ๊ฐ์ ์ผ๋ฐ์ ์ธ
ํ์ ๋ถ์ ์์
์ ์๋ํํ ์ ์์ต๋๋ค. ๋ณธ ํํ ๋ฆฌ์ผ์์๋ ๋ค์์ ์ํํ๋
๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค: 1. TIAToolbox๋ฅผ ์ฌ์ฉํ์ฌ WSI(Whole Slide Image)๋ฅผ
์ด๋ฏธ์ง๋ฅผ ๋ก๋ํ๋ ๋ฐฉ๋ฒ 2. ์๋ก ๋ค๋ฅธ Pytorch ๋ชจ๋ธ์ ์ฌ์ฉํ์ฌ ์ฌ๋ผ์ด๋๋ฅผ
ํจ์น ๋ ๋ฒจ(patch-level)์์ ๋ถ๋ฅํ๋ ๋ฐฉ๋ฒ. ๋ณธ ํํ ๋ฆฌ์ผ์์๋ TorchVision์
ResNet18
๋ชจ๋ธ๊ณผ ์ปค์คํ
(custom) HistoEncoder
๋ชจ๋ธ์ ์ฌ์ฉํ๋ ์์ ๋ฅผ ์ ๊ณตํฉ๋๋ค.
์์ํด๋ณด์!
๋ณธ ํํ ๋ฆฌ์ผ์์ ์ ๊ณตํ๋ ์์ ๋ฅผ ์คํํ๋ ค๋ฉด, ๋ค์๊ณผ ๊ฐ์ ํจํค์ง(packages)๊ฐ ํ์ํฉ๋๋ค.
- OpenJpeg
- OpenSlide
- Pixman
- TIAToolbox
- HistoEncoder (for a custom model example)
์ด ํจํค์ง๋ค์ ์ค์นํ๊ธฐ ์ํด ํฐ๋ฏธ๋(terminal)์ ์๋์ ๋ช ๋ น์ด๋ฅผ ์คํํด์ฃผ์ธ์:
- apt-get -y -qq install libopenjp2-7-dev libopenjp2-tools openslide-tools libpixman-1-dev
- pip install -q 'tiatoolbox<1.5' histoencoder && echo "Installation is done."
๋ํ, MacOS์์๋ apt-get
๋์ ์ brew install openjpeg openslide
๋ช
๋ น์ด๋ฅผ ์คํํ์ฌ ํ์ ํจํค์ง๋ค์ ์ค์นํ ์ ์์ต๋๋ค.
์ค์น์ ๋ํ ์ถ๊ฐ ์ ๋ณด๋ ์ฌ๊ธฐ
๋ฅผ ์ฐธ์กฐํ์ธ์.
"""Jupyter ๋
ธํธ๋ถ์ ์คํํ๋ ๋ฐ ํ์ํ ๋ชจ๋ ๋ถ๋ฌ์ค๊ธฐ."""
from __future__ import annotations
# ๋ก๊น
(logging) ์ค์ ํ๊ธฐ
import logging
import warnings
if logging.getLogger().hasHandlers():
logging.getLogger().handlers.clear()
warnings.filterwarnings("ignore", message=".*The 'nopython' keyword.*")
# ๋ฐ์ดํฐ์ ํ์ผ ๋ค์ด๋ก๋ํ๊ธฐ
import shutil
from pathlib import Path
from zipfile import ZipFile
# ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๋ฐ ์๊ฐํ
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from matplotlib import cm
import PIL
import contextlib
import io
from sklearn.metrics import accuracy_score, confusion_matrix
# WSI ๋ก๋ฉ ๋ฐ ์ฒ๋ฆฌ๋ฅผ ์ํ TIAToolbox
from tiatoolbox import logger
from tiatoolbox.models.architecture import vanilla
from tiatoolbox.models.engine.patch_predictor import (
IOPatchPredictorConfig,
PatchPredictor,
)
from tiatoolbox.utils.misc import download_data, grab_files_from_dir
from tiatoolbox.utils.visualization import overlay_prediction_mask
from tiatoolbox.wsicore.wsireader import WSIReader
# Torch-๊ด๋ จ
import torch
from torchvision import transforms
# ๊ทธ๋ํ ์ค์
mpl.rcParams["figure.dpi"] = 160 # for high resolution figure in notebook
mpl.rcParams["figure.facecolor"] = "white" # To make sure text is visible in dark mode
# ๋ง์ฝ GPU๋ฅผ ์ฌ์ฉํ์ง ์๋๋ค๋ฉด, ON_GPU๋ฅผ False๋ก ๋ณ๊ฒฝํ์ธ์.
ON_GPU = True
# ์ฅํฉํ ์ฝ๋ ๋ธ๋ก์ ์ฝ์ ์ถ๋ ฅ์ ์ต์ ํ๋ ํจ์
def suppress_console_output():
return contextlib.redirect_stderr(io.StringIO())
์ ์ ํ ์ ๋ฆฌ๋ฅผ ๋ณด์ฅํ๊ธฐ ์ํด(์์ปจ๋, ๋น์ ์ ์ข
๋ฃ), ์ด๋ฒ ์คํ์์
๋ค์ด๋ก๋๋๊ฑฐ๋ ์์ฑ๋ ๋ชจ๋ ํ์ผ์ global_save_dir
์ด๋ผ๋
ํ๋์ ๋๋ ํ ๋ฆฌ์ ์ ์ฅ๋๋ฉฐ, ์ด ๋๋ ํ ๋ฆฌ๋ โ./tmp/โ๋ก ์ค์ ๋ฉ๋๋ค.
์ ์ง๋ณด์๋ฅผ ์ฝ๊ฒ ํ๊ธฐ ์ํด ๋๋ ํ ๋ฆฌ ์ด๋ฆ์ ์ด ํ ๊ณณ์์๋ง ์ค์ ๋๋ฏ๋ก,
ํ์ํ๋ฉด ๊ฐํธํ๊ฒ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค.
warnings.filterwarnings("ignore")
global_save_dir = Path("./tmp/")
def rmdir(dir_path: str | Path) -> None:
"""๋๋ ํ ๋ฆฌ๋ฅผ ์ง์ฐ๊ธฐ ์ํ ๋์ฐ๋ฏธ ํจ์"""
if Path(dir_path).is_dir():
shutil.rmtree(dir_path)
logger.info("Removing directory %s", dir_path)
rmdir(global_save_dir) # ์ด์ ์คํ์์ ๋๋ ํ ๋ฆฌ๊ฐ ์๋ ๊ฒฝ์ฐ ์ญ์
global_save_dir.mkdir()
logger.info("Creating new directory %s", global_save_dir)
์ํ ๋ฐ์ดํฐ๋ก๋ ์ ์ฒด ์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง(whole-slide image)๋ฅผ ์ฌ์ฉํ๊ณ Kather 100k ๋ฐ์ดํฐ์ ์ ๊ฒ์ฆ(validation) ํ์ ์ง๋จ(subset)์์ ์ถ์ถํ ํจ์น๋ค์ ์ฌ์ฉํ ๊ฒ์ ๋๋ค.
wsi_path = global_save_dir / "sample_wsi.svs"
patches_path = global_save_dir / "kather100k-validation-sample.zip"
weights_path = global_save_dir / "resnet18-kather100k.pth"
logger.info("Download has started. Please wait...")
# ์ ์ฒด ์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง(whole-slide image) ์ํ์ ๋ค์ด๋ก๋ ํ๊ณ ์์ถ์ ํด์ ํ๊ธฐ
download_data(
"https://tiatoolbox.dcs.warwick.ac.uk/sample_wsis/TCGA-3L-AA1B-01Z-00-DX1.8923A151-A690-40B7-9E5A-FCBEDFC2394F.svs",
wsi_path,
)
# Kather 100K ๋ฐ์ดํฐ์
์ ํ๋ จํ๊ธฐ ์ํด ์ฌ์ฉ๋ ๊ฒ์ฆ ์ธํธ(validation set) ์ํ์ ๋ค์ด๋ก๋ํ๊ณ ์์ถ์ ํด์ ํ๊ธฐ
download_data(
"https://tiatoolbox.dcs.warwick.ac.uk/datasets/kather100k-validation-sample.zip",
patches_path,
)
with ZipFile(patches_path, "r") as zipfile:
zipfile.extractall(path=global_save_dir)
# ResNet18 ์ํคํ
์ฒ๋ก WSI(์ ์ฒด ์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง) ๋ถ๋ฅ๋ฅผ ์ํด ์ฌ์ ํ์ต๋ ๋ชจ๋ธ ๊ฐ์ค์น๋ฅผ ๋ค์ด๋ก๋ํ๊ธฐ
download_data(
"https://tiatoolbox.dcs.warwick.ac.uk/models/pc/resnet18-kather100k.pth",
weights_path,
)
logger.info("Download is complete.")
ํจ์น ๋ชฉ๋ก๊ณผ ํด๋น๋๋ ๋ผ๋ฒจ ๋ชฉ๋ก์ ์์ฑํฉ๋๋ค.
์๋ฅผ ๋ค์ด, label_list
์ ์ฒซ ๋ฒ์งธ ๋ผ๋ฒจ์
patch_list
์ ์ฒซ ๋ฒ์งธ ์ด๋ฏธ์ง ํจ์น์ ํด๋์ค๋ฅผ ๋ํ๋
๋๋ค.
# ํจ์น ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ณ ํจ์น ๋ชฉ๋ก๊ณผ ํด๋น ๋ผ๋ฒจ ๋ชฉ๋ก์ ์์ฑ
dataset_path = global_save_dir / "kather100k-validation-sample"
# ๋ฐ์ดํฐ์
๊ฒฝ๋ก ์ค์
image_ext = ".tif" # ๊ฐ ์ด๋ฏธ์ง์ ํ์ผ ํ์ฅ์
# ๋ผ๋ฒจ ID์ ํด๋์ค ์ด๋ฆ ๊ฐ์ ๋งคํ
label_dict = {
"BACK": 0, # Background (empty glass region) # ์ด ๋ถ๋ถ์ ๋ฐ์์ ์์ธํ ์ค๋ช
"NORM": 1, # Normal colon mucosa
"DEB": 2, # Debris
"TUM": 3, # Colorectal adenocarcinoma epithelium
"ADI": 4, # Adipose
"MUC": 5, # Mucus
"MUS": 6, # Smooth muscle
"STR": 7, # Cancer-associated stroma
"LYM": 8, # Lymphocytes
}
class_names = list(label_dict.keys())
class_labels = list(label_dict.values())
# ํจ์น ๋ชฉ๋ก ์์ฑ ๋ฐ ํ์ผ ์ด๋ฆ์์ ๋ผ๋ฒจ ์ถ์ถํ๊ธฐ
patch_list = []
label_list = []
for class_name, label in label_dict.items():
dataset_class_path = dataset_path / class_name
patch_list_single_class = grab_files_from_dir(
dataset_class_path,
file_types="*" + image_ext,
)
patch_list.extend(patch_list_single_class)
label_list.extend([label] * len(patch_list_single_class))
# ๋ฐ์ดํฐ์
ํต๊ณ ํ๊ธฐ
plt.bar(class_names, [label_list.count(label) for label in class_labels])
plt.xlabel("Patch types")
plt.ylabel("Number of patches")
# ํด๋์ค๋ณ ์์ ๊ฐ์ ์ง๊ณ
for class_name, label in label_dict.items():
logger.info(
"Class ID: %d -- Class Name: %s -- Number of images: %d",
label,
class_name,
label_list.count(label),
)
# ์ ์ฒด ๋ฐ์ดํฐ์
ํต๊ณ
logger.info("Total number of patches: %d", (len(patch_list)))
.. image-sg:: ../_static/img/tiatoolbox_tutorial/tiatoolbox_tutorial_001.png :alt: tiatoolbox tutorial :srcset: ../_static/img/tiatoolbox_tutorial/tiatoolbox_tutorial_001.png :class: sphx-glr-single-img
.. rst-class:: sphx-glr-script-out .. code-block:: none |2023-11-14|13:15:59.299| [INFO] Class ID: 0 -- Class Name: BACK -- Number of images: 211 |2023-11-14|13:15:59.299| [INFO] Class ID: 1 -- Class Name: NORM -- Number of images: 176 |2023-11-14|13:15:59.299| [INFO] Class ID: 2 -- Class Name: DEB -- Number of images: 230 |2023-11-14|13:15:59.299| [INFO] Class ID: 3 -- Class Name: TUM -- Number of images: 286 |2023-11-14|13:15:59.299| [INFO] Class ID: 4 -- Class Name: ADI -- Number of images: 208 |2023-11-14|13:15:59.299| [INFO] Class ID: 5 -- Class Name: MUC -- Number of images: 178 |2023-11-14|13:15:59.299| [INFO] Class ID: 6 -- Class Name: MUS -- Number of images: 270 |2023-11-14|13:15:59.299| [INFO] Class ID: 7 -- Class Name: STR -- Number of images: 209 |2023-11-14|13:15:59.299| [INFO] Class ID: 8 -- Class Name: LYM -- Number of images: 232 |2023-11-14|13:15:59.299| [INFO] Total number of patches: 2000
์ด ํจ์น ๋ฐ์ดํฐ์ ์์ ๋ณผ ์ ์๋ฏ์ด, 0๋ถํฐ 8๊น์ง์ ID๋ฅผ ๊ฐ์ง 9๊ฐ์ ํด๋์ค์ ๋ผ๋ฒจ์ด ์์ผ๋ฉฐ, ๊ฐ ํด๋์ค๋ ํด๋น ํจ์น์์ ์ฃผ๋ก ๋ํ๋๋ ์กฐ์ง ์ ํ์ ์ค๋ช ํฉ๋๋ค:
- BACK โถ ๋ฐฐ๊ฒฝ(Background)(๋น์ด ์๋ ์์ญ)
- LYM โถ ๋ฆผํ๊ตฌ(Lymphocytes)
- NORM โถ ์ ์ ๋์ฅ ์ ๋ง(Normal colon mucosa)
- DEB โถ ์กฐ์ง ํํธ(Debris)
- MUS โถ ํํ๊ทผ(Smooth muscle)
- STR โถ ์ ๊ด๋ จ ๊ธฐ์ง(Cancer-associated stroma)
- ADI โถ ์ง๋ฐฉ ์กฐ์ง(Adipose)
- MUC โถ ์ ์ก(Mucus)
- TUM โถ ๋์ฅ์ ์ ์(Colorectal adenocarcinoma epithelium)
๋จผ์ patch
๋ชจ๋๋ฅผ ์ฌ์ฉํ์ฌ ๋์งํธ ์ฌ๋ผ์ด๋ ๋ด์
๊ฐ ํจ์น์ ๋ํ ์์ธก์ ๊ตฌํ๋ ๋ฐฉ๋ฒ์ ์์ฐํ ํ, wsi
๋ชจ๋๋ฅผ ์ฌ์ฉํ์ฌ
ํฐ(large) ์ฌ๋ผ์ด๋์ ๋ํด ์์ธก์ ์ํํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค.
PatchPredictor ํด๋์ค๋ PyTorch๋ก ์์ฑ๋ CNN ๊ธฐ๋ฐ ๋ถ๋ฅ๊ธฐ๋ฅผ ์คํํฉ๋๋ค
๋ชจ๋ธ
์tiatoolbox.models.abc.ModelABC
`(๋ฌธ์)- <https://tia-toolbox.readthedocs.io/en/latest/_autosummary/tiatoolbox.models.models_abc.ModelABC.html>`__
ํด๋์ค ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ฅด๋ ๋ชจ๋ PyTorch๋ก ํ๋ จ๋ ๋ชจ๋ธ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ด์ ๋ํ ์์ธํ ๋ด์ฉ์ ๊ณ ๊ธ ๋ชจ๋ธ ๊ธฐ์ ์ ๊ดํ ์์ ๋
ธํธ๋ถ(notebook).
์ ์ฐธ์กฐํ์ญ์์ค. ์ปค์คํ
๋ชจ๋ธ์ ๋ก๋ํ๋ ค๋ฉด,
preproc_func(img)
์ ๊ฐ์ ์ ์ฒ๋ฆฌ ํจ์๋ฅผ ์์ฑํด์ผ ํ๋ฉฐ, ์ด ํจ์๋ ์ ๋ ฅ tensor๊ฐ ๋ก๋๋ ๋คํธ์ํฌ์ ์ ํฉํ ํ์์ผ๋ก ๋์ด ์๋์ง ํ์ธํด์ค๋๋ค.
- ๋ํ,
์ฌ์ ํ์ต๋ ๋ชจ๋ธ(pretrained_model)
์ ๋ฌธ์์ด ์ธ์๋ก ์ ๋ฌํ ์ ์์ต๋๋ค. ์ด๋ ์์ธก์ ์ํํ CNN ๋ชจ๋ธ์ ์ง์ ํ๋ฉฐ, ํด๋น ๋ชจ๋ธ์ ์ฌ๊ธฐ ๋์ด๋ ๋ชจ๋ธ ์ค ํ๋์ด์ด์ผ ํฉ๋๋ค. ๋ช ๋ น์ด๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:predictor = PatchPredictor(pretrained_model='resnet18-kather100k', pretrained_weights=weights_path, batch_size=32)
. pretrained_weights
:์ฌ์ ํ์ต๋ ๋ชจ๋ธ(pretrained_model)
์ ์ฌ์ฉํ ๋, ํด๋น ๋ชจ๋ธ์ ์ฌ์ ํ์ต๋ ๊ฐ์ค์น๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ค์ด๋ก๋ ๋ฉ๋๋ค. ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณต๋๋ ๊ฐ์ค์น๋ฅผ ๋ฎ์ด์ฐ๊ณ ์์ ๋ง์ ๊ฐ์ค์น๋ฅผ ์ฌ์ฉํ๋ ค๋ฉดpretrained_weight
์ธ์๋ฅผ ํตํด ๊ฐ์ค์น๋ฅผ ์ง์ ํ ์ ์์ต๋๋ค.batch_size
: ๋ชจ๋ธ์ ํ ๋ฒ์ ์ ๋ ฅ๋๋ ์ด๋ฏธ์ง์ ๊ฐ์๋ฅผ ์ง์ ํฉ๋๋ค. ์ด ๊ฐ์ด ํด์๋ก ๋ ๋ง์ (GPU)๋ฉ๋ชจ๋ฆฌ ์ฉ๋์ด ํ์ํฉ๋๋ค.
# TIAToolbox์์ ์ฌ์ ํ์ต๋ PyTorch ๋ชจ๋ธ ๊ฐ์ ธ์ค๊ธฐ
predictor = PatchPredictor(pretrained_model='resnet18-kather100k', batch_size=32)
# ์ฌ์ฉ์๋ ์๋ ์คํฌ๋ฆฝํธ๋ฅผ ํตํด ์ํ๋ PyTorch ๋ชจ๋ธ ์ํคํ
์ฒ๋ฅผ ๋ถ๋ฌ์ฌ ์ ์์ต๋๋ค.
model = vanilla.CNNModel(backbone="resnet18", num_classes=9) # torchvision.models.resnet18์์ ๋ชจ๋ธ ๋ถ๋ฌ์ค๊ธฐ
model.load_state_dict(torch.load(weights_path, map_location="cpu"), strict=True)
def preproc_func(img):
img = PIL.Image.fromarray(img)
img = transforms.ToTensor()(img)
return img.permute(1, 2, 0)
model.preproc_func = preproc_func
predictor = PatchPredictor(model=model, batch_size=32)
์์ธก๊ธฐ(predictor) ๊ฐ์ฒด๋ฅผ ์์ฑํ ํ patch
๋ชจ๋๋ฅผ ์ฌ์ฉํ์ฌ predict
๋ฉ์๋๋ฅผ ํธ์ถํฉ๋๋ค.
๊ทธ๋ฐ ๋ค์, ๋ถ๋ฅ ์ ํ๋์ ์ค์ฐจ ํ๋ ฌ(confusion matrix)์ ๊ณ์ฐํฉ๋๋ค.
with suppress_console_output():
output = predictor.predict(imgs=patch_list, mode="patch", on_gpu=ON_GPU)
acc = accuracy_score(label_list, output["predictions"])
logger.info("Classification accuracy: %f", acc)
# ํจ์น ๋ถ๋ฅ ๊ฒฐ๊ณผ๋ฅผ ์ํ ์ค์ฐจ ํ๋ ฌ(confusion_matrix) ์์ฑ ๋ฐ ์๊ฐํ
conf = confusion_matrix(label_list, output["predictions"], normalize="true")
df_cm = pd.DataFrame(conf, index=class_names, columns=class_names)
df_cm
.. rst-class:: sphx-glr-script-out .. code-block:: none |2023-11-14|13:16:03.215| [INFO] Classification accuracy: 0.993000
BACK | NORM | DEB | TUM | ADI | MUC | MUS | STR | LYM | |
---|---|---|---|---|---|---|---|---|---|
BACK | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.00000 |
NORM | 0.000000 | 0.988636 | 0.000000 | 0.011364 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.00000 |
DEB | 0.000000 | 0.000000 | 0.991304 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.008696 | 0.00000 |
TUM | 0.000000 | 0.000000 | 0.000000 | 0.996503 | 0.000000 | 0.003497 | 0.000000 | 0.000000 | 0.00000 |
ADI | 0.004808 | 0.000000 | 0.000000 | 0.000000 | 0.990385 | 0.000000 | 0.004808 | 0.000000 | 0.00000 |
MUC | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.988764 | 0.000000 | 0.011236 | 0.00000 |
MUS | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.996296 | 0.003704 | 0.00000 |
STR | 0.000000 | 0.000000 | 0.004785 | 0.000000 | 0.000000 | 0.004785 | 0.004785 | 0.985646 | 0.00000 |
LYM | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.004310 | 0.99569 |
IOPatchPredictorConfig
ํด๋์ค๋ฅผ ์๊ฐํฉ๋๋ค. ์ด ํด๋์ค๋ ๋ชจ๋ธ ์์ธก ์์ง์
์ํ ์ด๋ฏธ์ง ์ฝ๊ธฐ ๋ฐ ์์ธก ๊ฒฐ๊ณผ ์ฐ๊ธฐ ๊ตฌ์ฑ ์ค์ ์ ์ง์ ํฉ๋๋ค.
์ด ์ค์ ์ ๋ถ๋ฅ๊ธฐ(classifier)์๊ฒ WSI ํผ๋ผ๋ฏธ๋์ ์ด๋ ๋ ๋ฒจ์ ์ฝ๊ณ ,
๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋ฉฐ, ์ถ๋ ฅ์ ์์ฑํด์ผ ํ๋์ง ์๋ ค์ฃผ๋ ๋ฐ
ํ์์ ์
๋๋ค.
IOPatchPredictorConfig
์ ๋งค๊ฐ๋ณ์๋ ๋ค์๊ณผ ๊ฐ์ด ์ ์๋ฉ๋๋ค.
input_resolutions
: ์ ๋ ฅ์ ํด์๋๋ฅผ ์ง์ ํ๋ ๋์ ๋๋ฆฌ ํํ์ ๋ฆฌ์คํธ๋ก, ๊ฐ ์ ๋ ฅ์ ํด์๋๋ฅผ ์ค์ ํฉ๋๋ค. ๋ฆฌ์คํธ์ ์์๋model.forward()
์ ์์์ ๊ฐ์์ผํฉ๋๋ค. ๋ง์ฝ ๋ชจ๋ธ์ด ํ๋์ ์ ๋ ฅ๋ง ๋ฐ๋ ๊ฒฝ์ฐ, ํ๋์ ๋์ ๋๋ฆฌ๋ก'units'
์'resolution'
์ ์ง์ ํ๋ฉด ๋ฉ๋๋ค. TIAToolbox๋ ํ๋ ์ด์์ ์ ๋ ฅ์ ๋ฐ๋ ๋ชจ๋ธ์ ์ง์ํ๋ฏ๋ก, ์ฌ๋ฌ ์ ๋ ฅ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์๋ ๋ฌธ์ ์์ด ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ ๋(units)๊ณผ ํด์๋(resolution)์ ๋ํ ์์ธํ ๋ด์ฉ์ TIAToolbox ๋ฌธ์ ๋ฅผ ์ฐธ๊ณ ํ์ญ์์ค.patch_input_shape
: ๊ฐ์ฅ ํฐ ์ ๋ ฅ์ ํฌ๊ธฐ๋ฅผ (๋์ด, ๋๋น) ํ์์ผ๋ก ์ง์ ํฉ๋๋ค.stride_shape
: ์ฐ์๋ ๋ ํจ์น ์ฌ์ด์ ๊ฐ๊ฒฉ(๋จ๊ณ) ํฌ๊ธฐ๋ฅผ ์ง์ ํ๋ฉฐ, ํจ์น ์ถ์ถ ๊ณผ์ ์์ ์ฌ์ฉ๋ฉ๋๋ค. ์ฌ์ฉ์๊ฐstride_shape
๋ฅผpatch_input_shape
์ ๋์ผํ๊ฒ ์ค์ ํ๋ฉด, ํจ์น๋ค์ด ์ค์ฒฉ์์ด ์ถ์ถ๋๊ณ , ์ฒ๋ฆฌ๋ฉ๋๋ค.
wsi_ioconfig = IOPatchPredictorConfig(
input_resolutions=[{"units": "mpp", "resolution": 0.5}],
patch_input_shape=[224, 224],
stride_shape=[224, 224],
)
predict
๋ฉ์๋๋ ์
๋ ฅ ํจ์น์ CNN์ ์ ์ฉํ์ฌ ๊ฒฐ๊ณผ๋ฅผ ์ป์ต๋๋ค.
๋ค์์ ํด๋น ๋ฉ์๋์ ์ธ์์ ์ค๋ช
์
๋๋ค:
mode
: ์ฒ๋ฆฌํ ์ ๋ ฅ์ ์ ํ์ ์ง์ ํฉ๋๋ค. ์์ฉ ํ๋ก๊ทธ๋จ์ ๋ฐ๋ผpatch
,tile
๋๋wsi
์ค์์ ์ ํํฉ๋๋ค.imgs
: ์ ๋ ฅ ํ์ผ๋ค์ ๊ฒฝ๋ก ๋ฆฌ์คํธ๋ก, ์ ๋ ฅ ํ์ผ(input tiles) ๋๋ WSIs ๊ฒฝ๋ก์ ๋ชฉ๋ก์ด์ด์ผ ํฉ๋๋ค.return_probabilities
: ์ ๋ ฅ ํจ์น์ ์์ธก๋ ๋ผ๋ฒจ๊ณผ ํจ๊ป ํด๋์ค๋ณ ํ๋ฅ ์ ์ป์ผ๋ ค๋ฉด True ๋ก ์ค์ ํฉ๋๋ค.tile
๋๋wsi
๋ชจ๋์์ ์์ธก ๊ฒฐ๊ณผ๋ฅผ ๋ณํฉํ์ฌ ์์ธก ๋งต์ ์์ฑํ๋ ค๋ฉดreturn_probabilities=True
๋ก ์ค์ ํ ์ ์์ต๋๋ค.ioconfig
:IOPatchPredictorConfig
ํด๋์ค๋ฅผ ์ฌ์ฉํ์ฌ IO ๊ตฌ์ฑ ์ ๋ณด๋ฅผ ์ค์ ํฉ๋๋ค.resolution
๊ณผunit
(์๋์ ํ์๋์ง ์์): ์ถ์ถํ ํจ์น์ WSI ๋ ๋ฒจ ๋๋ ๋ง์ดํฌ๋ก ๋น ํฝ์ ํด์๋๋ฅผ ์ง์ ํฉ๋๋ค. ์ด๋ioconfig
๋์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ฌ๊ธฐ์ WSI ๋ ๋ฒจ์'baseline'
์ผ๋ก ์ง์ ํ๋ฉฐ, ์ด๋ ์ผ๋ฐ์ ์ผ๋ก ๋ ๋ฒจ 0์ ์ด ๊ฒฝ์ฐ ์ด๋ฏธ์ง๋ ํ๋์ ๋ ๋ฒจ๋ง ๊ฐ์ง๊ณ ์์ต๋๋ค. ๋ ์์ธํ ๋ด์ฉ์ ๋ฌธ์ ์์ ํ์ธํ ์ ์์ต๋๋ค.masks
:imgs
๋ฆฌ์คํธ์ ์๋ WSI์ ๋ง์คํฌ ๊ฒฝ๋ก ๋ฆฌ์คํธ์ ๋๋ค. ์ด ๋ง์คํฌ๋ ์๋ณธ WSI์์ ํจ์น๋ฅผ ์ถ์ถํ๊ณ ์ ํ๋ ์์ญ์ ์ง์ ํฉ๋๋ค. ํน์ WSI์ ๋ง์คํฌ๊ฐNone
์ผ๋ก ์ง์ ๋๋ฉด, ํด๋น WSI์ ๋ชจ๋ ํจ์น(๋ฐฐ๊ฒฝ ์์ญ ํฌํจ)์ ๋ํ ๋ผ๋ฒจ์ด ์์ธก๋ฉ๋๋ค. ์ด๋ ๋ถํ์ํ ๊ณ์ฐ์ ์ ๋ฐํ ์ ์์ต๋๋ค.merge_predictions
: ํจ์น ๋ถ๋ฅ ๊ฒฐ๊ณผ๋ฅผ 2D ๋งต์ผ๋ก ์์ฑํด์ผํ๋ ๊ฒฝ์ฐTrue
๋ก ์ค์ ํ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ํฐ WSI์ ๊ฒฝ์ฐ ๋ง์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ํ์ํ ์ ์์ต๋๋ค. ๋์์ ์ธ ํด๊ฒฐ์ฑ ์ผ๋ก๋merge_predictions=False
๋ก(๊ธฐ๋ณธ๊ฐ) ์ค์ ํ์ฌ, ์ถํ์merge_predictions
ํจ์๋ฅผ ์ฌ์ฉํด 2D ์์ธก ๋งต์ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
ํฐ WSI๋ฅผ ์ฌ์ฉํ๊ณ ์๊ธฐ ๋๋ฌธ์ ํจ์น ์ถ์ถ ๋ฐ ์์ธก ๊ณผ์ ์ ์๊ฐ์ด ๋ค์ ๊ฑธ๋ฆด ์ ์์ต๋๋ค.
(๋ง์ฝ Cuda๊ฐ ํ์ฑํ๋ GPU๋ฅผ ์ฌ์ฉํ ์ ์๋ค๋ฉด
ON_GPU=True
๋ก ์ค์ ํ์ฌ PyTorch์ Cuda๋ฅผ ํ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค).
with suppress_console_output():
wsi_output = predictor.predict(
imgs=[wsi_path],
masks=None,
mode="wsi",
merge_predictions=False,
ioconfig=wsi_ioconfig,
return_probabilities=True,
save_dir=global_save_dir / "wsi_predictions",
on_gpu=ON_GPU,
)
wsi_output
์ ์๊ฐํํ์ฌ ์์ธก ๋ชจ๋ธ์ด ์ ์ฒด ์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง(WSI)์์
์ด๋ป๊ฒ ์๋ํ๋์ง ํ์ธํ ์ ์์ต๋๋ค. ๋จผ์ ํจ์น ์์ธก ๊ฒฐ๊ณผ๋ฅผ ๋ณํฉํ ํ,
์ด๋ฅผ ์๋ณธ ์ด๋ฏธ์ง ์์ ์ค๋ฒ๋ ์ด๋ก ์๊ฐํํด์ผ ํฉ๋๋ค. ์ด์ ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก
merge_predictions
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ํจ์น ์์ธก์ ๋ณํฉํฉ๋๋ค.
์ด๋, 1.25x ๋งํผ ํ๋๋ ์์ธก ๋งต์ ์์ฑํ๊ธฐ ์ํด
resolution=1.25, units='power'
๋ก ๋งค๊ฐ๋ณ์๋ฅผ ์ค์ ํฉ๋๋ค.
๋ง์ฝ ๋ ๋์/๋ฎ์ ํด์๋ (๋ ํฐ/์์) ์์ธก ๋งต์ ์ํ๋ค๋ฉด,
์ด ๋งค๊ฐ๋ณ์๋ฅผ ์ ์ ํ ๋ณ๊ฒฝํด์ผ ํฉ๋๋ค.
์์ธก์ด ๋ณํฉ๋๋ฉด overlay_patch_prediction
ํจ์๋ฅผ ์ฌ์ฉํ์ฌ
์์ธก ๋งต์ WSI ์ธ๋ค์ผ์ ์ค๋ฒ๋ ์ดํฉ๋๋ค. ์ด๋ ์ฌ์ฉ๋ ํด์๋๋
์์ธก ๋ณํฉ์ ์ฌ์ฉ๋ ํด์๋์
์ผ์นํด์ผํฉ๋๋ค.
overview_resolution = (
4 # ํจ์น ์์ธก์ ๋ณํฉํ๊ณ ์๊ฐํํ๋ ํด์๋ ์ค์
)
# 'ํด์๋' ๋งค๊ฐ๋ณ์์ ๋จ์ ์ค์ . "power", "level", "mpp", ๋๋ "baseline" ์ค์์ ์ ํ ๊ฐ๋ฅ
overview_unit = "mpp"
wsi = WSIReader.open(wsi_path)
wsi_overview = wsi.slide_thumbnail(resolution=overview_resolution, units=overview_unit)
plt.figure(), plt.imshow(wsi_overview)
plt.axis("off")
.. image-sg:: ../_static/img/tiatoolbox_tutorial/tiatoolbox_tutorial_002.png :alt: tiatoolbox tutorial :srcset: ../_static/img/tiatoolbox_tutorial/tiatoolbox_tutorial_002.png :class: sphx-glr-single-img
์์ธก ๋งต์ ์ด ์ด๋ฏธ์ง์ ์ค๋ฒ๋ ์ดํ ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
# ์ ์ฒด ์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง(Whole slide image)์ ํจ์น ๋ ๋ฒจ ์์ธก ์๊ฐํ
# ๋จผ์ ๋ผ๋ฒจ ์์์ ๋งคํ ์ค์
label_color_dict = {}
label_color_dict[0] = ("empty", (0, 0, 0))
colors = cm.get_cmap("Set1").colors
for class_name, label in label_dict.items():
label_color_dict[label + 1] = (class_name, 255 * np.array(colors[label]))
pred_map = predictor.merge_predictions(
wsi_path,
wsi_output[0],
resolution=overview_resolution,
units=overview_unit,
)
overlay = overlay_prediction_mask(
wsi_overview,
pred_map,
alpha=0.5,
label_info=label_color_dict,
return_ax=True,
)
plt.show()
.. image-sg:: ../_static/img/tiatoolbox_tutorial/tiatoolbox_tutorial_003.png :alt: tiatoolbox tutorial :srcset: ../_static/img/tiatoolbox_tutorial/tiatoolbox_tutorial_003.png :class: sphx-glr-single-img
์ด ๋ถ๋ถ์์๋ TIAToolbox ์ธ๋ถ์ ์กด์ฌํ๋ ์ฌ์ ํ์ต๋ PyTorch ๋ชจ๋ธ์์ ํน์ง์ ์ถ์ถํ๋ ๋ฐฉ๋ฒ์ TIAToolbox์์ ์ ๊ณตํ๋ WSI ์ถ๋ก ์์ง์ ์ฌ์ฉํ์ฌ ๋ณด์ฌ์ค๋๋ค. ์ด๋ฅผ ์ค๋ช ํ๊ธฐ ์ํด, HistoEncoder๋ผ๋ ๋ณ๋ฆฌํ์ ์ด๋ฏธ์ง์ ํนํ๋ ๋ชจ๋ธ์ ์ฌ์ฉํ ๊ฒ์ ๋๋ค. HistoEncoder๋ ์กฐ์งํ ์ด๋ฏธ์ง์์ ํน์ง์ ์ถ์ถํ๋๋ก ์๊ฐ ์ง๋ ํ์ต ๋ฐฉ์(self-supervised) ์ผ๋ก ํ์ต๋์์ต๋๋ค. ์ด ๋ชจ๋ธ์ ๋ค์์์ ์ฌ์ฉํ ์ ์์ต๋๋ค:
โHistoEncoder: ๋์งํธ ๋ณ๋ฆฌํ์ ์ํ ๊ธฐ๋ณธ ๋ชจ๋ธโ (https://github.com/jopo666/HistoEncoder) ํฌ์ฑํค ๋ํ๊ต Pohjonen, Joona ํ.
ํน์ง ๋งต์ umap ์ฐจ์ ์ถ์๋ฅผ 3D(RGB)๋ก ์๊ฐํํ์ฌ, ์์์ ์ธ๊ธํ ์ฌ๋ฌ ์กฐ์ง ์ ํ ๊ฐ์ ์ฐจ์ด๋ฅผ ํน์ง๋ค์ด ์ด๋ป๊ฒ ํฌ์ฐฉํ๋์ง ๋ณด์ฌ์ค ๊ฒ์ ๋๋ค.
# ์ถ๊ฐ module ๊ฐ์ ธ์ค๊ธฐ
import histoencoder.functional as F
import torch.nn as nn
from tiatoolbox.models.engine.semantic_segmentor import DeepFeatureExtractor, IOSegmentorConfig
from tiatoolbox.models.models_abc import ModelABC
import umap
TIAToolbox๋ PyTorch์ nn.Module ์ ์์ํ๋ ModelABC ํด๋์ค๋ฅผ ์ ์ํ๋ฉฐ, ์ด๋ TIAToolbox์ ์ถ๋ก ์์ง์์ ์ฌ์ฉ๋ ๋ชจ๋ธ์ ๊ตฌ์กฐ๋ฅผ ๊ท์ ํฉ๋๋ค. ํ์ง๋ง histoencoder ๋ชจ๋ธ์ ์ด ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ฅด์ง ์๊ธฐ ๋๋ฌธ์, TIAToolbox ์์ง์ด ๊ธฐ๋ํ๋ ์ถ๋ ฅ๊ณผ ๋ฉ์๋๋ฅผ ์ ๊ณตํ๋ ํด๋์ค๋ก HistoEncoder๋ฅผ ๋ํ(wrap)ํด์ผ ํฉ๋๋ค.
class HistoEncWrapper(ModelABC):
"""tiatoolbox์ ModelABC ์ธํฐํ์ด์ค์ ๋ง์ถ HistoEncW๋ชจ๋ธ์ ๋ ํผ ์์ฑ"""
def __init__(self: HistoEncWrapper, encoder) -> None:
super().__init__()
self.feat_extract = encoder
def forward(self: HistoEncWrapper, imgs: torch.Tensor) -> torch.Tensor:
"""์
๋ ฅ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ธ์ ํตํด ์ ๋ฌ
Args:
imgs (torch.Tensor):
Model input.
"""
out = F.extract_features(self.feat_extract, imgs, num_blocks=2, avg_pool=True)
return out
@staticmethod
def infer_batch(
model: nn.Module,
batch_data: torch.Tensor,
*,
on_gpu: bool,
) -> list[np.ndarray]:
"""์
๋ ฅ ๋ฐฐ์น์ ๋ํ ์ถ๋ก ์คํ
์๋ฐฉํฅ ์ฐ์ฐ๊ณผ ์
์ถ๋ ฅ ์ง๊ณ๋ฅผ ์ํ ๋ก์ง์ด ํฌํจ๋์ด ์์ต๋๋ค.
Args:
model (nn.Module):
์ ์๋ PyTorch ๋ชจ๋ธ.
batch_data (torch.Tensor):
`torch.utils.data.DataLoader`์
์ํด ์์ฑ๋ ๋ฐ์ดํฐ์ ๋ฐฐ์น(batch).
on_gpu (bool):
์ถ๋ก ์ฐ์ฐ์ GPU์์ ํ ๊ฒ์ธ์ง.
"""
img_patches_device = batch_data.to('cuda') if on_gpu else batch_data
model.eval()
# ๊ธฐ์ธ๊ธฐ๋ฅผ ๊ณ์ฐํ์ง ์์(ํ๋ จ์ด ์๋)
with torch.inference_mode():
output = model(img_patches_device)
return [output.cpu().numpy()]
์ด์ ๋ํผ(wrapper)๋ฅผ ๋ง๋ค์์ผ๋, ํน์ง ์ถ์ถ ๋ชจ๋ธ์ ์์ฑํ๊ณ DeepFeatureExtractor ๋ฅผ ์ธ์คํด์คํ ํ์ฌ ์ด ๋ชจ๋ธ์ WSI์ ์ฌ์ฉํ ์ ์๊ฒ ํฉ๋๋ค. ์ด์ ์ ์ฌ์ฉํ๋ ๋์ผํ WSI๋ฅผ ์ฌ์ฉํ์ง๋ง, ์ด๋ฒ์๋ ๊ฐ ํจ์น์ ๋ํ ๋ผ๋ฒจ์ ์์ธกํ๋ ๋์ HistoEncoder ๋ชจ๋ธ์ ์ฌ์ฉํ์ฌ, WSI์ ํจ์น์์ ํน์ง์ ์ถ์ถํ ๊ฒ์ ๋๋ค.
# ๋ชจ๋ธ ๋ง๋ค๊ธฐ
encoder = F.create_encoder("prostate_medium")
model = HistoEncWrapper(encoder)
# ์ ์ฒ๋ฆฌ ํจ์ ์ค์
norm=transforms.Normalize(mean=[0.662, 0.446, 0.605],std=[0.169, 0.190, 0.155])
trans = [
transforms.ToTensor(),
norm,
]
model.preproc_func = transforms.Compose(trans)
wsi_ioconfig = IOSegmentorConfig(
input_resolutions=[{"units": "mpp", "resolution": 0.5}],
patch_input_shape=[224, 224],
output_resolutions=[{"units": "mpp", "resolution": 0.5}],
patch_output_shape=[224, 224],
stride_shape=[224, 224],
)
DeepFeatureExtractor
๋ฅผ ์์ฑํ ๋
auto_generate_mask=True
์ธ์๋ฅผ ์ ๋ฌํ ๊ฒ์
๋๋ค. ์ด๋ otsu ์๊ณ๊ฐ
์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํ์ฌ ์๋์ผ๋ก ์กฐ์ง ์์ญ์ ๋ง์คํฌ๋ฅผ ์์ฑํ์ฌ, ์ถ์ถ๊ธฐ๊ฐ
์กฐ์ง์ด ํฌํจ๋ ํจ์น์๋ง ์ฒ๋ฆฌํ๋๋ก ํฉ๋๋ค.
# ํน์ง ์ถ์ถ๊ธฐ ์์ฑ ๋ฐ WSI์์ ์คํํ๊ธฐ
extractor = DeepFeatureExtractor(model=model, auto_generate_mask=True, batch_size=32, num_loader_workers=4, num_postproc_workers=4)
with suppress_console_output():
out = extractor.predict(imgs=[wsi_path], mode="wsi", ioconfig=wsi_ioconfig, save_dir=global_save_dir / "wsi_features",)
์ด๋ฌํ ํน์ง๋ค์ ๋ค์ด์คํธ๋ฆผ ๋ชจ๋ธ์ ํ๋ จํ๋ ๋ฐ ์ฌ์ฉํ ์ ์์ง๋ง, ์ฌ๊ธฐ์๋ ํน์ง์ด ๋ฌด์์ ๋ํ๋ด๋์ง ์ง๊ด์ ์ผ๋ก ์ดํดํ๊ธฐ ์ํด UMAP ์ฐจ์ ์ถ์๋ฅผ ์ฌ์ฉํ์ฌ ํน์ง์ RGB ๊ณต๊ฐ์์ ์๊ฐํํ ๊ฒ์ ๋๋ค. ์ ์ฌํ ์์์ผ๋ก ๋ผ๋ฒจ๋ง๋ ํฌ์ธํธ๋ค์ ๋น์ทํ ํน์ง์ ๊ฐ์ง๊ณ ์์ด์ผ ํ๋ฏ๋ก, UMAP ์ถ์ ๊ฒฐ๊ณผ๋ฅผ WSI ์ธ๋ค์ผ์ ์ค๋ฒ๋ ์ดํ์ฌ ํน์ง๋ค์ด ์๋ก ๋ค๋ฅธ ์กฐ์ง ์์ญ์ผ๋ก ์์ฐ์ค๋ฝ๊ฒ ๋ถ๋ฆฌ๋๋์ง ํ์ธํ ์ ์์ต๋๋ค. ์ดํ, ์์์ ์์ฑํ ํจ์น ์์ค์ ์์ธก ๋งต๊ณผ ํจ๊ป ์ด๋ฅผ ์๊ฐํํ์ฌ, ํน์ง๊ณผ ํจ์น ์์ค ์์ธก ๊ฐ์ ์ฐจ์ด๋ฅผ ๋น๊ตํด๋ณด๊ฒ ์ต๋๋ค.
# ๋จผ์ , UMAP ์ถ์ ๊ณ์ฐ์ ์ํ ํจ์ ์ ์
def umap_reducer(x, dims=3, nns=10):
"""์
๋ ฅ ๋ฐ์ดํฐ์ UMAP ์ถ์"""
reducer = umap.UMAP(n_neighbors=nns, n_components=dims, metric="manhattan", spread=0.5, random_state=2)
reduced = reducer.fit_transform(x)
reduced -= reduced.min(axis=0)
reduced /= reduced.max(axis=0)
return reduced
# ํน์ง ์ถ์ถ๊ธฐ์์ ์ถ๋ ฅ๋ ํน์ง ๋ถ๋ฌ์ค๊ธฐ
pos = np.load(global_save_dir / "wsi_features" / "0.position.npy")
feats = np.load(global_save_dir / "wsi_features" / "0.features.0.npy")
pos = pos / 8 # 0.5mpp์์ ํน์ง์ ์ถ์ถํ๊ณ , 4mpp์์ ์ธ๋ค์ผ์ ์ค๋ฒ๋ ์ดํ๊ธฐ
# ํน์ง์ 3์ฐจ์(RGB) ๊ณต๊ฐ์ผ๋ก ์ถ์ํ๊ธฐ
reduced = umap_reducer(feats)
# ๋ถ๋ฅ๊ธฐ์ ์์ธก ๋งต์ ๋ค์ ๊ทธ๋ฆฌ๊ธฐ
overlay = overlay_prediction_mask(
wsi_overview,
pred_map,
alpha=0.5,
label_info=label_color_dict,
return_ax=True,
)
# ํน์ง ๋งต ์ถ์๋ฅผ ์๊ฐํํ๊ธฐ
plt.figure()
plt.imshow(wsi_overview)
plt.scatter(pos[:,0], pos[:,1], c=reduced, s=1, alpha=0.5)
plt.axis("off")
plt.title("UMAP reduction of HistoEnc features")
plt.show()
.. rst-class:: sphx-glr-horizontal * .. image-sg:: ../_static/img/tiatoolbox_tutorial/tiatoolbox_tutorial_004.png :alt: tiatoolbox tutorial :srcset: ../_static/img/tiatoolbox_tutorial/tiatoolbox_tutorial_004.png :class: sphx-glr-multi-img * .. image-sg:: ../_static/img/tiatoolbox_tutorial/tiatoolbox_tutorial_005.png :alt: UMAP reduction of HistoEnc features :srcset: ../_static/img/tiatoolbox_tutorial/tiatoolbox_tutorial_005.png :class: sphx-glr-multi-img
ํจ์น ์์ค ์์ธก๊ธฐ(patch-level predictor)์์ ์์ฑ๋ ์์ธก ๋งต๊ณผ ์๊ฐ ์ง๋ ํ์ต(self-supervised) ์ธ์ฝ๋๋ฅผ ํตํด ํน์ง์ ์ถ์ถํ ํน์ง ๋งต์ด WSI์์ ์กฐ์ง ์ ํ์ ๋ํ ์ ์ฌํ ์ ๋ณด๋ฅผ ํฌ์ฐฉํ๊ณ ์์์ ํ์ธํ ์ ์์ต๋๋ค. ์ด๋ ๋ชจ๋ธ์ด ์์๋๋ก ์๋ํ๊ณ ์์์ ํ์ธํ ์ ์๋ ์ข์ ๊ฒ์ฆ ๋ฐฉ๋ฒ์ ๋๋ค. ๋ํ, HistoEncoder ๋ชจ๋ธ์ด ์ถ์ถํ ํน์ง๋ค์ด ์กฐ์ง ์ ํ ๊ฐ์ ์ฐจ์ด๋ฅผ ์ ํฌ์ฐฉํ๊ณ ์์ผ๋ฉฐ, ๋ฐ๋ผ์ ์ด ํน์ง๋ค์ด ์กฐ์งํ์ ์ผ๋ก ์ค์ํ ์ ๋ณด๋ฅผ ์ธ์ฝ๋ฉํ๊ณ ์์์ ๋ณด์ฌ์ค๋๋ค.
์ด ๋
ธํธ๋ถ์์๋ PatchPredictor
์
DeepFeatureExtractor
ํด๋์ค์ predict
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ
ํฐ ํ์ผ(tiles)๊ณผ WSI์ ํจ์น์ ๋ํด ๋ผ๋ฒจ์ ์์ธกํ๊ฑฐ๋ ํน์ง์ ์ถ์ถํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค.
๋ํ, ํจ์น ์์ธก ๊ฒฐ๊ณผ๋ฅผ ๋ณํฉํ๊ณ ์
๋ ฅ ์ด๋ฏธ์ง/WSI์ ์์ธก ๋งต์
์ค๋ฒ๋ ์ด๋ก ์๊ฐํํ๋ merge_predictions
์ overlay_prediction_mask
๋ณด์กฐ ํจ์๋ ์๊ฐํฉ๋๋ค.
๋ชจ๋ ๊ณผ์ ์ TIAToolbox ๋ด์์ ์ด๋ฃจ์ด์ง๋ฉฐ, ์์ ์ฝ๋๋ฅผ ๋ฐ๋ผ ์ฝ๊ฒ ๊ตฌ์ฑํ ์ ์์ต๋๋ค. ์ ๋ ฅ๊ฐ๊ณผ ์ต์ ์ ์ฌ๋ฐ๋ฅด๊ฒ ์ค์ ํ๋ ๊ฒ์ ๊ผญ ํ์ธํ์ธ์. ๋ํ predict ํจ์์ ๋งค๊ฐ๋ณ์๋ฅผ ๋ณ๊ฒฝํ์ ๋ ์์ธก ๊ฒฐ๊ณผ์ ๋ฏธ์น๋ ์ํฅ์ ํ๊ตฌํ๋ ๊ฒ๋ ๊ถ์ฅํฉ๋๋ค. TIAToolbox ํ๋ ์์ํฌ์์ ์ ๊ณตํ๋ ์ปค๋ฎค๋ํฐ์ ๋ชจ๋ธ ๋๋ ์ฌ์ฉ์ ์ ์ ์ฌ์ ํ์ต๋ ๋ชจ๋ธ์ ์ฌ์ฉํ์ฌ, TIAToolbox ๋ชจ๋ธ ํด๋์ค์ ์ ์๋์ง ์์ ๊ตฌ์กฐ์ ๋ชจ๋ธ์ด๋ผ๋ ๋ํ WSI์ ๋ํด ์ถ๋ก ์ ์ํํ๋ ๋ฐฉ๋ฒ์ ์์ฐํ์ต๋๋ค.
๋ค์ ์๋ฃ๋ฅผ ํตํด ๋ ๋ง์ ๋ด์ฉ์ ๋ฐฐ์ธ ์ ์์ต๋๋ค: