generated from FNNDSC/python-chrisapp-template
-
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
1 parent
2387e07
commit 6087b72
Showing
16 changed files
with
164,140 additions
and
147 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
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 |
---|---|---|
@@ -1,19 +1,14 @@ | ||
# Python version can be changed, e.g. | ||
# FROM python:3.8 | ||
# FROM docker.io/fnndsc/conda:python3.10.2-cuda11.6.0 | ||
FROM docker.io/python:3.11.3-slim-bullseye | ||
FROM docker.io/fnndsc/pl-bestsurfreg-surface-resample:base-1 | ||
|
||
LABEL org.opencontainers.image.authors="FNNDSC <[email protected]>" \ | ||
org.opencontainers.image.title="Surface Data Registration" \ | ||
org.opencontainers.image.description="ChRIS plugin wrapper for bestsurfreg.pl and surface-resample" | ||
|
||
WORKDIR /usr/local/src/pl-bestsurfreg-surface-resample | ||
COPY ./fetal-template-29 $MNI_DATAPATH/fetal-template-29 | ||
|
||
COPY requirements.txt . | ||
RUN pip install -r requirements.txt | ||
|
||
COPY . . | ||
COPY . /usr/local/src/pl-bestsurfreg-surface-resample | ||
ARG extras_require=none | ||
RUN pip install ".[${extras_require}]" | ||
RUN pip install "/usr/local/src/pl-bestsurfreg-surface-resample[${extras_require}]" \ | ||
&& rm -rf /usr/local/src/pl-bestsurfreg-surface-resample | ||
|
||
CMD ["bsr2"] | ||
CMD ["bsrr"] |
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 @@ | ||
* |
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,19 @@ | ||
FROM docker.io/fnndsc/microminc-builder:latest as builder | ||
|
||
RUN microminc.sh -p '&do_cmd' bestsurfreg.pl surface-resample \ | ||
surftracc surface-stats measure_surface_area print_n_polygons \ | ||
/microminc | ||
|
||
FROM python:3.11.2-slim-bullseye | ||
|
||
RUN apt-get update && apt-get install -y perl \ | ||
&& rm -rf /var/lib/apt/lists/* | ||
|
||
COPY --from=builder /microminc /opt/microminc | ||
ENV PATH=/opt/microminc/bin:$PATH \ | ||
LD_LIBRARY_PATH=/opt/microminc/lib:$LD_LIBRARY_PATH \ | ||
MINC_FORCE_V2=1 MINC_COMPRESS=4 VOLUME_CACHE_THRESHOLD=-1 \ | ||
CIVET_JOB_SCHEDULER=DEFAULT \ | ||
MNIBASEPATH=/opt/microminc \ | ||
MNI_DATAPATH=/opt/microminc/share \ | ||
PERL5LIB=/opt/microminc/perl |
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 @@ | ||
Uses [microminc](https://github.com/FNNDSC/microminc) | ||
to create a minimized base image for `pl-bestsurfreg-surface-resample`, | ||
which copies the necessary minc-tool binaries to a multi-arch conda image. | ||
|
||
This image is built and pushed manually. |
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 @@ | ||
#!/bin/bash -ex | ||
|
||
exec docker buildx build --push \ | ||
--platform linux/amd64,linux/arm64,linux/ppc64le \ | ||
-t docker.io/fnndsc/pl-bestsurfreg-surface-resample:base-1 . |
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,16 @@ | ||
""" | ||
Python *ChRIS* plugin module for running ``bestsurfreg.pl`` followed by ``surface-resample``. | ||
""" | ||
|
||
__version__ = '1.0.0' | ||
|
||
DISPLAY_TITLE = r""" | ||
_ _ _ __ __ _ | ||
| | | | | | / _| / _| | | | ||
_ __ | |______| |__ ___ ___| |_ ___ _ _ _ __| |_ _ __ ___ __ _ ______ ___ _ _ _ __| |_ __ _ ___ ___ ______ _ __ ___ ___ __ _ _ __ ___ _ __ | | ___ | ||
| '_ \| |______| '_ \ / _ \/ __| __/ __| | | | '__| _| '__/ _ \/ _` |______/ __| | | | '__| _/ _` |/ __/ _ \______| '__/ _ \/ __|/ _` | '_ ` _ \| '_ \| |/ _ \ | ||
| |_) | | | |_) | __/\__ \ |_\__ \ |_| | | | | | | | __/ (_| | \__ \ |_| | | | || (_| | (_| __/ | | | __/\__ \ (_| | | | | | | |_) | | __/ | ||
| .__/|_| |_.__/ \___||___/\__|___/\__,_|_| |_| |_| \___|\__, | |___/\__,_|_| |_| \__,_|\___\___| |_| \___||___/\__,_|_| |_| |_| .__/|_|\___| | ||
| | __/ | | | | ||
|_| |___/ |_| | ||
""" |
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,70 @@ | ||
#!/usr/bin/env python | ||
import itertools | ||
import os | ||
import sys | ||
import shutil | ||
from concurrent.futures import ThreadPoolExecutor | ||
from pathlib import Path | ||
|
||
from chris_plugin import chris_plugin, PathMapper, curry_name_mapper | ||
|
||
from bestsurfreg import DISPLAY_TITLE | ||
from bestsurfreg.cmd import register_surface_data | ||
from bestsurfreg.find_template import find_template | ||
from bestsurfreg.params import parser | ||
|
||
|
||
@chris_plugin( | ||
parser=parser, | ||
title='Surface Data Registration', | ||
category='MRI', # ref. https://chrisstore.co/plugins | ||
min_memory_limit='1Gi', # supported units: Mi, Gi | ||
min_cpu_limit='1000m', # millicores, e.g. "1000m" = 1 CPU core | ||
min_gpu_limit=0 # set min_gpu_limit=1 to enable GPU | ||
) | ||
def main(options, inputdir: Path, outputdir: Path): | ||
print(DISPLAY_TITLE, flush=True) | ||
if options.copy: | ||
shutil.copytree(inputdir, outputdir) | ||
|
||
try: | ||
template_surface = find_template(options.target, inputdir) | ||
file_to_register = find_template(options.data, inputdir) | ||
except FileNotFoundError as e: | ||
logger.error(str(e)) | ||
sys.exit(1) | ||
|
||
proc = len(os.sched_getaffinity(0)) | ||
print(f'Using {proc} threads', flush=True) | ||
|
||
mapper = PathMapper.file_mapper(inputdir, outputdir, glob=options.pattern, | ||
name_mapper=curry_name_mapper(options.output_fname)) | ||
|
||
with ThreadPoolExecutor(max_workers=proc) as pool: | ||
results = pool.map( | ||
call_register_surface_data, | ||
mapper, | ||
itertools.repeat(template_surface), | ||
itertools.repeat(file_to_register), | ||
itertools.repeat(options) | ||
) | ||
|
||
rc = next(filter(lambda r: r != 0, results), 0) | ||
sys.exit(rc) | ||
|
||
|
||
def call_register_surface_data(t: tuple[Path, Path], template_surface: Path, file_to_register: Path, options) -> int: | ||
input_surface, output_registered_data = t | ||
sm_file = output_registered_data.with_suffix('.sm') | ||
|
||
return register_surface_data( | ||
input_surface, template_surface, file_to_register, sm_file, output_registered_data, | ||
options.min_control_mesh, options.max_control_mesh, options.blur_coef, | ||
options.neighbourhood_radius, options.maximum_blur, | ||
output_registered_data.with_suffix('.bestsurfreg.log'), | ||
output_registered_data.with_suffix('.surface-resample.log') | ||
) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
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,103 @@ | ||
""" | ||
Wrapper functions for external commands. | ||
""" | ||
import shlex | ||
from pathlib import Path | ||
import subprocess as sp | ||
from colorama import Fore, Style | ||
from datetime import datetime | ||
|
||
FAILED = Style.BRIGHT + Fore.RED + 'FAILED' + Fore.RESET + Style.RESET_ALL | ||
|
||
|
||
def register_surface_data( | ||
surface: Path, | ||
template_surface: Path, | ||
file_to_register: Path, | ||
sm_file: Path, | ||
registered_data: Path, | ||
min_control_mesh, | ||
max_control_mesh, | ||
blur_coef, | ||
neighborhood_radius, | ||
maximum_blur, | ||
bestsurfreg_log_file: Path, | ||
surface_resample_log_file: Path | ||
) -> int: | ||
cmds: list[tuple[list[str | Path], Path]] = [ | ||
( | ||
bestsurfreg( | ||
surface, template_surface, sm_file, | ||
min_control_mesh, max_control_mesh, | ||
blur_coef, neighborhood_radius, maximum_blur, | ||
), | ||
bestsurfreg_log_file, | ||
), | ||
( | ||
surface_resample(surface, template_surface, file_to_register, sm_file, registered_data), | ||
surface_resample_log_file | ||
) | ||
] | ||
subject = _subj_log_name(surface) | ||
for cmd, log_file in cmds: | ||
str_cmd = shlex.join(map(str, cmd)) | ||
print(f'{log_prefix(subject)}$> {str_cmd}', flush=True) | ||
with log_file.open('wb') as f: | ||
proc = sp.run(cmd, stdout=f, stderr=sp.STDOUT) | ||
if proc.returncode != 0: | ||
print(f'{log_prefix(subject)} {FAILED}, see {log_file}', flush=True) | ||
return proc.returncode | ||
return 0 | ||
|
||
|
||
def log_prefix(subj) -> str: | ||
now = datetime.now().isoformat() | ||
return f'{Style.DIM }[{now}{Style.RESET_ALL} {subj}{Style.DIM}]{Style.RESET_ALL}' | ||
|
||
|
||
def bestsurfreg( | ||
surface: Path, | ||
target: Path, | ||
sm_file: Path, | ||
min_control_mesh, | ||
max_control_mesh, | ||
blur_coef, | ||
neighborhood_radius, | ||
maximum_blur, | ||
): | ||
return [ | ||
'bestsurfreg.pl', | ||
'-clobber', | ||
'-min_control_mesh', str(min_control_mesh), | ||
'-max_control_mesh', str(max_control_mesh), | ||
'-blur_coef', str(blur_coef), | ||
'-neighbourhood_radius', str(neighborhood_radius), | ||
'-maximum_blur', str(maximum_blur), | ||
surface, | ||
target, | ||
sm_file | ||
] | ||
|
||
|
||
def surface_resample( | ||
surface: Path, | ||
target: Path, | ||
data: Path, | ||
sm_file: Path, | ||
registered_data: Path, | ||
): | ||
return ['surface-resample', '-nearest', surface, target, data, sm_file, registered_data] | ||
|
||
|
||
def _subj_log_name(surface: Path) -> str: | ||
"""Color coding of a subject identifier for terminal log output.""" | ||
s = f'{surface.parent.name}/{surface.name}' | ||
color = _COLORS[hash(s) % len(_COLORS)] | ||
return color + s + Fore.RESET | ||
|
||
|
||
_COLORS = ( | ||
Fore.GREEN, Fore.YELLOW, Fore.BLUE, Fore.MAGENTA, Fore.CYAN, | ||
Fore.LIGHTGREEN_EX, Fore.LIGHTYELLOW_EX, Fore.LIGHTBLUE_EX, | ||
Fore.LIGHTMAGENTA_EX, Fore.LIGHTCYAN_EX, | ||
) |
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,21 @@ | ||
import os | ||
from pathlib import Path | ||
|
||
MNI_DATAPATH = Path(os.getenv('MNI_DATAPATH', None)) | ||
FILE_RESOLUTION_DESCRIPTION = 'Should be any of an absolute path, a relative path, ' \ | ||
'a path relative to the input directory, or a relative path to ' \ | ||
f'MNI_DATAPATH(={MNI_DATAPATH}).' | ||
""" | ||
An English description of how this program will resolve paths to data files. | ||
""" | ||
|
||
|
||
def find_template(p: str | os.PathLike, input_dir: Path) -> Path: | ||
""" | ||
A function which implements the functionality described by the value of ``FILE_RESOLUTION_DESCRIPTION``. | ||
""" | ||
to_check = (Path(p), input_dir / p, MNI_DATAPATH / p) | ||
for resolved_path in to_check: | ||
if resolved_path.is_file(): | ||
return resolved_path | ||
raise FileNotFoundError(f"None of the following paths are a file: {to_check}") |
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,35 @@ | ||
from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser | ||
|
||
from bestsurfreg import __version__ | ||
from bestsurfreg.find_template import FILE_RESOLUTION_DESCRIPTION | ||
|
||
parser = ArgumentParser(description='ChRIS plugin wrapper for bestsurfreg.pl and surface-resample', | ||
formatter_class=ArgumentDefaultsHelpFormatter) | ||
parser.add_argument('-c', '--copy', action='store_true', | ||
help='Copy all input files to output dir.') | ||
parser.add_argument('-p', '--pattern', default='**/*.obj', type=str, | ||
help='input surface file filter glob') | ||
parser.add_argument('-o', '--output_fname', type=str, | ||
default='{}.to_individual.txt', | ||
help='Name for output registered surface data. ' | ||
'{} represents the basename of the input file.') | ||
parser.add_argument('-t', '--target', type=str, | ||
default='fetal-template-29/bh.smoothwm.mni.obj', | ||
help=f'Registration target. {FILE_RESOLUTION_DESCRIPTION}') | ||
parser.add_argument('-d', '--data', type=str, | ||
default='fetal-template-29/bh.mask.1D.dset', | ||
help='Vertex-wise data (which can be a mask) to be registered from the target ' | ||
f'to each input surface. {FILE_RESOLUTION_DESCRIPTION}') | ||
parser.add_argument('--min_control_mesh', type=int, default=80, | ||
help='control mesh must be no less than X nodes...') | ||
parser.add_argument('--max_control_mesh', type=int, default=81920, | ||
help='control mesh must be no greater than X nodes...') | ||
parser.add_argument('-blur_coef', type=float, default=1.25, | ||
help='factor to increase/decrease blurring') | ||
parser.add_argument('--neighbourhood_radius', type=float, default=2.8, | ||
help='neighbourhood radius') | ||
parser.add_argument('--maximum_blur', type=float, default=1.9, | ||
help='specify target spacing') | ||
|
||
parser.add_argument('-V', '--version', action='version', | ||
version=f'%(prog)s {__version__}') |
Oops, something went wrong.