Skip to content

Commit 50f0953

Browse files
authored
Initial commit
0 parents  commit 50f0953

37 files changed

+1769
-0
lines changed

.flake8

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#########################
2+
# Flake8 Configuration #
3+
# (.flake8) #
4+
#########################
5+
[flake8]
6+
ignore =
7+
# pickle
8+
S301
9+
S403
10+
S404
11+
S603
12+
# Line break before binary operator (flake8 is wrong)
13+
W503
14+
# Ignore the spaces black puts before columns.
15+
E203
16+
# allow path extensions for testing.
17+
E402
18+
DAR101
19+
DAR201
20+
# flake and pylance disagree on linebreaks in strings.
21+
N400
22+
# asserts are ok in test.
23+
S101
24+
exclude =
25+
.tox,
26+
.git,
27+
__pycache__,
28+
docs/conf.py,
29+
build,
30+
dist,
31+
*.pyc,
32+
*.bib,
33+
*.egg-info,
34+
.cache,
35+
.eggs,
36+
data.
37+
src/jaxwt/__init__.py
38+
max-line-length = 120
39+
max-complexity = 20
40+
import-order-style = pycharm
41+
application-import-names =
42+
jaxwt
43+
tests

.github/workflows/md2pdf.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Convert README to PDF
2+
3+
on: [push]
4+
5+
jobs:
6+
convert_via_pandoc:
7+
name: Convert via Pandoc
8+
runs-on: ubuntu-22.04
9+
steps:
10+
- uses: actions/checkout@v2
11+
- run: mkdir output
12+
- uses: maxheld83/pandoc@v2
13+
with:
14+
args: "--pdf-engine=xelatex --output=output/README.pdf README.md"
15+
- uses: actions/upload-artifact@main
16+
with:
17+
name: readme
18+
path: output
19+

.github/workflows/test.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: Tests
2+
3+
on: [ push, pull_request ]
4+
5+
jobs:
6+
lint:
7+
name: Lint
8+
runs-on: ubuntu-latest
9+
strategy:
10+
matrix:
11+
python-version: [3.11.0]
12+
steps:
13+
- uses: actions/checkout@v2
14+
- name: Set up Python ${{ matrix.python-version }}
15+
uses: actions/setup-python@v2
16+
with:
17+
python-version: ${{ matrix.python-version }}
18+
- name: Install dependencies
19+
run: pip install nox
20+
- name: Run flake8
21+
run: nox -s lint
22+
- name: Run mypy
23+
run: nox -s typing

.gitignore

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
.vscode/
2+
.pytest_cache/
3+
tciaDownload/
4+
data/scan_index.pkl
5+
data/gtexport
6+
data/tciaDownload
7+
data/pickled
8+
weights/
9+
runs/
10+
11+
# Byte-compiled / optimized / DLL files
12+
__pycache__/
13+
*.py[cod]
14+
*$py.class
15+
16+
# C extensions
17+
*.so
18+
19+
# Distribution / packaging
20+
.Python
21+
build/
22+
develop-eggs/
23+
dist/
24+
downloads/
25+
eggs/
26+
.eggs/
27+
lib/
28+
lib64/
29+
parts/
30+
sdist/
31+
var/
32+
wheels/
33+
share/python-wheels/
34+
*.egg-info/
35+
.installed.cfg
36+
*.egg
37+
MANIFEST
38+
39+
# PyInstaller
40+
# Usually these files are written by a python script from a template
41+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
42+
*.manifest
43+
*.spec
44+
45+
# Installer logs
46+
pip-log.txt
47+
pip-delete-this-directory.txt
48+
49+
# Unit test / coverage reports
50+
htmlcov/
51+
.tox/
52+
.nox/
53+
.coverage
54+
.coverage.*
55+
.cache
56+
nosetests.xml
57+
coverage.xml
58+
*.cover
59+
*.py,cover
60+
.hypothesis/
61+
.pytest_cache/
62+
cover/
63+
64+
# Translations
65+
*.mo
66+
*.pot
67+
68+
# Django stuff:
69+
*.log
70+
local_settings.py
71+
db.sqlite3
72+
db.sqlite3-journal
73+
74+
# Flask stuff:
75+
instance/
76+
.webassets-cache
77+
78+
# Scrapy stuff:
79+
.scrapy
80+
81+
# Sphinx documentation
82+
docs/_build/
83+
84+
# PyBuilder
85+
.pybuilder/
86+
target/
87+
88+
# Jupyter Notebook
89+
.ipynb_checkpoints
90+
91+
# IPython
92+
profile_default/
93+
ipython_config.py
94+
95+
# pyenv
96+
# For a library or package, you might want to ignore these files since the code is
97+
# intended to run in multiple environments; otherwise, check them in:
98+
# .python-version
99+
100+
# pipenv
101+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
102+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
103+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
104+
# install all needed dependencies.
105+
#Pipfile.lock
106+
107+
# poetry
108+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
109+
# This is especially recommended for binary packages to ensure reproducibility, and is more
110+
# commonly ignored for libraries.
111+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
112+
#poetry.lock
113+
114+
# pdm
115+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
116+
#pdm.lock
117+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
118+
# in version control.
119+
# https://pdm.fming.dev/#use-with-ide
120+
.pdm.toml
121+
122+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
123+
__pypackages__/
124+
125+
# Celery stuff
126+
celerybeat-schedule
127+
celerybeat.pid
128+
129+
# SageMath parsed files
130+
*.sage.py
131+
132+
# Environments
133+
.env
134+
.venv
135+
env/
136+
venv/
137+
ENV/
138+
env.bak/
139+
venv.bak/
140+
141+
# Spyder project settings
142+
.spyderproject
143+
.spyproject
144+
145+
# Rope project settings
146+
.ropeproject
147+
148+
# mkdocs documentation
149+
/site
150+
151+
# mypy
152+
.mypy_cache/
153+
.dmypy.json
154+
dmypy.json
155+
156+
# Pyre type checker
157+
.pyre/
158+
159+
# pytype static type analyzer
160+
.pytype/
161+
162+
# Cython debug symbols
163+
cython_debug/
164+
165+
# PyCharm
166+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
167+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
168+
# and can be added to the global gitignore or merged into this file. For a more nuclear
169+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
170+
.idea/
171+
172+
test1.png
173+
test2.png

README.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Day 13 Medical-Image Segmentation
2+
Medical applications are among the most exciting use cases of image segmentation networks.
3+
In this exercise, you will study the publication
4+
["Towards Patient-Individual PI-RADS v2 Sector Map:
5+
CNN for Automatic Segmentation of Prostatic Zones
6+
from T2-Weighted MRI"](https://www.var.ovgu.de/pub/2019_Meyer_ISBI_Zone_Segmentation.pdf)
7+
by Meyer et al.
8+
9+
10+
1. To get started run
11+
12+
```bash
13+
python ./data/download.py
14+
```
15+
16+
in your terminal. The script will download and prepare the medical scans and domain-expert
17+
annotations for you.
18+
19+
Data loading and resampling work already.
20+
21+
1. #### Find the bounding box roi as described below by finishing the `compute_roi` function.
22+
Once you have obtained the train and test data, you must create a preprocessing pipeline.
23+
Proceed to `src/util.py` and compute the so called region of interest.
24+
Meyer et al. define this region as:
25+
26+
"The images were acquired by two different
27+
types of Siemens 3T MRI scanners (MAGNETOM Trio and Skyra)
28+
with a body coil. The ground truth segmentation of the prostate
29+
zones was created on the axial images with 3D Slicer [19] by a medical
30+
student and subsequently corrected by an expert urologist. All
31+
volumes were resampled to a spacing of 0.5 × 0.5 × 3 mm which
32+
corresponds to the highest in-plane resolution and maintains the rela-
33+
tion of in-plane to inter-plane resolution of the dataset. A bounding
34+
box ROI of the prostate was automatically extracted with help of
35+
sagittal and coronal T2w series: the ROI was defined as the intersecting
36+
volume of the three MR sequences."
37+
38+
See wikipedia's [anatomical plane](https://en.wikipedia.org/wiki/Anatomical_plane) article for a description of the terminology.
39+
The plots below depict the situation for the 0004 scans:
40+
41+
![roi3d](./fig/roi3d.png)
42+
![xy](./fig/xy.png)
43+
![yz](./fig/yz.png)
44+
45+
After computing the intersection of all tensors, we can consider i.e. slice 12 of the
46+
transversal scan:
47+
48+
![roi](./fig/roi.png)
49+
50+
Your implementation needs to translate the array indices from local into global coordinate systems and back.
51+
In other words, we require a rotation and translation, or more formally
52+
53+
$$ \mathbf{R}\mathbf{x} + \mathbf{o} = \mathbf{g} .$$
54+
55+
With a rotation matrix $\mathbf{R} \in \mathbb{R}^{3,3}$, the local coordinate vector $\mathbf{x \in \mathbb{R}^{3}}$, the offset $\mathbf{o} \in \mathbb{R}^{3}$, and the global coordinate line $\mathbf{g}$.
56+
Evaluate this transform for every coordinate box line. Use the `box_lines` function from the
57+
`util.py` module to generate a bounding box at the origin. All points in every line must be transformed using the above relationship.
58+
59+
The region of interest is the overlap of all boxes in the global coordinate system. Use [np.amin](https://numpy.org/doc/stable/reference/generated/numpy.amin.html) and [np.amax](https://numpy.org/doc/stable/reference/generated/numpy.amax.html) to find roi-box points $\mathbf{r} \in \mathbb{R}^{3}$.
60+
61+
To obtain array indices, transform all box points back into the local system. Or, more formally:
62+
63+
$$ \mathbf{R}^{-1} \mathbf{r} - \mathbf{o} = \mathbf{x}_{\text{roi}} $$
64+
65+
With the inverse of the rotation matrix $\mathbf{R}^{-1}$ use [np.linalg.inv](https://numpy.org/doc/stable/reference/generated/numpy.linalg.inv.html) to compute it. $\mathbf{x}_{\text{roi}} \in \mathbb{R}^{3}$ is a point on the boundary of the local roi-box we seek.
66+
Transform all boundary points.
67+
68+
Using the smallest and largest coordinate values of the roi box in
69+
local coordinates now allows array indexing. Following Meyer et al. we discard all but the axial `t2w` scans.
70+
71+
Test your implementation by setting the if-condition wrapping the plotting utility in `compute_roi` to `True` and running vscode pytest `test_roi`. Remember to set it back to `False` afterwards.
72+
73+
2. #### Implement the UNet.
74+
75+
Navigate to the `train.py` module file in the `src` folder.
76+
Finish the `UNet3D` class, as discussed in the lecture.
77+
Use the [flax.linen.Conv](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/_autosummary/flax.linen.Conv.html), [flax.linen.relu](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/_autosummary/flax.linen.activation.relu.html), and [flax.linen.ConvTranspose](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/_autosummary/flax.linen.ConvTranspose.html), to build your model.
78+
79+
3. #### Implement the focal-loss
80+
81+
Open the `util.py` module in `src` and implement the `softmax_focal_loss` function as discussed in the lecture:
82+
83+
$$\mathcal{L}(\mathbf{o},\mathbf{I})=-\mathbf{I}\cdot(1-\sigma_s(\mathbf{o}))^\gamma\cdot\alpha\cdot\ln(\sigma_s(\mathbf{o})) $$
84+
85+
with output logits $\mathbf{o}$, the corresponding labels $\mathbf{I}$ and the softmax function $\sigma_s$.
86+
87+
4. #### Run and test the training script.
88+
89+
Execute the training script with by running `scripts/train.slurm` (locally or using `sbatch`).
90+
91+
After training you can test your model by changing the `checkpoint_name` variable in `src/sample.py` to the desired model checkpoint and running `scripts/test.slurm`.
92+
93+
#### Solution:
94+
![slice](./fig/prostatext2.png)
95+
![slice](./fig/prostatext2_net.png)
96+
![slice](./fig/prostatext2_true.png)
97+
98+
5. #### (Optional) Implement mean Intersection-over-Union (mIoU)
99+
100+
Open the `meanIoU.py` in `src` and implement the `compute_iou` function as discussed below.
101+
mIoU is the most common metric used for evaluating semantic segmentation tasks. It can be computed using the values from a confusion matrix as given below
102+
103+
$$\text{mIoU} = \frac{1}{k} \sum_{c=0}^{k}\frac{TP_c}{TP_c+FP_c+FN_c}$$
104+
105+
where `k`, `TP`, `FP`, and `FN` are number of classes, True Positives, False Positives and False Negatives respectively.
106+
The mIoU value generally ranges from 0 to 1 with 0 means no intersection area between predicted segmentation map and ground truth map and 1 means its a perfect fit between these two.
107+
Generally any $\text{mIoU}>0.5$ is considered better.
108+
109+
Run the script with
110+
```bash
111+
python -m src.meanIoU
112+
```
113+
114+
### Acknowledgments:
115+
We thank our course alumni Barbara Wichtmann, for bringing this problem to our attention.
116+
Without her feedback, this code would not exist.
117+

README.pdf

674 KB
Binary file not shown.

0 commit comments

Comments
 (0)