Skip to content

Commit 873b0f9

Browse files
authored
HoVerNet training pipeline (#999)
Fixes Project-MONAI/MONAI#4878 ### Description HoVerNet training tutorial - [x] ignite version pipeline ### Checks <!--- Put an `x` in all the boxes that apply, and remove the not applicable items --> - [ ] Notebook runs automatically `./runner [-p <regex_pattern>]` Signed-off-by: KumoLiu <[email protected]>
1 parent 73f0e6a commit 873b0f9

File tree

4 files changed

+836
-0
lines changed

4 files changed

+836
-0
lines changed

pathology/hovernet/README.MD

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# HoVerNet Examples
2+
3+
This folder contains ignite version examples to run train and validate a HoVerNet model.
4+
It also has torch version notebooks to run training and evaluation.
5+
<p align="center">
6+
<img src="https://ars.els-cdn.com/content/image/1-s2.0-S1361841519301045-fx1_lrg.jpg" alt="hovernet scheme")
7+
</p>
8+
implementation based on:
9+
10+
Simon Graham et al., HoVer-Net: Simultaneous Segmentation and Classification of Nuclei in Multi-Tissue Histology Images.' Medical Image Analysis, (2019). https://arxiv.org/abs/1812.06499
11+
12+
### 1. Data
13+
14+
CoNSeP datasets which are used in the examples can be downloaded from https://warwick.ac.uk/fac/cross_fac/tia/data/hovernet/.
15+
- First download CoNSeP dataset to `data_root`.
16+
- Run prepare_patches.py to prepare patches from images.
17+
18+
### 2. Questions and bugs
19+
20+
- For questions relating to the use of MONAI, please us our [Discussions tab](https://github.com/Project-MONAI/MONAI/discussions) on the main repository of MONAI.
21+
- For bugs relating to MONAI functionality, please create an issue on the [main repository](https://github.com/Project-MONAI/MONAI/issues).
22+
- For bugs relating to the running of a tutorial, please create an issue in [this repository](https://github.com/Project-MONAI/Tutorials/issues).
23+
24+
25+
### 3. List of notebooks and examples
26+
#### [Prepare Your Data](./prepare_patches.py)
27+
This example is used to prepare patches from tiles referring to the implementation from https://github.com/vqdang/hover_net/blob/master/extract_patches.py. Prepared patches will be saved in `data_root`/Prepared.
28+
29+
```bash
30+
# Run to know all possible options
31+
python ./prepare_patches.py -h
32+
33+
# Prepare patches from images
34+
python ./prepare_patches.py \
35+
--root `data_root`
36+
```
37+
38+
#### [HoVerNet Training](./training.py)
39+
This example uses MONAI workflow to train a HoVerNet model on prepared CoNSeP dataset.
40+
Since HoVerNet is training via a two-stage approach. First initialised the model with pre-trained weights on the [ImageNet dataset](https://ieeexplore.ieee.org/document/5206848), trained only the decoders for the first 50 epochs, and then fine-tuned all layers for another 50 epochs. We need to specify `--stage` during training.
41+
42+
Each user is responsible for checking the content of models/datasets and the applicable licenses and determining if suitable for the intended use.
43+
The license for the pre-trained model used in examples is different than MONAI license. Please check the source where these weights are obtained from:
44+
https://github.com/vqdang/hover_net#data-format
45+
46+
47+
```bash
48+
# Run to know all possible options
49+
python ./training.py -h
50+
51+
# Train a hovernet model on single-gpu(replace with your own ckpt path)
52+
export CUDA_VISIBLE_DEVICES=0; python training.py \
53+
--ep 50 \
54+
--stage 0 \
55+
--bs 16 \
56+
--root `save_root`
57+
export CUDA_VISIBLE_DEVICES=0; python training.py \
58+
--ep 50 \
59+
--stage 1 \
60+
--bs 4 \
61+
--root `save_root` \
62+
--ckpt logs/stage0/checkpoint_epoch=50.pt
63+
64+
# Train a hovernet model on multi-gpu (NVIDIA)(replace with your own ckpt path)
65+
torchrun --nnodes=1 --nproc_per_node=2 training.py \
66+
--ep 50 \
67+
--bs 8 \
68+
--root `save_root` \
69+
--stage 0
70+
torchrun --nnodes=1 --nproc_per_node=2 training.py \
71+
--ep 50 \
72+
--bs 2 \
73+
--root `save_root` \
74+
--stage 1 \
75+
--ckpt logs/stage0/checkpoint_epoch=50.pt
76+
```
77+
78+
#### [HoVerNet Validation](./evaluation.py)
79+
This example uses MONAI workflow to evaluate the trained HoVerNet model on prepared test data from CoNSeP dataset.
80+
With their metrics on original mode. We reproduce the results with Dice: 0.82762; PQ: 0.48976; F1d: 0.73592.
81+
```bash
82+
# Run to know all possible options
83+
python ./evaluation.py -h
84+
85+
# Evaluate a HoVerNet model
86+
python ./evaluation.py
87+
--root `save_root` \
88+
--ckpt logs/stage0/checkpoint_epoch=50.pt
89+
```
90+
91+
## Disclaimer
92+
93+
This is an example, not to be used for diagnostic purposes.

pathology/hovernet/evaluation.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import os
2+
import glob
3+
import logging
4+
import torch
5+
from argparse import ArgumentParser
6+
from monai.data import DataLoader, CacheDataset
7+
from monai.networks.nets import HoVerNet
8+
from monai.engines import SupervisedEvaluator
9+
from monai.transforms import (
10+
LoadImaged,
11+
Lambdad,
12+
Activationsd,
13+
Compose,
14+
CastToTyped,
15+
ComputeHoVerMapsd,
16+
ScaleIntensityRanged,
17+
CenterSpatialCropd,
18+
)
19+
from monai.handlers import (
20+
MeanDice,
21+
StatsHandler,
22+
CheckpointLoader,
23+
)
24+
from monai.utils.enums import HoVerNetBranch
25+
from monai.apps.pathology.handlers.utils import from_engine_hovernet
26+
from monai.apps.pathology.engines.utils import PrepareBatchHoVerNet
27+
from skimage import measure
28+
29+
30+
def prepare_data(data_dir, phase):
31+
data_dir = os.path.join(data_dir, phase)
32+
33+
images = list(sorted(
34+
glob.glob(os.path.join(data_dir, "*/*image.npy"))))
35+
inst_maps = list(sorted(
36+
glob.glob(os.path.join(data_dir, "*/*inst_map.npy"))))
37+
type_maps = list(sorted(
38+
glob.glob(os.path.join(data_dir, "*/*type_map.npy"))))
39+
40+
data_dicts = [
41+
{"image": _image, "label_inst": _inst_map, "label_type": _type_map}
42+
for _image, _inst_map, _type_map in zip(images, inst_maps, type_maps)
43+
]
44+
45+
return data_dicts
46+
47+
48+
def run(cfg):
49+
if cfg["mode"].lower() == "original":
50+
cfg["patch_size"] = [270, 270]
51+
cfg["out_size"] = [80, 80]
52+
elif cfg["mode"].lower() == "fast":
53+
cfg["patch_size"] = [256, 256]
54+
cfg["out_size"] = [164, 164]
55+
56+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
57+
val_transforms = Compose(
58+
[
59+
LoadImaged(keys=["image", "label_inst", "label_type"], image_only=True),
60+
Lambdad(keys="label_inst", func=lambda x: measure.label(x)),
61+
CastToTyped(keys=["image", "label_inst"], dtype=torch.int),
62+
CenterSpatialCropd(
63+
keys="image",
64+
roi_size=cfg["patch_size"],
65+
),
66+
ScaleIntensityRanged(keys=["image"], a_min=0.0, a_max=255.0, b_min=0.0, b_max=1.0, clip=True),
67+
ComputeHoVerMapsd(keys="label_inst"),
68+
Lambdad(keys="label_inst", func=lambda x: x > 0, overwrite="label"),
69+
CenterSpatialCropd(
70+
keys=["label", "hover_label_inst", "label_inst", "label_type"],
71+
roi_size=cfg["out_size"],
72+
),
73+
CastToTyped(keys=["image", "label_inst", "label_type"], dtype=torch.float32),
74+
]
75+
)
76+
77+
# Create MONAI DataLoaders
78+
valid_data = prepare_data(cfg["root"], "valid")
79+
valid_ds = CacheDataset(data=valid_data, transform=val_transforms, cache_rate=1.0, num_workers=4)
80+
val_loader = DataLoader(
81+
valid_ds,
82+
batch_size=cfg["batch_size"],
83+
num_workers=cfg["num_workers"],
84+
pin_memory=torch.cuda.is_available()
85+
)
86+
87+
# initialize model
88+
model = HoVerNet(
89+
mode=cfg["mode"],
90+
in_channels=3,
91+
out_classes=cfg["out_classes"],
92+
act=("relu", {"inplace": True}),
93+
norm="batch",
94+
pretrained_url=None,
95+
freeze_encoder=False,
96+
).to(device)
97+
98+
post_process_np = Compose([
99+
Activationsd(keys=HoVerNetBranch.NP.value, softmax=True),
100+
Lambdad(keys=HoVerNetBranch.NP.value, func=lambda x: x[1: 2, ...] > 0.5)])
101+
post_process = Lambdad(keys="pred", func=post_process_np)
102+
103+
# Evaluator
104+
val_handlers = [
105+
CheckpointLoader(load_path=cfg["ckpt_path"], load_dict={"net": model}),
106+
StatsHandler(output_transform=lambda x: None),
107+
]
108+
evaluator = SupervisedEvaluator(
109+
device=device,
110+
val_data_loader=val_loader,
111+
prepare_batch=PrepareBatchHoVerNet(extra_keys=['label_type', 'hover_label_inst']),
112+
network=model,
113+
postprocessing=post_process,
114+
key_val_metric={"val_dice": MeanDice(include_background=False, output_transform=from_engine_hovernet(keys=["pred", "label"], nested_key=HoVerNetBranch.NP.value))},
115+
val_handlers=val_handlers,
116+
amp=cfg["amp"],
117+
)
118+
119+
state = evaluator.run()
120+
print(state)
121+
122+
123+
def main():
124+
parser = ArgumentParser(description="Tumor detection on whole slide pathology images.")
125+
parser.add_argument(
126+
"--root",
127+
type=str,
128+
default="/workspace/Data/CoNSeP/Prepared/consep",
129+
help="root data dir",
130+
)
131+
132+
parser.add_argument("--bs", type=int, default=16, dest="batch_size", help="batch size")
133+
parser.add_argument("--no-amp", action="store_false", dest="amp", help="deactivate amp")
134+
parser.add_argument("--classes", type=int, default=5, dest="out_classes", help="output classes")
135+
parser.add_argument("--mode", type=str, default="original", help="choose either `original` or `fast`")
136+
137+
parser.add_argument("--cpu", type=int, default=8, dest="num_workers", help="number of workers")
138+
parser.add_argument("--use_gpu", type=bool, default=True, dest="use_gpu", help="whether to use gpu")
139+
parser.add_argument("--ckpt", type=str, dest="ckpt_path", help="checkpoint path")
140+
141+
args = parser.parse_args()
142+
cfg = vars(args)
143+
print(cfg)
144+
145+
logging.basicConfig(level=logging.INFO)
146+
run(cfg)
147+
148+
149+
if __name__ == "__main__":
150+
main()

0 commit comments

Comments
 (0)