여러 판독자와 STAPLE로 CT 볼륨 분할하기
Potato에서 판독자 세 명에게 같은 NIfTI CT 스캔을 주고, 각 판독자의 비장 마스크를 내보낸 뒤, SimpleITK에서 STAPLE 합의에 대해 모든 판독자를 평가합니다.
3D Slicer나 ITK-SNAP에서 CT 스캔을 분할하는 의료 영상 연구자는 이 튜토리얼을 따라 Potato 2.13.1에서 같은 스캔을 판독자 세 명에게 주고, 판독자들의 3D Dice 일치도를 Potato에서 읽고, 각 판독자의 마스크를 NIfTI 라벨 볼륨으로 내보내고, STAPLE로 합의 마스크를 만들 수 있습니다. STAPLE(Simultaneous Truth and Performance Level Estimation)은 여러 판독자의 마스크로부터 합의 분할을 추정하며, 그 합의에 대한 각 판독자의 민감도와 특이도도 함께 추정합니다. Dice 계수는 두 마스크의 겹침을 재며, 공유하는 복셀이 없으면 0, 마스크가 같으면 1입니다.
Potato 2.13.1은 판독자 사이의 Dice를 계산하고, 판독자가 셋 이상이면 각 판독자에 대한 STAPLE의 민감도와 특이도를 계산하지만, 합의 마스크는 내보내지 않습니다. 이 튜토리얼의 합의 마스크는 Potato가 내보낸 마스크에 SimpleITK를 적용해 만듭니다. 실제 사례는 Medical Segmentation Decathlon의 CT 스캔 세 개에서 비장을 분할합니다. 이 페이지의 수치는 그 스캔들에 Potato 2.12.1을 실행한 결과에서 나왔고, 일치도 보고서는 저장된 마스크에 Potato 2.13.1로 다시 실행했습니다. Potato는 임상 또는 규제 승인을 받지 않은 연구용 소프트웨어이며, 이 페이지의 어떤 내용도 환자 진료를 위한 것이 아닙니다.
스캔
Medical Segmentation Decathlon(MSD)은 벤치마킹을 위한 분할 과제 열 가지를 공개했습니다(Antonelli et al., 2022). Task09는 비장으로, Memorial Sloan Kettering Cancer Center의 CT 볼륨 61개입니다. 학습 볼륨 41개에는 전문가의 비장 마스크가 딸려 있고, 테스트 볼륨 20개에는 없습니다. MSD 사이트와 과제의 dataset.json은 모두 라이선스를 CC BY-SA 4.0으로 밝힙니다. CC BY-SA 4.0에서는 출처를 밝히고 스캔을 공유하고 개작할 수 있으며, 개작물은 같은 라이선스로 공유해야 합니다.
Task09는 1.6GB 아카이브 하나로 내려받습니다. 이 튜토리얼은 가장 작은 학습 스캔 세 개(합쳐서 약 30MB)를 사용하며, 나중에 합의를 확인하기 위한 정답 키로 전문가 마스크를 따로 보관합니다.
curl -O https://msd-for-monai.s3-us-west-2.amazonaws.com/Task09_Spleen.tar
tar -xf Task09_Spleen.tar \
Task09_Spleen/imagesTr/spleen_3.nii.gz Task09_Spleen/labelsTr/spleen_3.nii.gz \
Task09_Spleen/imagesTr/spleen_8.nii.gz Task09_Spleen/labelsTr/spleen_8.nii.gz \
Task09_Spleen/imagesTr/spleen_56.nii.gz Task09_Spleen/labelsTr/spleen_56.nii.gz각 스캔은 슬라이스당 512 × 512 복셀이고 슬라이스 두께는 5.0에서 7.5mm이며, 비장은 축상 슬라이스 9에서 19개에 걸쳐 있습니다.
| 스캔 | 슬라이스 수 | 픽셀 크기(mm) | 슬라이스 두께(mm) | 비장이 있는 슬라이스 |
|---|---|---|---|---|
| spleen_3 | 40 | 0.74 | 5.0 | 14 |
| spleen_8 | 31 | 0.79 | 7.5 | 19 |
| spleen_56 | 33 | 0.70 | 7.0 | 9 |
볼륨 지원과 함께 Potato 설치
볼륨 뷰어는 nibabel로 NIfTI를, pydicom으로 DICOM을 읽고 highdicom으로 DICOM SEG를 씁니다. volumes 추가 패키지가 세 라이브러리를 설치합니다.
pip install 'potato-annotation[volumes]==2.13.1'Potato는 설치된 패키지에서 뷰어의 스크립트와 스타일을 제공합니다. 테스트 실행에서 어노테이션 페이지의 헤드리스 브라우저는 다른 어떤 호스트에도 요청을 보내지 않았습니다. 패키지와 스캔이 컴퓨터에 있으면 튜토리얼 전체가 네트워크 연결 없이 실행되며, 에어갭 배포가 그 구성을 다룹니다.
프로젝트 폴더
스캔 세 개를 media/ 폴더에 넣고 데이터 파일에 한 줄에 사례 하나씩 나열합니다.
spleen-readers/
├── config.yaml
├── data/
│ └── cases.jsonl
└── media/
├── spleen_3.nii.gz
├── spleen_8.nii.gz
└── spleen_56.nii.gzvolume은 media/ 기준으로 스캔을 지정하고, text는 뷰어 위에 표시되는 캡션입니다.
{"id": "spleen_3", "volume": "spleen_3.nii.gz", "text": "MSD Task09 spleen_3"}
{"id": "spleen_8", "volume": "spleen_8.nii.gz", "text": "MSD Task09 spleen_8"}
{"id": "spleen_56", "volume": "spleen_56.nii.gz", "text": "MSD Task09 spleen_56"}volume 값은 DICOM 시리즈 하나가 든 폴더를 지정할 수도 있습니다. 파일과 폴더 이름은 브라우저에 전달되므로 사례 파일과 폴더에는 가명 이름을 붙이세요.
설정
아래 설정은 완전하며 2.12.1과 2.13.1에서 python -m potato.validate_cli config.yaml --strict를 통과합니다.
annotation_task_name: Spleen segmentation, three readers
task_dir: .
output_annotation_dir: annotation_output/
media_directory: media
data_files:
- data/cases.jsonl
item_properties:
id_key: id
text_key: text
user_config:
allow_all_users: true
users: []
# Every reader segments every case
num_annotators_per_item: 3
annotation_schemes:
- annotation_type: volume_annotation
name: spleen
description: Paint the spleen on every axial slice that shows the spleen
source_field: volume
labels:
- {name: Spleen, color: "#e6553a", key_value: "1"}
# Boxes are saved as separate shapes and never become mask voxels
tools: [brush, eraser]
brush_size: 6
# A NIfTI file has no modality header, so the CT windows are named here
window_presets:
- {name: Abdomen, center: 50, width: 400}
- {name: Liver, center: 30, width: 150}
default_window: Abdomen세 가지 설정에는 이유가 있습니다.
tools: [brush, eraser]. 뷰어에는 상자와 다각형 도구도 있습니다. 볼륨 뷰어의 상자는 별도의 도형으로 저장되며 마스크 복셀이 되지 않고 Dice 점수나 NIfTI 라벨 볼륨에도 반영되지 않습니다. Potato 2.13.1부터는 다각형을 닫으면 그 슬라이스의 마스크에 다각형 영역이 칠해지며, Potato 2.12.1은 다각형을 도형으로 저장했습니다. 이 실행의 판독자들은 브러시를 썼습니다. 탐색(Navigate) 도구는 항상 추가됩니다.brush_size: 6. 브러시 반지름은 복셀 단위이며 기본값은 2이고 슬라이더는 15까지입니다. 이 스캔들의 0.7에서 0.8mm 픽셀에서는 반지름 6이 지름 약 9mm의 원판을 칠합니다.window_presets. Potato는 헤더가 CT라고 밝히는 DICOM 시리즈에만 내장 CT 윈도우를 제공합니다. NIfTI 파일에는 그런 헤더가 없으므로, 프리셋이 없으면 뷰어는 spleen_3을 중심 -380, 폭 1,287의 자동 윈도우로 엽니다. 자동 윈도우는 1,287 하운스필드 단위를 회색조에 펼쳐서 비장과 인접 장기 사이의 대비를 거의 남기지 않습니다. 두 프리셋은 Radiopaedia의 복부 연조직 및 간 윈도우입니다.
스캔 판독
프로젝트 폴더에서 서버를 시작합니다.
potato start config.yaml -p 8000각 판독자는 서버 주소를 열고 사용자 이름과 비밀번호로 등록하며, num_annotators_per_item: 3이 모든 사례를 판독자 세 명에게 주므로 사례 세 개를 모두 받습니다. 뷰어는 환자의 오른쪽이 뷰어의 왼쪽에 오는 방사선학 관례로 축상, 관상, 시상 뷰를 보여 줍니다. 윈도우 메뉴는 Abdomen (50/400)으로 열리며 Liver (30/150)와 자동 윈도우도 나열합니다.
브러시는 b를, Spleen 라벨은 1을 누른 다음 슬라이스마다 비장을 칠합니다. 관상이나 시상 뷰의 획은 그 획이 지나는 모든 축상 슬라이스에 기록되며, 지우개(e)는 복셀을 제거합니다. 모든 슬라이스를 칠할 필요는 없습니다. 비장의 첫 번째와 마지막 축상 슬라이스, 그리고 그 사이에서 윤곽이 바뀌는 몇 슬라이스를 칠한 다음 i를 눌러 Potato의 형태 기반 보간으로 그 사이 슬라이스를 채우세요. 채워진 슬라이스는 일반 마스크 복셀이며, 실행 취소는 채워진 슬라이스를 제거합니다. 다음을 클릭하면 사례가 저장됩니다.
판독을 시작하기 전에, 예를 들어 비장문(splenic hilum)의 혈관이 비장에 속하는지 같은 문서화된 규칙에 합의하고 모든 판독자에게 같은 사본을 주세요. 아래의 일치도 수치는 판독자의 꼼꼼함만큼 그 규칙도 측정합니다. 중간에 추가한 규칙은 앞선 사례들이 다른 규칙으로 판독되게 만듭니다.
이 페이지의 바탕이 된 테스트 실행
이 페이지의 수치는 실행 중인 프로젝트를 거쳐 작업하고 뷰어가 보내는 것과 같은 요청으로 각 사례를 저장한 스크립트 판독자 세 명에게서 나왔습니다. 각 판독자의 마스크는 MSD 전문가 마스크에서 출발해 세 가지 방식 중 하나로 바꾼 것입니다.
- ana는 전문가의 윤곽보다 복셀 하나 안쪽으로 모든 슬라이스를 칠했습니다.
- ben은 첫 슬라이스, 마지막 슬라이스, 그 사이의 세 슬라이스마다 하나를 정확히 칠한 뒤 보간을 눌렀습니다. spleen_3에서 ben은 14개 슬라이스 중 6개를 칠했고 보간이 나머지 8개를 채웠습니다.
- cai는 전문가의 윤곽보다 복셀 세 개 바깥쪽으로 모든 슬라이스를 칠했고 비장의 맨 위와 맨 아래 슬라이스를 놓쳤습니다.
스크립트 판독자 덕분에 결과를 전문가 마스크와 대조해 확인할 수 있으며, 사람의 오류는 스크립트로 만든 세 패턴보다 덜 규칙적입니다.
Potato에서 판독자 간 일치도
Potato의 일치도 보고서는 volume_annotation 마스크를 사례마다, 라벨마다 판독자 쌍별 3D Dice로 점수화합니다. 두 판독자 모두 라벨을 칠하지 않은 쌍은 일치로 세지 않고 그 라벨에서 제외합니다. 브라우저에서 /admin을 열고, 프로젝트 폴더의 admin_api_key.txt에 있는 관리자 키를 입력한 다음 /admin/iaa를 엽니다. 테스트 실행에서 보고서는 판독자 세 명이 모두 있는 사례 세 개를 나열하고 다음을 제시했습니다.
mean_dice 0.887
dice_by_label.Spleen 0.887
n_pairs_by_label.Spleen 90.887은 사례당 세 쌍씩 9개 쌍에 대한 평균입니다. Potato 2.13.1부터 보고서는 판독자가 셋 이상인 각 사례에 STAPLE도 실행하고 각 판독자의 비율을 사례에 걸쳐 평균합니다. 이 페이지의 저장된 마스크에 실행했을 때 2.13.1 보고서는 다음을 추가했습니다.
staple.Spleen.sensitivity ana 0.959 ben 0.939 cai 0.935
staple.Spleen.specificity ana 1.000 ben 0.998 cai 0.944민감도는 아래의 SimpleITK 수치의 평균과 같습니다. 복셀이 2백만 개를 넘는 볼륨에서 Potato는 모든 판독자의 복셀을 둘러싼 상자 안에서 STAPLE을 실행하므로, 특이도는 그 상자 안의 복셀만 세어 SimpleITK의 전체 스캔 수치보다 낮게 나옵니다. 보고서는 쌍별 Dice를 제공하지 않습니다. 내보낸 마스크에서 계산하면 쌍은 0.954(spleen_3의 ana와 ben)에서 0.785(spleen_56의 ben과 cai)에 걸쳤고, cai가 들어간 모든 쌍은 같은 사례에서 ana와 ben의 쌍보다 점수가 낮았습니다. Dice는 우연 보정이 되어 있지 않습니다. Potato의 우연 보정 기하 일치도는 2D 이미지의 상자와 다각형을 다루며 볼륨은 점수화하지 않습니다. reliability 내보내기는 선택 및 평점 질문만 다루며, 이 설정에서 python -m potato.export -c config.yaml -f reliability -o export/는 "No question type with a reliability coefficient"로 멈춥니다.
NIfTI 라벨 볼륨 내보내기
volume_nifti 내보내기는 판독자와 사례마다 라벨 볼륨 하나를 씁니다.
python -m potato.export -c config.yaml -f volume_nifti -o export/export/labels__spleen.json
export/ana/spleen_3__spleen.nii.gz
export/ana/spleen_8__spleen.nii.gz
export/ana/spleen_56__spleen.nii.gz
export/ben/...
export/cai/...각 .nii.gz 파일은 배경이 0이고 Spleen이 1인 uint8 볼륨이며, 원본 스캔의 복셀 순서와 아핀을 따릅니다. 테스트 실행에서 내보낸 모든 볼륨은 그 볼륨이 나온 스캔과 같은 모양과 아핀을 가졌습니다. 따라서 마스크는 스캔과 복셀 단위로 정렬됩니다. labels__spleen.json은 값을 라벨 이름에 대응시킵니다. 스킴에 라벨이 둘 이상일 때 두 라벨이 공유하는 복셀은 스킴 목록에서 뒤에 있는 라벨로 갑니다.
DICOM 시리즈에는 -f dicom_seg가 원본 시리즈를 참조하는 DICOM Segmentation을 씁니다. 릴리스의 CT 팬텀 예시에서 내보내기는 내용 설명에 어노테이터의 사용자 이름이 들어간 이진 SEG 하나를 썼습니다. DICOM 표준은 SEG가 원본 시리즈의 환자 및 검사 모듈을 복사하도록 요구하며, 팬텀의 SEG는 팬텀의 환자 이름을 유지했습니다. 그런 이유로 SEG 내보내기는 식별 가능한 것으로 다루세요. 이 튜토리얼의 NIfTI 스캔에서는 dicom_seg가 파일을 쓰지 않고 volume_nifti를 쓰라는 경고를 출력합니다.
STAPLE 합의와 판독자별 Dice
아래 스크립트는 사례마다 세 판독자의 마스크에 SimpleITK의 STAPLE 필터를 실행하고, 합의 확률이 0.5 이상인 복셀을 남기고, 합의를 판독자의 내보내기 옆에 씁니다. 그런 다음 스크립트는 각 판독자의 STAPLE 민감도와 특이도, 그리고 합의에 대한 각 판독자의 Dice를 출력합니다. pip install SimpleITK로 SimpleITK를 설치하고(테스트 실행은 2.5.6을 썼습니다) 프로젝트 폴더에서 스크립트를 실행하세요.
from pathlib import Path
import SimpleITK as sitk
EXPORT = Path("export")
READERS = sorted(p.name for p in EXPORT.iterdir() if p.is_dir() and p.name != "staple")
CASES = sorted({f.name.split("__")[0] for r in READERS for f in (EXPORT / r).glob("*__spleen.nii.gz")})
(EXPORT / "staple").mkdir(exist_ok=True)
def dice(a, b):
overlap = sitk.LabelOverlapMeasuresImageFilter()
overlap.Execute(a, b)
return overlap.GetDiceCoefficient()
print(f"{'case':10} {'reader':6} {'sens':>6} {'spec':>8} {'dice':>6}")
summary = {}
for case in CASES:
masks = [sitk.ReadImage(str(EXPORT / r / f"{case}__spleen.nii.gz"), sitk.sitkUInt8) for r in READERS]
staple = sitk.STAPLEImageFilter()
staple.SetForegroundValue(1)
probability = staple.Execute(masks)
consensus = sitk.Cast(probability >= 0.5, sitk.sitkUInt8)
consensus.CopyInformation(masks[0])
sitk.WriteImage(consensus, str(EXPORT / "staple" / f"{case}__spleen.nii.gz"))
for reader, mask, sens, spec in zip(READERS, masks, staple.GetSensitivity(), staple.GetSpecificity()):
d = dice(mask, consensus)
summary.setdefault(reader, []).append(d)
print(f"{case:10} {reader:6} {sens:6.3f} {spec:8.5f} {d:6.3f}")
print("mean Dice against STAPLE:", {r: round(sum(v) / len(v), 3) for r, v in summary.items()})case reader sens spec dice
spleen_3 ana 0.966 1.00000 0.984
spleen_3 ben 0.948 0.99997 0.971
spleen_3 cai 0.960 0.99907 0.901
spleen_56 ana 0.951 1.00000 0.980
spleen_56 ben 0.921 0.99998 0.954
spleen_56 cai 0.860 0.99947 0.834
spleen_8 ana 0.960 1.00000 0.981
spleen_8 ben 0.948 0.99993 0.971
spleen_8 cai 0.985 0.99795 0.921
mean Dice against STAPLE: {'ana': 0.982, 'ben': 0.965, 'cai': 0.885}민감도와 Dice를 먼저 읽으세요. 배경 복셀은 spleen_8에서 비장 복셀의 74배, spleen_56에서 378배입니다. 따라서 모든 판독자의 특이도는 0.998 이상으로 반올림되며, 작은 차이도 각 판독자가 비장 밖을 얼마나 칠했는지에 따라 판독자의 순위를 매깁니다. cai는 윤곽 바깥을 칠했고 세 스캔 모두에서 가장 낮은 특이도를 가졌습니다. spleen_56에서 cai의 민감도는 표에서 가장 낮은 0.860이었는데, cai가 놓친 두 슬라이스가 9개 슬라이스에 불과한 비장에서 더 큰 비중을 차지했기 때문입니다. ben의 보간된 마스크는 모든 사례에서 ana와 cai의 중간 점수를 받았습니다.
이 세 스캔에서 STAPLE 합의는 3명 중 2명 다수결과 같은 복셀 집합이었습니다. 판독자가 세 명이고 다른 판독자보다 크게 나쁜 판독자가 없으면, STAPLE의 가중치는 어떤 복셀의 결과도 바꾸지 않았습니다. 이 경우 STAPLE은 판독자별 민감도와 특이도를 더하며, 판독자가 더 많거나 부주의한 판독자가 꼼꼼한 판독자를 수적으로 이길 때는 합의 자체가 투표와 다를 수 있습니다.
전문가 마스크에 대한 합의 확인
MSD 전문가 마스크 덕분에 테스트 실행을 확인할 수 있습니다. 각 판독자의 마스크와 STAPLE 합의를 전문가 마스크에 대한 Dice로 평가했습니다.
| 스캔 | ana | ben | cai | STAPLE 합의 |
|---|---|---|---|---|
| spleen_3 | 0.973 | 0.956 | 0.909 | 0.981 |
| spleen_8 | 0.975 | 0.957 | 0.925 | 0.983 |
| spleen_56 | 0.964 | 0.940 | 0.841 | 0.974 |
합의는 모든 스캔에서 어느 개별 판독자보다 전문가 마스크에 대해 더 높은 점수를 받았습니다. ana는 윤곽 안쪽으로 복셀 하나, cai는 바깥쪽으로 복셀 세 개를 그렸고, 3명 중 2명 합의는 두 윤곽 사이에 놓였습니다. 반대 방향의 스크립트 오류는 합의에 유리하며, 실제 판독자를 대상으로 한 연구는 같은 결과를 주장하기 전에 자체 전문가 기준이 필요합니다.
CT 분할을 위한 다른 도구
3D Slicer는 한 사람이 정밀하게 분할하기 위한 무료 데스크톱 애플리케이션입니다. Segment Editor의 Fill between slices와 Grow from seeds 효과는 몇 개의 칠한 슬라이스나 시드로 분할을 완성하며, SlicerRT 확장의 Segment Comparison 모듈은 두 분할 사이의 Dice와 Hausdorff 거리를 계산합니다. ITK-SNAP은 반자동 능동 윤곽을 갖춘 3D 분할용 무료 데스크톱 애플리케이션이며, 버전 4.4.0에서 AI 기능이 추가되었습니다. MONAI Label은 AI 보조 라벨링과 능동 학습을 위한 Apache-2.0 서버로, 3D Slicer, MITK, OHIF 같은 뷰어에서 사용합니다. OHIF Viewer는 DICOMweb 아카이브에서 검사를 읽고 분할 워크플로를 지원하는 MIT 라이선스 웹 뷰어입니다.
Potato는 같은 사례를 자신이 운영하는 서버의 브라우저로 여러 판독자에게 줄 때, 그리고 연구에 판독자들의 일치도가 필요할 때 알맞습니다. Slicer나 ITK-SNAP의 분할 도구를 선호하는 판독자는 Potato의 NIfTI 내보내기를 그곳에서 열 수 있습니다. 의료 영상 및 임상 텍스트 어노테이션 도구 비교가 이 도구들을 Potato와 더 자세히 비교합니다.
CT 분할에서 Potato의 한계
- Potato에 합의 마스크 없음. Potato 2.13.1은 쌍별 Dice와 각 판독자의 STAPLE 비율을 보고하며, 합의 마스크는 내보낸 마스크에 SimpleITK를 적용해 얻습니다.
- 쌍별 Dice 없음.
/admin/iaa는 모든 쌍에 걸쳐 라벨당 평균 Dice 하나를 제공합니다. 위처럼 내보내기에서 쌍별 수치를 계산하세요. - 볼륨 뷰어에 대화형 AI 도구 없음. 마스크는 브러시, 지우개, 닫힌 다각형, 보간, 항목에 지정된 RTSTRUCT 윤곽에서 나오며, 버전 2.14부터는 연구 자체의 마스크로 학습시킨
volume-unet모델에서도 나옵니다. MONAI Label, ITK-SNAP, OHIF는 대화형 모델 보조 3D 분할을 제공합니다. - 4D 볼륨, 융합 PET/CT, 사선 재구성 없음. 볼륨이 둘 이상인 NIfTI 파일은 거부되며, 사선 획득은 가장 가까운 직교 격자에 표시됩니다.
- 비식별화 없음. Potato는 설정에 나열하지 않은 DICOM 헤더 필드를 판독자에게 숨기며, 파일, 파일 이름, 픽셀은 업로드된 그대로 둡니다.
다음으로 볼 자료
- Potato의 의료 및 임상 어노테이션, Potato의 임상 텍스트 및 영상 기능을 한곳에
- Potato로 하는 의료 영상 어노테이션, DICOM 시리즈, RTSTRUCT 시작 마스크, DICOM 헤더 필터링
- 의료 영상 및 임상 텍스트 어노테이션 도구 비교, 3D Slicer, ITK-SNAP, MONAI Label, OHIF, 유료 플랫폼을 Potato와 나란히
- 내보내기 형식,
volume_nifti,dicom_seg와 다른 형식 - 작업 할당, Potato가 판독자들에게 사례를 분배하는 방법
- 에어갭 배포, 외부로 나가는 경로가 없는 네트워크에서 Potato 실행하기
- 볼륨, 포인트 클라우드, 비디오 모델 학습, Potato 2.14.0의
volume-unet트레이너로 판독자들이 칠한 마스크에 3D U-Net을 학습시키기
참고문헌
- Antonelli, M., Reinke, A., Bakas, S., Farahani, K., Kopp-Schneider, A., Landman, B. A., Litjens, G., Menze, B., Ronneberger, O., Summers, R. M., van Ginneken, B., Bilello, M., Bilic, P., Christ, P. F., Do, R. K. G., Gollub, M. J., Heckers, S. H., Huisman, H., Jarnagin, W. R., et al. (2022). The Medical Segmentation Decathlon. Nature Communications, 13, 4128. https://doi.org/10.1038/s41467-022-30695-9
- Warfield, S. K., Zou, K. H., & Wells, W. M. (2004). Simultaneous truth and performance level estimation (STAPLE): An algorithm for the validation of image segmentation. IEEE Transactions on Medical Imaging, 23(7), 903-921. https://doi.org/10.1109/TMI.2004.828354