Skip to content

Commit

Permalink
initial
Browse files Browse the repository at this point in the history
  • Loading branch information
jaehwan committed May 20, 2024
1 parent 8c6c83b commit 78340d2
Show file tree
Hide file tree
Showing 208 changed files with 21,970 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
test/
.git/
*.tar.gz
42 changes: 42 additions & 0 deletions Dockerfile
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 $@
13 changes: 13 additions & 0 deletions README.md
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)


4 changes: 4 additions & 0 deletions build.sh
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"
195 changes: 195 additions & 0 deletions custom_algorithm.py
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)
5 changes: 5 additions & 0 deletions export.sh
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
Loading

0 comments on commit 78340d2

Please sign in to comment.