From ab7cbbaf39f2892976746ccdbfddf33b926c11f6 Mon Sep 17 00:00:00 2001 From: Jennings Zhang Date: Fri, 9 Dec 2022 04:23:41 -0500 Subject: [PATCH] Update python wrapper --- README.md | 42 ++++++++++++++++++++- ep_surface_fit.py | 88 +++++++++++++++++++++++++------------------ setup.py | 2 +- surface_fit_script.pl | 6 +-- 4 files changed, 96 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index a7169ac..f1b39cf 100644 --- a/README.md +++ b/README.md @@ -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 + +... diff --git a/ep_surface_fit.py b/ep_surface_fit.py index 3ca65fd..b6af37f 100644 --- a/ep_surface_fit.py +++ b/ep_surface_fit.py @@ -18,16 +18,16 @@ DISPLAY_TITLE = r""" - __ __ _ _ - / _| / _(_) | - ___ _ _ _ __| |_ __ _ ___ ___ | |_ _| |_ -/ __| | | | '__| _/ _` |/ __/ _ \ | _| | __| -\__ \ |_| | | | || (_| | (_| __/ | | | | |_ -|___/\__,_|_| |_| \__,_|\___\___| |_| |_|\__| - ______ - |______| + __ __ _ _ + / _| / _(_) | + ___ _ __ ______ ___ _ _ _ __| |_ __ _ ___ ___ | |_ _| |_ + / _ \ '_ \______/ __| | | | '__| _/ _` |/ __/ _ \ | _| | __| +| __/ |_) | \__ \ |_| | | | || (_| | (_| __/ | | | | |_ + \___| .__/ |___/\__,_|_| |_| \__,_|\___\___| |_| |_|\__| + | | ______ + |_| |______| - Parameterized batch experiment + Parameterized batch experiment """ @@ -35,21 +35,28 @@ 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', ) @@ -57,22 +64,34 @@ 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)) @@ -80,19 +99,16 @@ def main(options: Namespace, inputdir: Path, outputdir: Path): 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: @@ -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) diff --git a/setup.py b/setup.py index aeebd0c..4269119 100644 --- a/setup.py +++ b/setup.py @@ -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='Jennings.Zhang@childrens.harvard.edu', diff --git a/surface_fit_script.pl b/surface_fit_script.pl index 12f1e4b..48f6bdd 100755 --- a/surface_fit_script.pl +++ b/surface_fit_script.pl @@ -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"; @@ -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)"],