generated from FNNDSC/python-chrisapp-template
-
Notifications
You must be signed in to change notification settings - Fork 0
/
lgd_hemis.py
154 lines (121 loc) · 4.83 KB
/
lgd_hemis.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#!/usr/bin/env python
import os
import subprocess as sp
import sys
from argparse import ArgumentParser, Namespace, ArgumentDefaultsHelpFormatter
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Optional
from chris_plugin import chris_plugin, PathMapper
from loguru import logger
__version__ = '1.0.0'
DISPLAY_TITLE = r"""
_ _ _ _ _
| | | | | | | | (_)
_ __ | |______| | __ _ __| |______| |__ ___ _ __ ___ _ ___
| '_ \| |______| |/ _` |/ _` |______| '_ \ / _ \ '_ ` _ \| / __|
| |_) | | | | (_| | (_| | | | | | __/ | | | | | \__ \
| .__/|_| |_|\__, |\__,_| |_| |_|\___|_| |_| |_|_|___/
| | __/ |
|_| |___/
"""
parser = ArgumentParser(description='WM mask extraction for LGD project data',
formatter_class=ArgumentDefaultsHelpFormatter)
parser.add_argument('-p', '--pattern', default='**/*seg*.mnc', type=str,
help='input segmentation files glob')
@chris_plugin(
parser=parser,
title='LGD WM Hemispheres',
category='Surfaces',
min_memory_limit='500Mi',
min_cpu_limit='1000m',
min_gpu_limit=0
)
def main(options: Namespace, inputdir: Path, outputdir: Path):
print(DISPLAY_TITLE, flush=True)
mapper = PathMapper.file_mapper(inputdir, outputdir, glob=options.pattern)
input_files, output_files = zip(*mapper)
proc = len(os.sched_getaffinity(0))
logger.debug('Using {} threads', proc)
with ThreadPoolExecutor(max_workers=proc) as pool:
results = pool.map(lgd_hemis_wrapper, input_files, output_files)
if not all(results):
sys.exit(1)
def lgd_hemis_wrapper(seg: Path, output_path: Path) -> bool:
"""
:returns: True if successful
"""
t2 = find_t2(seg)
if t2 is None:
return False
wm_left = output_path.with_stem('wm_left')
wm_right = output_path.with_stem('wm_right')
logger.info(f'{seg.parent} (segmentation={seg.name} t2={t2.name}) -> {output_path.with_stem("wm_{left,right}")}')
try:
lgd_hemis(seg, t2, wm_left, wm_right)
return True
except sp.CalledProcessError as e:
logger.error(f'{e.cmd} exited with code: {e.returncode}')
return False
def lgd_hemis(seg: Path, t2: Path, wm_left: Path, wm_right: Path) -> None:
"""
Prepares intermediate input files for and then runs ``extract_wm_hemispheres``
"""
with TemporaryDirectory(prefix=seg.parent.name) as tmpdir:
brain_mask = os.path.join(tmpdir, 'brain_mask.mnc')
classified = os.path.join(tmpdir, 'classified.mnc')
create_brain_mask(seg, brain_mask)
regions2classified(seg, classified)
extract_wm_hemispheres(classified, t2, brain_mask, wm_left, wm_right)
def create_brain_mask(seg, output) -> None:
"""
Create whole brain mask.
"""
cmd = ('minccalc', '-quiet', '-unsigned', '-byte', '-expr', 'A[0]>0.5', seg, output)
sp.run(cmd, check=True)
def regions2classified(seg, output) -> None:
"""
Convert Lana's fetal brain region segmentations to the
classified labeled expected by ``extract_wm_hemispheres``
- WM = 3
- GM = 2
- CSF = 1
- BG = 0
"""
cp = 'if(A[0]>111.5&&A[0]<113.5){out=2}'
csf = 'if(A[0]>123.5&&A[0]<124.5){out=1}'
cerebellum = 'if(A[0]>99.5&&A[0]<101.5){out=1}'
midbrain = 'if(A[0]>93.5&&A[0]<95.5){out=1}'
bg = 'if(A[0]<0.5){out=0}'
wm = '{out=3}'
regions = (bg, csf, cerebellum, midbrain, cp, wm)
expr = ' else '.join(regions)
cmd = ('minccalc', '-quiet', '-unsigned', '-byte', '-expr', expr, seg, output)
sp.run(cmd, check=True)
def extract_wm_hemispheres(classified, t1, brain_mask, wm_left, wm_right):
"""
Create white matter masks for left and right hemispheres.
Using t2 as t1 seems to work just fine.
"""
# modified extract_wm_hemispheres script, see base/Dockerfile for details
cmd = ('extract_wm_hemispheres_fetus', classified, t1, brain_mask, 'none', 'none', wm_left, wm_right)
sp.run(cmd, stdout=sp.DEVNULL, stderr=sp.STDOUT, check=True)
def find_t2(seg: Path) -> Optional[Path]:
"""
Every subject directory is assumed to contain exactly two files.
The file which is not the segmentations, is the t2.
"""
glob = seg.parent.glob(f'*{seg.suffix}')
others = filter(lambda p: p.name != seg.name, glob)
first = next(others, None)
second = next(others, None)
if second is not None:
logger.error(f'Too many files in {seg.parent}')
return None
if first is None:
logger.error(f't2 image not found in {seg.parent}')
return None
return first
if __name__ == '__main__':
main()