PyTorch 2.2 中文官方教程(五)(3)https://developer.aliyun.com/article/1482502
使用 PyTorch 和 TIAToolbox 进行全幻灯片图像分类
原文:
pytorch.org/tutorials/intermediate/tiatoolbox_tutorial.html
译者:飞龙
提示
为了充分利用本教程,我们建议使用这个Colab 版本。这将允许您尝试下面介绍的信息。
介绍
在本教程中,我们将展示如何使用 PyTorch 深度学习模型和 TIAToolbox 来对全幻灯片图像(WSIs)进行分类。WSI 是通过手术或活检拍摄的人体组织样本的图像,并使用专门的扫描仪进行扫描。病理学家和计算病理学研究人员使用它们来研究疾病,如癌症在微观水平上的情况,以便了解肿瘤生长等情况,并帮助改善患者的治疗。
使 WSI 难以处理的是它们的巨大尺寸。例如,典型的幻灯片图像具有100,000x100,000 像素,其中每个像素可能对应于幻灯片上约 0.25x0.25 微米。这在加载和处理这样的图像中带来了挑战,更不用说单个研究中可能有数百甚至数千个 WSI(更大的研究产生更好的结果)!
传统的图像处理流程不适用于 WSI 处理,因此我们需要更好的工具。这就是TIAToolbox可以帮助的地方,它提供了一组有用的工具,以快速和高效地导入和处理组织幻灯片。通常,WSI 以金字塔结构保存,具有多个在各种放大级别上优化可视化的相同图像副本。金字塔的级别 0(或底层)包含具有最高放大倍数或缩放级别的图像,而金字塔中的较高级别具有基础图像的较低分辨率副本。金字塔结构如下所示。
WSI 金字塔堆栈(来源)
TIAToolbox 允许我们自动化常见的下游分析任务,例如组织分类。在本教程中,我们将展示如何:1. 使用 TIAToolbox 加载 WSI 图像;2. 使用不同的 PyTorch 模型对幻灯片进行补丁级别的分类。在本教程中,我们将提供使用 TorchVision ResNet18
模型和自定义 HistoEncoder <github.com/jopo666/HistoEncoder
>`__ 模型的示例。
让我们开始吧!
设置环境
要运行本教程中提供的示例,需要以下软件包作为先决条件。
- OpenJpeg
- OpenSlide
- Pixman
- TIAToolbox
- HistoEncoder(用于自定义模型示例)
请在终端中运行以下命令以安装这些软件包:
apt-get -y -qq install libopenjp2-7-dev libopenjp2-tools openslide-tools libpixman-1-dev pip install -q ‘tiatoolbox<1.5’ histoencoder && echo “安装完成。”
或者,您可以运行brew install openjpeg openslide
在 MacOS 上安装先决条件软件包,而不是apt-get
。有关安装的更多信息可以在这里找到。
导入相关库
"""Import modules required to run the Jupyter notebook.""" from __future__ import annotations # Configure logging import logging import warnings if logging.getLogger().hasHandlers(): logging.getLogger().handlers.clear() warnings.filterwarnings("ignore", message=".*The 'nopython' keyword.*") # Downloading data and files import shutil from pathlib import Path from zipfile import ZipFile # Data processing and visualization 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 # TIAToolbox for WSI loading and processing 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-related import torch from torchvision import transforms # Configure plotting 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 # If you are not using GPU, change ON_GPU to False ON_GPU = True # Function to suppress console output for overly verbose code blocks 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: """Helper function to delete directory.""" if Path(dir_path).is_dir(): shutil.rmtree(dir_path) logger.info("Removing directory %s", dir_path) rmdir(global_save_dir) # remove directory if it exists from previous runs global_save_dir.mkdir() logger.info("Creating new directory %s", global_save_dir)
下载数据
对于我们的样本数据,我们将使用一个整个幻灯片图像,以及来自Kather 100k数据集验证子集的补丁。
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...") # Downloading and unzip a sample 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, ) # Download and unzip a sample of the validation set used to train the Kather 100K dataset 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) # Download pretrained model weights for WSI classification using ResNet18 architecture download_data( "https://tiatoolbox.dcs.warwick.ac.uk/models/pc/resnet18-kather100k.pth", weights_path, ) logger.info("Download is complete.")
读取数据
我们创建一个补丁列表和一个相应标签列表。例如,label_list
中的第一个标签将指示patch_list
中第一个图像补丁的类。
# Read the patch data and create a list of patches and a list of corresponding labels dataset_path = global_save_dir / "kather100k-validation-sample" # Set the path to the dataset image_ext = ".tif" # file extension of each image # Obtain the mapping between the label ID and the class name 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()) # Generate a list of patches and generate the label from the filename 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)) # Show some dataset statistics plt.bar(class_names, [label_list.count(label) for label in class_labels]) plt.xlabel("Patch types") plt.ylabel("Number of patches") # Count the number of examples per class 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), ) # Overall dataset statistics logger.info("Total number of patches: %d", (len(patch_list)))
|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
如您所见,对于这个补丁数据集,我们有 9 个类/标签,ID 为 0-8,并附带类名,描述补丁中的主要组织类型:
- BACK ⟶ 背景(空玻璃区域)
- LYM ⟶ 淋巴细胞
- NORM ⟶ 正常结肠粘膜
- DEB ⟶ 碎片
- MUS ⟶ 平滑肌
- STR ⟶ 癌相关基质
- ADI ⟶ 脂肪
- MUC ⟶ 粘液
- TUM ⟶ 结直肠腺癌上皮
分类图像补丁
我们首先使用patch
模式,然后使用wsi
模式来为数字切片中的每个补丁获取预测。
定义PatchPredictor
模型
PatchPredictor 类运行基于 PyTorch 编写的 CNN 分类器。
model
可以是任何经过训练的 PyTorch 模型,约束是它应该遵循tiatoolbox.models.abc.ModelABC
(文档)<tia-toolbox.readthedocs.io/en/latest/_autosummary/tiatoolbox.models.models_abc.ModelABC.html
>__ 类结构。有关此事的更多信息,请参阅[我们关于高级模型技术的示例笔记本](https://github.com/TissueImageAnalytics/tiatoolbox/blob/develop/examples/07-advanced-modeling.ipynb)。为了加载自定义模型,您需要编写一个小的预处理函数,如
preproc_func(img)`,确保输入张量的格式适合加载的网络。- 或者,您可以将
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)内存容量。
# Importing a pretrained PyTorch model from TIAToolbox predictor = PatchPredictor(pretrained_model='resnet18-kather100k', batch_size=32) # Users can load any PyTorch model architecture instead using the following script model = vanilla.CNNModel(backbone="resnet18", num_classes=9) # Importing model from 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)
预测补丁标签
我们创建一个预测器对象,然后使用patch
模式调用predict
方法。然后计算分类准确度和混淆矩阵。
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) # Creating and visualizing the confusion matrix for patch classification results conf = confusion_matrix(label_list, output["predictions"], normalize="true") df_cm = pd.DataFrame(conf, index=class_names, columns=class_names) df_cm
|2023-11-14|13:16:03.215| [INFO] Classification accuracy: 0.993000
背景 | 正常 | 碎片 | 肿瘤 | 脂肪 | 粘液 | 平滑肌 | 结缔组织 | 淋巴 | |
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
,这是一个指定图像读取和预测写入的配置的类,用于模型预测引擎。这是为了通知分类器应该读取 WSI 金字塔的哪个级别,处理数据并生成输出。
IOPatchPredictorConfig
的参数定义如下:
input_resolutions
: 以字典形式的列表,指定每个输入的分辨率。列表元素必须与目标model.forward()
中的顺序相同。如果您的模型只接受一个输入,您只需要放置一个指定'units'
和'resolution'
的字典。请注意,TIAToolbox 支持具有多个输入的模型。有关单位和分辨率的更多信息,请参阅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
: 输入列表,应该是指向输入瓷砖或 WSI 的路径列表。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
: 如果需要生成补丁分类结果的二维地图,则可以将此参数设置为True
。然而,对于大型 WSI,这将需要大量可用内存。另一种(默认)解决方案是将merge_predictions=False
,然后使用稍后将看到的merge_predictions
函数生成 2D 预测地图。
由于我们使用了大型 WSI,补丁提取和预测过程可能需要一些时间(如果您可以访问启用了 Cuda 的 GPU 和 PyTorch+Cuda,请确保将ON_GPU=True
)。
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
来查看预测模型在我们的全幻灯片图像上的工作方式。我们首先需要合并补丁预测输出,然后将其可视化为覆盖在原始图像上的叠加图。与之前一样,使用merge_predictions
方法来合并补丁预测。在这里,我们设置参数resolution=1.25, units='power'
以在 1.25 倍放大率下生成预测地图。如果您想要更高/更低分辨率(更大/更小)的预测地图,您需要相应地更改这些参数。当预测合并完成后,使用overlay_patch_prediction
函数将预测地图叠加在 WSI 缩略图上,该缩略图应该以用于预测合并的分辨率提取。
overview_resolution = ( 4 # the resolution in which we desire to merge and visualize the patch predictions ) # the unit of the `resolution` parameter. Can be "power", "level", "mpp", or "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")
将预测地图叠加在这幅图像上如下所示:
# Visualization of whole-slide image patch-level prediction # first set up a label to color mapping 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()
使用专门用于病理学的模型进行特征提取
在本节中,我们将展示如何从 TIAToolbox 之外存在的预训练 PyTorch 模型中提取特征,使用 TIAToolbox 提供的 WSI 推理引擎。为了说明这一点,我们将使用 HistoEncoder,这是一个专门用于计算病理学的模型,已经以自监督的方式进行训练,以从组织学图像中提取特征。该模型已经在这里提供:
‘HistoEncoder: Foundation models for digital pathology’ (github.com/jopo666/HistoEncoder
) 由赫尔辛基大学的 Pohjonen, Joona 和团队提供。
我们将绘制一个 3D(RGB)的 UMAP 降维特征图,以可视化特征如何捕捉上述提到的一些组织类型之间的差异。
# Import some extra modules 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 定义了一个名为 ModelABC 的类,它是一个继承 PyTorch nn.Module的类,并指定了模型应该如何才能在 TIAToolbox 推理引擎中使用。histoencoder 模型不遵循这种结构,因此我们需要将其包装在一个类中,该类的输出和方法是 TIAToolbox 引擎所期望的。
class HistoEncWrapper(ModelABC): """Wrapper for HistoEnc model that conforms to tiatoolbox ModelABC interface.""" def __init__(self: HistoEncWrapper, encoder) -> None: super().__init__() self.feat_extract = encoder def forward(self: HistoEncWrapper, imgs: torch.Tensor) -> torch.Tensor: """Pass input data through the model. 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]: """Run inference on an input batch. Contains logic for forward operation as well as i/o aggregation. Args: model (nn.Module): PyTorch defined model. batch_data (torch.Tensor): A batch of data generated by `torch.utils.data.DataLoader`. on_gpu (bool): Whether to run inference on a GPU. """ img_patches_device = batch_data.to('cuda') if on_gpu else batch_data model.eval() # Do not compute the gradient (not training) with torch.inference_mode(): output = model(img_patches_device) return [output.cpu().numpy()]
现在我们有了我们的包装器,我们将创建我们的特征提取模型,并实例化一个DeepFeatureExtractor以允许我们在 WSI 上使用这个模型。我们将使用与上面相同的 WSI,但这次我们将使用 HistoEncoder 模型从 WSI 的补丁中提取特征,而不是为每个补丁预测某个标签。
# create the model encoder = F.create_encoder("prostate_medium") model = HistoEncWrapper(encoder) # set the pre-processing function 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
参数。这将自动使用大津阈值法创建组织区域的掩模,以便提取器仅处理包含组织的那些补丁。
# create the feature extractor and run it on the 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 缩略图上时,特征是否自然地分离成不同的组织区域。我们将把它与上面的补丁级别预测地图一起绘制,以查看特征与补丁级别预测的比较。
# First we define a function to calculate the umap reduction def umap_reducer(x, dims=3, nns=10): """UMAP reduction of the input data.""" 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 # load the features output by our feature extractor 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 # as we extracted at 0.5mpp, and we are overlaying on a thumbnail at 4mpp # reduce the features into 3 dimensional (rgb) space reduced = umap_reducer(feats) # plot the prediction map the classifier again overlay = overlay_prediction_mask( wsi_overview, pred_map, alpha=0.5, label_info=label_color_dict, return_ax=True, ) # plot the feature map reduction 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()
我们看到,来自我们的补丁级预测器的预测地图和来自我们的自监督特征编码器的特征地图捕捉了 WSI 中关于组织类型的类似信息。这是一个很好的健全检查,表明我们的模型正在按预期工作。它还显示了 HistoEncoder 模型提取的特征捕捉了组织类型之间的差异,因此它们正在编码组织学相关信息。
下一步去哪里
在这个笔记本中,我们展示了如何使用PatchPredictor
和DeepFeatureExtractor
类及其predict
方法来预测大块瓷砖和 WSI 的补丁的标签,或提取特征。我们介绍了merge_predictions
和overlay_prediction_mask
辅助函数,这些函数合并了补丁预测输出,并将结果预测地图可视化为覆盖在输入图像/WSI 上的叠加图。
所有过程都在 TIAToolbox 内部进行,我们可以轻松地将各个部分组合在一起,按照我们的示例代码。请确保正确设置输入和选项。我们鼓励您进一步调查更改predict
函数参数对预测输出的影响。我们已经演示了如何在 TIAToolbox 框架中使用您自己预训练的模型或研究社区提供的模型来执行对大型 WSI 的推断,即使模型结构未在 TIAToolbox 模型类中定义。
您可以通过以下资源了解更多信息: