# HG changeset patch # User gregor.m # Date 1606138307 0 # Node ID 1d62de03829da13d8906ea3311f5513d77e37e1e "planemo upload commit c6cd06d44dce1eef9136017289d362f144687dc1" diff -r 000000000000 -r 1d62de03829d README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.md Mon Nov 23 13:31:47 2020 +0000 @@ -0,0 +1,15 @@ +# Galaxy Wrapper for WaveletMovies + +To test the wrapper use `planemo`: + +```bash +# install +conda install planemo + +# run the test(s) +planemo test + +# run a local galaxy instance with the tool installed +planemo serve + +``` diff -r 000000000000 -r 1d62de03829d SpyBOAT.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SpyBOAT.xml Mon Nov 23 13:31:47 2020 +0000 @@ -0,0 +1,222 @@ + + + spyboat + + python $__tool_directory__/cl_wrapper.py --version + $log + + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + save_preprocessed['selection'] == 'Yes' + + + + + + + + + + + + + + + + + + diff -r 000000000000 -r 1d62de03829d __pycache__/output_report.cpython-38.pyc Binary file __pycache__/output_report.cpython-38.pyc has changed diff -r 000000000000 -r 1d62de03829d cl_wrapper.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cl_wrapper.py Mon Nov 23 13:31:47 2020 +0000 @@ -0,0 +1,196 @@ +#!/usr/bin/env python + +## Gets interfaced by Galaxy or bash scripting +import argparse +import sys, os +import logging + +from skimage import io +from numpy import float32 + +import spyboat +import output_report + +logging.basicConfig(level=logging.INFO, stream=sys.stdout, force=True) +logger = logging.getLogger('wrapper') + +# ----------command line parameters --------------- + +parser = argparse.ArgumentParser(description='Process some arguments.') + +# I/O +parser.add_argument('--input_path', help="Input movie location", required=True) +parser.add_argument('--phase_out', help='Phase output file name', required=True) +parser.add_argument('--period_out', help='Period output file name', required=True) +parser.add_argument('--power_out', help='Power output file name', required=True) +parser.add_argument('--amplitude_out', help='Amplitude output file name', required=True) +parser.add_argument('--preprocessed_out', help="Preprocessed-input output file name, 'None'", required=False) + + +# (Optional) Multiprocessing + +parser.add_argument('--ncpu', help='Number of processors to use', + required=False, type=int, default=1) + +# Optional spatial downsampling +parser.add_argument('--rescale', help='Rescale the image by a factor given in %%, None means no rescaling', + required=False, type=int) +# Optional Gaussian smoothing +parser.add_argument('--gauss_sigma', help='Gaussian smoothing parameter, None means no smoothing', required=False, type=float) + +# Wavelet Analysis Parameters +parser.add_argument('--dt', help='Sampling interval', required=True, type=float) +parser.add_argument('--Tmin', help='Smallest period', required=True, type=float) +parser.add_argument('--Tmax', help='Biggest period', required=True, type=float) +parser.add_argument('--nT', help='Number of periods to scan for', required=True, type=int) + +parser.add_argument('--Tcutoff', help='Sinc cut-off period, disables detrending if not set', required=False, type=float) +parser.add_argument('--win_size', help='Sliding window size for amplitude normalization, None means no normalization', + required=False, type=float) + +# Optional masking +parser.add_argument('--masking', help="Set to either 'dynamic', 'fixed' or 'None' which is the default", default='None', required=False, type=str) + +parser.add_argument('--mask_frame', + help="The frame of the input movie to create a fixed mask from, needs masking set to 'fixed'", + required=False, type=int) + + +parser.add_argument('--mask_thresh', help='The threshold of the mask, all pixels with less than this value get masked (if masking enabled).', + required=False, type=float, + default=0) + +# output overview/snapshots +parser.add_argument('--html_fname', help="Name of the html report.", + default='OutputReport.html', required=False, type=str) + +parser.add_argument('--report_img_path', help="For the html report, can be set in Galaxy. Defaults to cwd.", default='.', required=False, type=str) + +parser.add_argument('--version', action='version', version='0.0.1') + +arguments = parser.parse_args() + +logger.info("Received following arguments:") +for arg in vars(arguments): + logger.info(f'{arg} -> {getattr(arguments, arg)}') + +# ------------Read the input---------------------------------------- +try: + movie = spyboat.open_tif(arguments.input_path) +except FileNotFoundError: + logger.critical(f"Couldn't open {arguments.input_path}, check movie storage directory!") + + sys.exit(1) + +# -------- Do (optional) spatial downsampling --------------------------- + +scale_factor = arguments.rescale + +# defaults to None +if not scale_factor: + logger.info('No downsampling requested..') + +elif 0 < scale_factor < 100: + logger.info(f'Downsampling the movie to {scale_factor:d}% of its original size..') + movie = spyboat.down_sample(movie, scale_factor / 100) +else: + raise ValueError('Scale factor must be between 0 and 100!') + +# -------- Do (optional) pre-smoothing ------------------------- +# note that downsampling already is a smoothing operation.. + +# check if pre-smoothing requested +if not arguments.gauss_sigma: + logger.info('No pre-smoothing requested..') +else: + logger.info(f'Pre-smoothing the movie with Gaussians, sigma = {arguments.gauss_sigma:.2f}..') + + movie = spyboat.gaussian_blur(movie, arguments.gauss_sigma) + +# ----- Set up Masking before processing ---- + +mask = None +if arguments.masking == 'fixed': + if not arguments.mask_frame: + logger.critical("Frame number for fixed masking is missing!") + sys.exit(1) + + if (arguments.mask_frame > movie.shape[0]) or (arguments.mask_frame < 0): + logger.critical(f'Requested frame does not exist, input only has {movie.shape[0]} frames.. exiting') + sys.exit(1) + + else: + logger.info(f'Creating fixed mask from frame {arguments.mask_frame} with threshold {arguments.mask_thresh}') + mask = spyboat.create_fixed_mask(movie, arguments.mask_frame, + arguments.mask_thresh) +elif arguments.masking == 'dynamic': + logger.info(f'Creating dynamic mask with threshold {arguments.mask_thresh}') + mask = spyboat.create_dynamic_mask(movie, arguments.mask_thresh) + +else: + logger.info('No masking requested..') + +# ------ Retrieve wavelet parameters --------------------------- + +Wkwargs = {'dt': arguments.dt, + 'Tmin': arguments.Tmin, + 'Tmax': arguments.Tmax, + 'nT': arguments.nT, + 'T_c' : arguments.Tcutoff, # defaults to None + 'win_size' : arguments.win_size # defaults to None +} + +# start parallel processing +results = spyboat.run_parallel(movie, arguments.ncpu, **Wkwargs) + +# --- masking? --- + +if mask is not None: + # mask all output movies (in place!) + for key in results: + logger.info(f'Masking {key}') + spyboat.apply_mask(results[key], mask, fill_value=-1) + +# --- Produce Output Report Figures/png's --- + +# create the directory, yes we have to do that ourselves :) +# galaxy then magically renders the html from that +try: + + if arguments.report_img_path != '.': + logger.info(f'Creating report directory {arguments.report_img_path}') + os.mkdir(arguments.report_img_path) + + # jump to the middle of the movie + snapshot_frame = int(movie.shape[0]/2) + output_report.produce_snapshots(movie, results, snapshot_frame, Wkwargs, img_path=arguments.report_img_path) + + output_report.produce_distr_plots(results, Wkwargs, img_path=arguments.report_img_path) + + output_report.create_html(snapshot_frame, arguments.html_fname) + + +except FileExistsError as e: + logger.critical(f"Could not create html report directory: {repr(e)}") + + +# --- save out result movies --- + +# save phase movie +io.imsave(arguments.phase_out, results['phase'], plugin="tifffile") +logger.info(f'Written {arguments.phase_out}') +# save period movie +io.imsave(arguments.period_out, results['period'], plugin="tifffile") +logger.info(f'Written {arguments.period_out}') +# save power movie +io.imsave(arguments.power_out, results['power'], plugin="tifffile") +logger.info(f'Written {arguments.power_out}') +# save amplitude movie +io.imsave(arguments.amplitude_out, results['amplitude'], plugin="tifffile") +logger.info(f'Written {arguments.amplitude_out}') + +# save out the probably pre-processed (scaled and blurred) input movie for +# direct comparison to results and coordinate mapping etc. +if arguments.preprocessed_out: + io.imsave(arguments.preprocessed_out, movie, plugin='tifffile') + logger.info(f'Written {arguments.preprocessed_out}') diff -r 000000000000 -r 1d62de03829d output_report.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/output_report.py Mon Nov 23 13:31:47 2020 +0000 @@ -0,0 +1,199 @@ +''' Produces plots and a summary html 'headless' ''' + +import os +import matplotlib +# headless plotting and disable latex +matplotlib.use('Agg') +matplotlib.rcParams['text.usetex'] = False +import matplotlib.pyplot as ppl + +import logging + +import spyboat.plotting as spyplot + +logger = logging.getLogger(__name__) + +# figure resolution +DPI=250 + +def produce_snapshots(input_movie, results, frame, Wkwargs, + img_path='.'): + + ''' + Takes the *input_movie* and the + *results* dictionary from spyboat.processing.run_parallel + and produces phase, period and amplitude snapshot png's. + + For the period snapshot also the period range is needed, + hence the analysis dictionary 'Wkwargs' also gets passed. + + The output files name pattern is: + [input, phase, period, amplitude]_frame{frame}.png + and the storage location in *img_path*. + + These get picked up by 'create_html' + ''' + + + spyplot.input_snapshot(input_movie[frame]) + fig = ppl.gcf() + out_path = os.path.join(img_path, f'input_frame{frame}.png') + fig.savefig(out_path, dpi=DPI) + + spyplot.phase_snapshot(results['phase'][frame]) + fig = ppl.gcf() + out_path = os.path.join(img_path, f'phase_frame{frame}.png') + fig.savefig(out_path, dpi=DPI) + + spyplot.period_snapshot(results['period'][frame], + Wkwargs, + time_unit = 'a.u.') + + fig = ppl.gcf() + out_path = os.path.join(img_path, f'period_frame{frame}.png') + fig.savefig(out_path, dpi=DPI) + + spyplot.amplitude_snapshot(results['amplitude'][frame]) + fig = ppl.gcf() + out_path = os.path.join(img_path, f'amplitude_frame{frame}.png') + fig.savefig(out_path, dpi=DPI) + + + logger.info(f'Produced 4 snapshots for frame {frame}..') + +def produce_distr_plots(results, Wkwargs, img_path='.'): + + ''' + Output file names are: + + period_distr.png, power_distr.png and phase_distr.png + ''' + + spyplot.period_distr_dynamics(results['period'], Wkwargs) + fig = ppl.gcf() + out_path = os.path.join(img_path, f'period_distr.png') + fig.savefig(out_path, dpi=DPI) + + spyplot.power_distr_dynamics(results['power'], Wkwargs) + fig = ppl.gcf() + out_path = os.path.join(img_path, f'power_distr.png') + fig.savefig(out_path, dpi=DPI) + + spyplot.phase_coherence_dynamics(results['phase'], Wkwargs) + fig = ppl.gcf() + out_path = os.path.join(img_path, f'phase_distr.png') + fig.savefig(out_path, dpi=DPI) + + logger.info(f'Produced 3 distribution plots..') + + +def create_html(frame_num, html_fname='OutputReport.html'): + + ''' + The html generated assumes the respective png's (7 in total) + have been created with 'produce_snapshots' and 'produce_distr_plots' + and can be found at the cwd (that's how Galaxy works..) + ''' + + html_string =f''' + + SpyBOAT Output Report + + + + + + +

SpyBOAT Results Report

+
+ + +

Snapshots - Frame {frame_num}

+ + + + + + + ''' + + with open(html_fname, 'w') as OUT: + + OUT.write(html_string) + + logger.info(f'Created html report') + return html_string + +# for local testing +# create_html(125) diff -r 000000000000 -r 1d62de03829d run_tests.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/run_tests.sh Mon Nov 23 13:31:47 2020 +0000 @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +# example command, minimal options: no detrending, +# no rescaling, no masking, no amplitude norm., no blurring + +INPUT_PATH='./test-data/test-movie.tif' +# INPUT_PATH='./test-data/SCN_L20_Evans2013-half.tif' +SCRIPT_PATH='.' + +python3 $SCRIPT_PATH/cl_wrapper.py --input_path $INPUT_PATH --phase_out phase_twosines_out.tif --period_out period_twosines_out.tif --power_out power_twosines_out.tif --amplitude_out amplitude_twosines_out.tif --dt .5 --Tmin 20 --Tmax 30 --nT 200 --ncpu 6 --masking dynamic --preprocessed_out preproc_two_sines.tif --gauss_sigma 3 --rescale 50 --Tcutoff 40 --masking fixed --mask_frame 10 --mask_thresh 8 + +printf "\n" +# printf "\nError examples:\n" + +# python3 $SCRIPT_PATH/cl_wrapper.py --input_path $INPUT_PATH --phase_out phase_twosines_out.tif --period_out period_twosines_out.tif --power_out power_twosines_out.tif --amplitude_out amplitude_twosines_out.tif --dt 2. --Tmin 20 --Tmax 30 --nT 200 --ncpu 6 --save_input True --masking fixed diff -r 000000000000 -r 1d62de03829d styles.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/styles.css Mon Nov 23 13:31:47 2020 +0000 @@ -0,0 +1,50 @@ + +body{ margin:10 100; background:whitesmoke; } +/*body{ margin:10 100; background:darkslategrey; }*/ +.center{ + display: block; + margin-left: auto; + margin-right: auto; + width: 40%;} + +/* matplotlib output at 1600x1200 */ +.distr_gallery { + display: grid; + margin: 0 auto; + text-align: center; + /* border: 1px dashed rgba(4, 4, 4, 0.35); */ + grid-template-columns: repeat(3,1fr); + grid-template-rows: 20vw; + grid-gap: 0px; + column-gap: 0px +} +.distr_gallery__img { + width: 100%; + height: 100%; + object-fit: contain; +} + + +/* matplotlib output at 1600x1200 */ +.snapshot_gallery { + display: grid; + margin: 0 auto; + border: 1px dashed rgba(4, 4, 4, 0.35); + text-align: center; + grid-template-columns: repeat(2,1fr); + grid-template-rows: repeat(2,20vw); + grid-gap: 5px; +} +.snapshot_gallery__img { + width: 100%; + height: 100%; + object-fit: contain; +} + +#areaA { + background-color: lime; +} + +#areaB { + background-color: yellow; +} diff -r 000000000000 -r 1d62de03829d test-data/test-movie.tif Binary file test-data/test-movie.tif has changed