Working on docker containers
Browse files- README.md +17 -2
- docker/preprocess.Dockerfile +1 -1
- poetry.lock +0 -0
- pyment/__init__.py +15 -0
- pyment/models/__init__.py +3 -0
- pyment/models/sfcn/__init__.py +4 -0
- pyment/models/sfcn/sfcn.py +76 -0
- pyment/models/sfcn/sfcn_multi.py +42 -0
- pyproject.toml +9 -0
- scripts/predict_from_fastsurfer_folder.py +27 -0
- sfcn-multi.index +0 -0
README.md
CHANGED
|
@@ -1,9 +1,24 @@
|
|
| 1 |
## Build docker container for preprocessing
|
|
|
|
|
|
|
|
|
|
| 2 |
docker build \
|
| 3 |
-f docker/preprocess.Dockerfile \
|
| 4 |
-t pyment/preprocessing:1.0.0 \
|
| 5 |
-
--build-arg CHECKPOINTS_FOLDER=<path_to_fastsurfer_checkpoints>
|
| 6 |
.
|
|
|
|
| 7 |
|
| 8 |
## Run docker container for preprocessing
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
## Build docker container for preprocessing
|
| 2 |
+
Note that for now, building the container requires a folder called <checkpoints> that contains the FastSurfer segmentation checkpoints
|
| 3 |
+
|
| 4 |
+
```
|
| 5 |
docker build \
|
| 6 |
-f docker/preprocess.Dockerfile \
|
| 7 |
-t pyment/preprocessing:1.0.0 \
|
|
|
|
| 8 |
.
|
| 9 |
+
```
|
| 10 |
|
| 11 |
## Run docker container for preprocessing
|
| 12 |
+
Running the container for preprocessing requires three volumes:
|
| 13 |
+
- Inputs: A folder containing input data. All nifti-files detected in this folder or one of its subfolders will be processed
|
| 14 |
+
- Outputs: A folder where the preprocessed images will be written.
|
| 15 |
+
- Licenses: A folder containing the freesurfer license
|
| 16 |
+
```
|
| 17 |
+
docker run --rm \
|
| 18 |
+
--user $(id -u):$(id -g) \
|
| 19 |
+
--volume <path_to_input>:/input \
|
| 20 |
+
--volume <path_to_ouput>:/output \
|
| 21 |
+
--volume <path_to_licenses>:/licenses \
|
| 22 |
+
--gpus all \
|
| 23 |
+
pyment/preprocessing:1.0.0
|
| 24 |
+
```
|
docker/preprocess.Dockerfile
CHANGED
|
@@ -31,4 +31,4 @@ COPY scripts/preprocess.sh /scripts/preprocess.sh
|
|
| 31 |
CMD ["/bin/sh", "/scripts/preprocess.sh", \
|
| 32 |
"--license", "/licenses/freesurfer.txt", \
|
| 33 |
"--python", "/envs/fastsurfer/bin/python", \
|
| 34 |
-
"/input", "/output"]
|
|
|
|
| 31 |
CMD ["/bin/sh", "/scripts/preprocess.sh", \
|
| 32 |
"--license", "/licenses/freesurfer.txt", \
|
| 33 |
"--python", "/envs/fastsurfer/bin/python", \
|
| 34 |
+
"/input", "/output/fastsurfer"]
|
poetry.lock
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
pyment/__init__.py
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import tomllib
|
| 3 |
+
|
| 4 |
+
def _get_version():
|
| 5 |
+
"""Get version from pyproject.toml"""
|
| 6 |
+
pyproject_path = os.path.join(
|
| 7 |
+
os.path.dirname(__file__), os.pardir, 'pyproject.toml'
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
with open(pyproject_path, 'rb') as f:
|
| 11 |
+
data = tomllib.load(f)
|
| 12 |
+
|
| 13 |
+
return data['project']['version']
|
| 14 |
+
|
| 15 |
+
__version__ = _get_version()
|
pyment/models/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .sfcn import SFCN, MultiTaskSFCN
|
| 2 |
+
|
| 3 |
+
__all__ = ['SFCN', 'MultiTaskSFCN']
|
pyment/models/sfcn/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .sfcn import SFCN
|
| 2 |
+
from .sfcn_multi import MultiTaskSFCN
|
| 3 |
+
|
| 4 |
+
__all__ = ['SFCN', 'MultiTaskSFCN']
|
pyment/models/sfcn/sfcn.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from abc import abstractmethod
|
| 2 |
+
from typing import Any, Callable, Tuple
|
| 3 |
+
|
| 4 |
+
import tensorflow as tf
|
| 5 |
+
from tensorflow.keras.layers import (
|
| 6 |
+
Activation, BatchNormalization, Conv3D, Dropout, GlobalAveragePooling3D,
|
| 7 |
+
GlobalMaxPooling3D, Input, MaxPooling3D, Reshape
|
| 8 |
+
)
|
| 9 |
+
from tensorflow.keras.models import Model
|
| 10 |
+
from tensorflow.keras.regularizers import Regularizer
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class SFCN(Model):
|
| 15 |
+
FILTERS = [32, 64, 128, 256, 256, 64]
|
| 16 |
+
|
| 17 |
+
@abstractmethod
|
| 18 |
+
def construct_prediction_head(
|
| 19 |
+
bottleneck: tf.Tensor,
|
| 20 |
+
name: str
|
| 21 |
+
) -> tf.Tensor:
|
| 22 |
+
pass
|
| 23 |
+
|
| 24 |
+
def __init__(self, *,
|
| 25 |
+
input_shape: Tuple[int] = (224, 192, 224),
|
| 26 |
+
include_top: bool = True,
|
| 27 |
+
pooling: str = 'avg',
|
| 28 |
+
dropout: float = 0.,
|
| 29 |
+
regularizer: Regularizer = None,
|
| 30 |
+
activation: Any = 'relu',
|
| 31 |
+
name: str = 'SFCN'
|
| 32 |
+
):
|
| 33 |
+
self.inputs = Input(input_shape, name=f'{name}_inputs')
|
| 34 |
+
x = Reshape(input_shape + (1,), name=f'{name}_expand-dims')(
|
| 35 |
+
self.inputs
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
for i in range(len(self.FILTERS) - 1):
|
| 39 |
+
x = Conv3D(
|
| 40 |
+
self.FILTERS[i],
|
| 41 |
+
(3, 3, 3),
|
| 42 |
+
padding='SAME',
|
| 43 |
+
activation=None,
|
| 44 |
+
kernel_regularizer=regularizer,
|
| 45 |
+
name=f'{name}_block-{i}_conv'
|
| 46 |
+
)(x)
|
| 47 |
+
x = BatchNormalization(name=f'{name}_block-{i}_norm')(x)
|
| 48 |
+
x = Activation(activation, name=f'{name}_block-{i}_activation')(x)
|
| 49 |
+
x = MaxPooling3D((2, 2, 2), name=f'{name}_block-{i}_pool')(x)
|
| 50 |
+
|
| 51 |
+
x = Conv3D(
|
| 52 |
+
self.FILTERS[-1],
|
| 53 |
+
(1, 1, 1),
|
| 54 |
+
padding='SAME',
|
| 55 |
+
activation=None,
|
| 56 |
+
name=f'{name}_top_conv'
|
| 57 |
+
)(x)
|
| 58 |
+
x = BatchNormalization(name=f'{name}_top_norm')(x)
|
| 59 |
+
x = Activation(activation, name=f'{name}_top_activation')(x)
|
| 60 |
+
|
| 61 |
+
if pooling == 'avg':
|
| 62 |
+
pooling = GlobalAveragePooling3D
|
| 63 |
+
elif pooling == 'max':
|
| 64 |
+
pooling = GlobalMaxPooling3D
|
| 65 |
+
else:
|
| 66 |
+
raise ValueError(f'Unknown pooling layer {pooling}')
|
| 67 |
+
|
| 68 |
+
x = pooling(name=f'{name}_top_pool')(x)
|
| 69 |
+
|
| 70 |
+
self.bottleneck = x
|
| 71 |
+
|
| 72 |
+
if include_top:
|
| 73 |
+
x = Dropout(dropout, name=f'{name}_dropout')(x)
|
| 74 |
+
x = self.construct_prediction_head(x, name=f'{name}_predictions')
|
| 75 |
+
|
| 76 |
+
super().__init__(self.inputs, x)
|
pyment/models/sfcn/sfcn_multi.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from tensorflow import concat, Tensor
|
| 2 |
+
from tensorflow.keras.layers import Activation, BatchNormalization, Dense
|
| 3 |
+
|
| 4 |
+
from .sfcn import SFCN
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class MultiTaskSFCN(SFCN):
|
| 8 |
+
@classmethod
|
| 9 |
+
def construct_prediction_head(
|
| 10 |
+
cls,
|
| 11 |
+
bottleneck: Tensor,
|
| 12 |
+
name: str
|
| 13 |
+
) -> Tensor:
|
| 14 |
+
x = bottleneck
|
| 15 |
+
depths = []
|
| 16 |
+
|
| 17 |
+
for i in range(len(depths)):
|
| 18 |
+
x = Dense(depths[i], activation=None,
|
| 19 |
+
name=f'{name}_dense-{i+1}_dense')(x)
|
| 20 |
+
x = BatchNormalization(name=f'{name}_dense-{i+1}_norm')(x)
|
| 21 |
+
x = Activation('relu', name=f'{name}_dense-{i+1}_activation')(x)
|
| 22 |
+
|
| 23 |
+
heads = [
|
| 24 |
+
Dense(1, activation=None, name=f'{name}_predictions_age'),
|
| 25 |
+
Dense(1, activation='sigmoid', name=f'{name}_predictions_sex'),
|
| 26 |
+
Dense(1, activation='sigmoid',
|
| 27 |
+
name=f'{name}_predictions_handedness'),
|
| 28 |
+
Dense(1, activation=None, name=f'{name}_predictions_bmi'),
|
| 29 |
+
Dense(1, activation=None,
|
| 30 |
+
name=f'{name}_predictions_fluid_intelligence'),
|
| 31 |
+
Dense(1, activation=None, name=f'{name}_predictions_neuroticism')
|
| 32 |
+
]
|
| 33 |
+
heads = [head(x) for head in heads]
|
| 34 |
+
|
| 35 |
+
return concat(heads, axis=-1)
|
| 36 |
+
|
| 37 |
+
def __init__(self, *args,
|
| 38 |
+
pooling: str = 'max',
|
| 39 |
+
name: str = 'SFCNMulti',
|
| 40 |
+
**kwargs
|
| 41 |
+
):
|
| 42 |
+
super().__init__(*args, pooling=pooling, name=name, **kwargs)
|
pyproject.toml
CHANGED
|
@@ -12,6 +12,15 @@ license-files = [
|
|
| 12 |
readme = "README.md"
|
| 13 |
requires-python = ">=3.10.2"
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
[build-system]
|
| 16 |
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
| 17 |
build-backend = "poetry.core.masonry.api"
|
|
|
|
| 12 |
readme = "README.md"
|
| 13 |
requires-python = ">=3.10.2"
|
| 14 |
|
| 15 |
+
[tool.poetry]
|
| 16 |
+
packages = [{include = "pyment"}]
|
| 17 |
+
|
| 18 |
+
[tool.poetry.dependencies]
|
| 19 |
+
python = "^3.10.2"
|
| 20 |
+
tensorflow = "^2.15.0"
|
| 21 |
+
numpy = "^1.24.0"
|
| 22 |
+
protobuf = "^4.25.0"
|
| 23 |
+
|
| 24 |
[build-system]
|
| 25 |
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
| 26 |
build-backend = "poetry.core.masonry.api"
|
scripts/predict_from_fastsurfer_folder.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
|
| 3 |
+
from pyment.models import MultiTaskSFCN
|
| 4 |
+
|
| 5 |
+
def predict_from_fastsurfer_folder(folder: str, output: str, weights: str):
|
| 6 |
+
model = MultiTaskSFCN()
|
| 7 |
+
|
| 8 |
+
if __name__ == '__main__':
|
| 9 |
+
parser = argparse.ArgumentParser(
|
| 10 |
+
'Generates predictions from SFCN multi and a given weights file for '
|
| 11 |
+
'all images in a fastsurfer folder'
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
parser.add_argument('folder', help='Path to fastsurfer folder')
|
| 15 |
+
parser.add_argument(
|
| 16 |
+
'output', help='Path where CSV with predictions are written'
|
| 17 |
+
)
|
| 18 |
+
parser.add_argument('-w', '--weights', help='Path to weights file')
|
| 19 |
+
|
| 20 |
+
args = parser.parse_args()
|
| 21 |
+
|
| 22 |
+
predict_from_fastsurfer_folder(
|
| 23 |
+
folder=args.folder,
|
| 24 |
+
output=args.output,
|
| 25 |
+
weights=args.weights
|
| 26 |
+
)
|
| 27 |
+
|
sfcn-multi.index
ADDED
|
Binary file (8.49 kB). View file
|
|
|