# 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