-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
jaehwan
committed
May 20, 2024
1 parent
8c6c83b
commit 78340d2
Showing
208 changed files
with
21,970 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
test/ | ||
.git/ | ||
*.tar.gz |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
FROM pytorch/pytorch | ||
|
||
RUN groupadd -r user && useradd -m --no-log-init -r -g user user | ||
|
||
RUN mkdir -p /opt/app /input /output \ | ||
&& chown user:user /opt/app /input /output | ||
|
||
USER user | ||
WORKDIR /opt/app | ||
|
||
ENV PATH="/home/user/.local/bin:${PATH}" | ||
|
||
RUN python -m pip install --user -U pip && python -m pip install --user pip-tools && python -m pip install --upgrade pip | ||
COPY --chown=user:user nnUNet/ /opt/app/nnUNet/ | ||
RUN python -m pip install -e nnUNet | ||
#RUN python -m pip uninstall -y scipy | ||
#RUN python -m pip install --user --upgrade scipy | ||
|
||
COPY --chown=user:user requirements.txt /opt/app/ | ||
RUN python -m pip install --user -r requirements.txt | ||
|
||
|
||
# This is the checkpoint file, uncomment the line below and modify /local/path/to/the/checkpoint to your needs | ||
COPY --chown=user:user nnUNetTrainer__nnUNetPlans__3d_fullres.zip /opt/algorithm/checkpoint/nnUNet/ | ||
RUN python -c "import zipfile; import os; zipfile.ZipFile('/opt/algorithm/checkpoint/nnUNet/nnUNetTrainer__nnUNetPlans__3d_fullres.zip').extractall('/opt/algorithm/checkpoint/nnUNet/')" | ||
|
||
COPY --chown=user:user custom_algorithm.py /opt/app/ | ||
COPY --chown=user:user process.py /opt/app/ | ||
|
||
# COPY --chown=user:user weights /opt/algorithm/checkpoint | ||
ENV nnUNet_results="/opt/algorithm/checkpoint/" | ||
ENV nnUNet_raw="/opt/algorithm/nnUNet_raw_data_base" | ||
ENV nnUNet_preprocessed="/opt/algorithm/preproc" | ||
# ENV ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=64(nope!) | ||
# ENV nnUNet_def_n_proc=1 | ||
|
||
#ENTRYPOINT [ "python3", "-m", "process" ] | ||
|
||
ENV MKL_SERVICE_FORCE_INTEL=1 | ||
|
||
# Launches the script | ||
ENTRYPOINT python -m process $0 $@ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# DMX Solution to HaNSeg Challenge | ||
|
||
The Head and Neck oragan-at-risk CT & MR segmentation challenge. Contribution to the Grand Challenge (MICCAI 2023) | ||
|
||
Challenge URL: **[HaN-Seg 2023 challenge](https://han-seg2023.grand-challenge.org/)** | ||
|
||
This solution is based on: | ||
|
||
- [ANTsPY](https://antspy.readthedocs.io/en/latest/) | ||
- [nnUNetv2](https://github.com/MIC-DKFZ/nnUNet/) | ||
- [Zhack47](https://github.com/Zhack47/HaNSeg-QuantIF) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
#!/usr/bin/env bash | ||
SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" | ||
# docker build --no-cache -t hanseg2023algorithm "$SCRIPTPATH" | ||
docker build -t hanseg2023algorithm_dmx "$SCRIPTPATH" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
import ants | ||
import SimpleITK as sitk | ||
|
||
from evalutils import SegmentationAlgorithm | ||
|
||
import logging | ||
from pathlib import Path | ||
from typing import ( | ||
Optional, | ||
Pattern, | ||
Tuple, | ||
) | ||
|
||
from pandas import DataFrame | ||
from evalutils.exceptions import FileLoaderError, ValidationError | ||
from evalutils.validators import DataFrameValidator | ||
from evalutils.io import ( | ||
ImageLoader, | ||
) | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class HanSegUniquePathIndicesValidator(DataFrameValidator): | ||
""" | ||
Validates that the indicies from the filenames are unique | ||
""" | ||
|
||
def validate(self, *, df: DataFrame): | ||
try: | ||
paths_ct = df["path_ct"].tolist() | ||
paths_mrt1 = df["path_mrt1"].tolist() | ||
paths = paths_ct + paths_mrt1 | ||
except KeyError: | ||
raise ValidationError( | ||
"Column `path_ct` or `path_mrt1` not found in DataFrame." | ||
) | ||
|
||
assert len(paths_ct) == len( | ||
paths_mrt1 | ||
), "The number of CT and MR images is not equal." | ||
|
||
|
||
class HanSegUniqueImagesValidator(DataFrameValidator): | ||
""" | ||
Validates that each image in the set is unique | ||
""" | ||
|
||
def validate(self, *, df: DataFrame): | ||
try: | ||
hashes_ct = df["hash_ct"].tolist() | ||
hashes_mrt1 = df["hash_mrt1"].tolist() | ||
hashes = hashes_ct + hashes_mrt1 | ||
except KeyError: | ||
raise ValidationError( | ||
"Column `hash_ct` or `hash_mrt1` not found in DataFrame." | ||
) | ||
|
||
if len(set(hashes)) != len(hashes): | ||
raise ValidationError( | ||
"The images are not unique, please submit a unique image for " | ||
"each case." | ||
) | ||
|
||
|
||
class Hanseg2023Algorithm(SegmentationAlgorithm): | ||
def __init__( | ||
self, | ||
input_path=Path("/input/images/"), | ||
output_path=Path("/output/images/head_neck_oar/"), | ||
**kwargs, | ||
): | ||
super().__init__( | ||
validators=dict( | ||
input_image=( | ||
HanSegUniqueImagesValidator(), | ||
HanSegUniquePathIndicesValidator(), | ||
) | ||
), | ||
input_path=input_path, | ||
output_path=output_path, | ||
**kwargs, | ||
) | ||
|
||
def _load_input_image(self, *, case) -> Tuple[sitk.Image, Path]: | ||
input_image_file_path_ct = case["path_ct"] | ||
input_image_file_path_mrt1 = case["path_mrt1"] | ||
|
||
input_image_file_loader = self._file_loaders["input_image"] | ||
if not isinstance(input_image_file_loader, ImageLoader): | ||
raise RuntimeError("The used FileLoader was not of subclass ImageLoader") | ||
|
||
# Load the image for this case | ||
|
||
#input_image_ct = input_image_file_loader.load_image(input_image_file_path_ct) | ||
#input_image_mrt1 = input_image_file_loader.load_image( | ||
# input_image_file_path_mrt1 | ||
#) | ||
# Ok so hear me out... | ||
# Instead of loading the nrrd files as SimpleITK images, shifting to ants Image, | ||
# doing the registration, then back to SITK Image, we load them directly with ants | ||
# I did this back when time limit was 5 min, to win seconds | ||
input_image_ct = ants.image_read(input_image_file_path_ct.__str__()) | ||
input_image_mrt1 = ants.image_read(input_image_file_path_mrt1.__str__()) | ||
|
||
# Check that it is the expected image | ||
#if input_image_file_loader.hash_image(input_image_ct) != case["hash_ct"]: | ||
# raise RuntimeError("CT image hashes do not match") | ||
#if input_image_file_loader.hash_image(input_image_mrt1) != case["hash_mrt1"]: | ||
# raise RuntimeError("MR image hashes do not match") | ||
|
||
return ( | ||
input_image_ct, | ||
input_image_file_path_ct, | ||
input_image_mrt1, | ||
input_image_file_path_mrt1, | ||
) | ||
|
||
def process_case(self, *, idx, case): | ||
# Load and test the image for this case | ||
( | ||
input_image_ct, | ||
input_image_file_path_ct, | ||
input_image_mrt1, | ||
input_image_file_path_mrt1, | ||
) = self._load_input_image(case=case) | ||
|
||
# Segment nodule candidates | ||
segmented_nodules = self.predict( | ||
image_ct=input_image_ct, image_mrt1=input_image_mrt1 | ||
) | ||
|
||
# Write resulting segmentation to output location | ||
segmentation_path = self._output_path / input_image_file_path_ct.name.replace( | ||
"_CT", "_seg" | ||
) | ||
self._output_path.mkdir(parents=True, exist_ok=True) | ||
sitk.WriteImage(segmented_nodules, str(segmentation_path), True) | ||
|
||
# Write segmentation file path to result.json for this case | ||
return { | ||
"outputs": [dict(type="metaio_image", filename=segmentation_path.name)], | ||
"inputs": [ | ||
dict(type="metaio_ct_image", filename=input_image_file_path_ct.name), | ||
dict( | ||
type="metaio_mrt1_image", filename=input_image_file_path_mrt1.name | ||
), | ||
], | ||
"error_messages": [], | ||
} | ||
|
||
def _load_cases( | ||
self, | ||
*, | ||
folder: Path, | ||
file_loader: ImageLoader, | ||
file_filter: Optional[Pattern[str]] = None, | ||
) -> DataFrame: | ||
cases = [] | ||
|
||
paths_ct = sorted(folder.glob("ct/*"), key=self._file_sorter_key) | ||
paths_mrt1 = sorted(folder.glob("t1-mri/*"), key=self._file_sorter_key) | ||
|
||
for pth_ct, pth_mr in zip(paths_ct, paths_mrt1): | ||
if file_filter is None or ( | ||
file_filter.match(str(pth_ct)) and file_filter.match(str(pth_mr)) | ||
): | ||
try: | ||
case_ct = file_loader.load(fname=pth_ct)[0] | ||
case_mrt1 = file_loader.load(fname=pth_mr)[0] | ||
new_cases = [ | ||
{ | ||
"hash_ct": case_ct["hash"], | ||
"path_ct": case_ct["path"], | ||
"hash_mrt1": case_mrt1["hash"], | ||
"path_mrt1": case_mrt1["path"], | ||
} | ||
] | ||
except FileLoaderError: | ||
logger.warning( | ||
f"Could not load {pth_ct.name} or {pth_mr.name} using {file_loader}." | ||
) | ||
else: | ||
cases += new_cases | ||
else: | ||
logger.info( | ||
f"Skip loading {pth_ct.name} and {pth_mr.name} because it doesn't match {file_filter}." | ||
) | ||
|
||
if len(cases) == 0: | ||
raise FileLoaderError( | ||
f"Could not load any files in {folder} with " f"{file_loader}." | ||
) | ||
|
||
return DataFrame(cases) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#!/usr/bin/env bash | ||
|
||
./build.sh | ||
|
||
docker save hanseg2023algorithm_dmx | gzip -c > HanSeg2023AlgorithmDMX.tar.gz |
Oops, something went wrong.