Skip to content

Commit

Permalink
Update python wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
jennydaman committed Dec 9, 2022
1 parent 13d1e5b commit ab7cbba
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 42 deletions.
42 changes: 40 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,43 @@
[![ci](https://github.com/FNNDSC/ep-surface_fit_parameterized/actions/workflows/ci.yml/badge.svg)](https://github.com/FNNDSC/ep-surface_fit_parameterized/actions/workflows/ci.yml)

`ep-surface_fit_parameterized` is a [_ChRIS_](https://chrisproject.org/) plugin
for experimenting with `surface_fit` (ASP algorithm from CIVET).
This plugin is not intended for general use.
for experimenting with the parameters of `surface_fit` (ASP algorithm from CIVET).

## Usage

`surface_fit_script.pl` is a Perl wrapper for `surface_fit`.
`ep_surface_fit(.py)` is a Python script for running `surface_fit_script.pl`
as a _ChRIS_ _ds_-plugin on multiple subjects.

`ep_surface_fit` processes every laplacian grid (`*.mnc`) + starting surface (`*.obj`)
pair found in its input directory. For every `*.mnc` file found, `ep_surface_fit` will
search for a `*.obj` surface file in the same directory to use as a starting surface.

When multiple inputs are found, they are processed in parallel.

### Parameters

Multiple stages of `surface_fit` can be run by specifying multiple values
as a comma-separated list.
If some parameter values are given as CSV whereas others are given as singular,
the singular value is reused for later iterations. Example:

```shell
ep_surface_fit --iter-outer 100,100,400 --stretch-weight 80,60,40 --laplacian-weight 1e-4 ...
```

The schedule is interpreted as:

1. 100 iterations with sw=80 lw=1e-4
2. 100 iterations with sw=60 lw=1e-4
3. 400 iterations with sw=80 lw=1e-4

#### `--size`

Number of triangles in the surface mesh, i.e. resolution

- 20480 improves performance and is more suitable for fetal brains 20-28 GA
- 81920 is standard
- 327680 is used for high-resolution adult human brain

...
88 changes: 52 additions & 36 deletions ep_surface_fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,81 +18,97 @@


DISPLAY_TITLE = r"""
__ __ _ _
/ _| / _(_) |
___ _ _ _ __| |_ __ _ ___ ___ | |_ _| |_
/ __| | | | '__| _/ _` |/ __/ _ \ | _| | __|
\__ \ |_| | | | || (_| | (_| __/ | | | | |_
|___/\__,_|_| |_| \__,_|\___\___| |_| |_|\__|
______
|______|
__ __ _ _
/ _| / _(_) |
___ _ __ ______ ___ _ _ _ __| |_ __ _ ___ ___ | |_ _| |_
/ _ \ '_ \______/ __| | | | '__| _/ _` |/ __/ _ \ | _| | __|
| __/ |_) | \__ \ |_| | | | || (_| | (_| __/ | | | | |_
\___| .__/ |___/\__,_|_| |_| \__,_|\___\___| |_| |_|\__|
| | ______
|_| |______|
Parameterized batch experiment
Parameterized batch experiment
"""

parser = ArgumentParser(description='surface_fit wrapper',
formatter_class=ArgumentDefaultsHelpFormatter)
parser.add_argument('--no-fail', dest='no_fail', action='store_true',
help='Produce exit code 0 even if any subprocesses do not.')
parser.add_argument('--sw', type=int, default=200, help='stretch weight')
parser.add_argument('--lw', type=float, default=5e-6, help='laplacian weight')
parser.add_argument('--iter', type=int, default=600, help='iterations')
parser.add_argument('--resize', type=float, default=1.0, help='linear scaling')
parser.add_argument('--step-increment', type=float, default=0.20, help='step increment')


parser.add_argument('-V', '--version', action='version',
version=f'%(prog)s {__version__}')

parser.add_argument('--size', type=str, default='81920', help='number of polygons')
parser.add_argument('--stretch-weight', type=str, default='100', help='stretch weight')
parser.add_argument('--laplacian-weight', type=str, default='5e-6', help='laplacian weight')
parser.add_argument('--iter-outer', type=str, default='1000', help='total number of iterations per stage')
parser.add_argument('--iter-inner', type=str, default='50', help='save every few iterations')
parser.add_argument('--iso-value', type=str, default='10',
help='Chamfer value of laplacian map indicating mask boundary (i.e. target value)')
parser.add_argument('--step-size', type=str, default='0.10', help='Step size per iteration')
parser.add_argument('--oversample', type=str, default='0', help='subsampling (0=none, n=#points extra along edge)')
parser.add_argument('--self-dist', type=str, default='0.01', help='distance to check for self-intersection')
parser.add_argument('--self-weight', type=str, default='1.0', help='weight for self-intersection constraint')
parser.add_argument('--taubin', type=str, default='0',
help='iterations of taubin smoothing to perform between cycles of surface_fit')


@chris_plugin(
parser=parser,
title='surface_fit experiment',
category='Experimental',
category='Experiment',
min_memory_limit='1Gi',
min_cpu_limit='1000m',
)
def main(options: Namespace, inputdir: Path, outputdir: Path):
print(DISPLAY_TITLE, file=sys.stderr, flush=True)

params = [
'-lw',
str(options.lw),
'-size',
options.size,
'-sw',
str(options.sw),
'-iter',
str(options.iter),
'-resize',
str(options.resize),
'-si',
str(options.step_increment)
options.stretch_weight,
'-lw',
options.laplacian_weight,
'-iter-outer',
options.iter_outer,
'-iter-inner',
options.iter_inner,
'-iso-value',
options.iso_value,
'-step-size',
options.step_size,
'-oversample',
options.oversample,
'-self-dist',
options.self_dist,
'-self-weight',
options.self_weight,
'-taubin',
options.taubin
]

nproc = len(os.sched_getaffinity(0))
logger.info('Using {} threads.', nproc)

mapper = PathMapper.file_mapper(inputdir, outputdir, glob='**/*.mnc')
mapper = PathMapper.file_mapper(inputdir, outputdir, glob='**/*.mnc', suffix='.obj')
with ThreadPoolExecutor(max_workers=nproc) as pool:
results = pool.map(lambda t, p: run_surface_fit(*t, p), mapper, itertools.repeat(params))

if not options.no_fail and not all(results):
sys.exit(1)


def run_surface_fit(mask: Path, output_mask: Path, params: list[str]) -> bool:
def run_surface_fit(grid: Path, output_surf: Path, params: list[str]) -> bool:
"""
:return: True if successful
"""
surface = locate_surface_for(mask)
if surface is None:
logger.error('No starting surface found for {}', mask)
starting_surface = locate_surface_for(grid)
if starting_surface is None:
logger.error('No starting surface found for {}', grid)
return False

if mask != output_mask:
shutil.copy(mask, output_mask)
output_surf = output_mask.with_suffix('._81920.obj')
cmd = ['surface_fit_script.pl', *params, output_mask, surface, output_surf]
cmd = ['surface_fit_script.pl', *params, grid, starting_surface, output_surf]
log_file = output_surf.with_name(output_surf.name + '.log')
logger.info('Starting: {}', ' '.join(map(str, cmd)))
with log_file.open('wb') as log_handle:
Expand All @@ -101,7 +117,7 @@ def run_surface_fit(mask: Path, output_mask: Path, params: list[str]) -> bool:
rc_file.write_text(str(job.returncode))

if job.returncode == 0:
logger.info('Finished: {} -> {}', mask, output_surf)
logger.info('Finished: {} -> {}', starting_surface, output_surf)
return True

logger.error('FAILED -- check log file for details: {}', log_file)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name='ep_surface_fit',
version='0.3.0',
version='0.4.0',
description='surface_fit wrapper',
author='Jennings Zhang',
author_email='[email protected]',
Expand Down
6 changes: 3 additions & 3 deletions surface_fit_script.pl
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
my $given_size = "81920";
my $given_sw = "100";
my $given_lw = "5e-6";
my $given_iter_outer = "100";
my $given_iter_outer = "1000";
my $given_iter_inner = "50";
my $given_iso = "10";
my $given_si = "0.10";
Expand All @@ -65,8 +65,8 @@
['-size', 'string', 1, \$given_size, "number of polygons"],
['-sw', 'string', 1, \$given_sw, "stretch weight"],
['-lw', 'string', 1, \$given_lw, "laplacian weight"],
['-iter-outer', 'string', 1, \$given_iter_outer, "outer loop iterations"],
['-iter-inner', 'string', 1, \$given_iter_inner, "inner loop iterations"],
['-iter-outer', 'string', 1, \$given_iter_outer, "total number of iterations per stage"],
['-iter-inner', 'string', 1, \$given_iter_inner, "save every few iterations"],
['-iso-value', 'string', 1, \$given_iso, "Chamfer value of laplacian map indicating mask boundary (i.e. target value)"],
['-step-size', 'string', 1, \$given_si, "Step size per iteration"],
['-oversample', 'string', 1, \$given_subsample, "do subsampling (0=none, n=#points extra along edge)"],
Expand Down

0 comments on commit ab7cbba

Please sign in to comment.