plot.py


Module for map and graph plotting.


Requires Python packages/modules:

Imports Core class.


class streamlines.plot.Plot(state, imported_parameters, geodata, preprocess, trace, analysis, mapping)

Plot maps and distributions.

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
__init__(state, imported_parameters, geodata, preprocess, trace, analysis, mapping)
Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
_force_display(fig)

TBD

_new_figure(title=None, window_title=None, pdf=False, x_pixel_scale=1, y_pixel_scale=1, window_size_factor=None, projection=None)
Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
_record_fig(fig_name, fig)
Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
do()

Display all output

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
static generate_hillshade(array, azimuth_degrees, angle_altitude_degrees)

Hillshade render DTM topo with light from direction azimuth_degrees and tilt angle_attitude_degrees

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_aspect(window_size_factor=None, cmap=None, do_plot_contours=None, contour_label_suffix=None, contour_label_fontsize=None)

Streamlines, points on semi-transparent shaded relief

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_blockages_overlay(axes, color='k', shape='s')

Blocked zone pixels

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_channels(window_size_factor=None, do_reset_mask=True)

Streamlines, points on semi-transparent shaded relief

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_classical_streamlines(window_size_factor=None)

Classic streamlines on color shaded relief

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_classical_streamlines_and_vectors(window_size_factor=None, sl_color=None, vec_color=None, vec_alpha=None, vec_scale=None)

Classic streamlines and gradient vector field on color shaded relief

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_classical_streamlines_overlay(axes, sl_color='blue')

Classic streamlines (overlay method)

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_compound_markers(axes, flag, colors, msf=1.0)

Streamlines, points on semi-transparent shaded relief

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_contours_overlay(axes, Z, mask=None, n_contours=None, contour_interval=None, linewidth=None, contour_label_suffix='m', contour_label_fontsize=12)

Streamlines, points on semi-transparent shaded relief

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_distributions()

Plot probability distributions of processed streamline data.

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_dtm_shaded_relief(window_size_factor=None)

Hillshade view of source DTM

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_flow_maps(window_size_factor=None)

Streamlines, points on semi-transparent shaded relief

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_gradient_vector_field(window_size_factor=None, vec_color='purple', vec_alpha=0.5, vec_scale=30)

Topographic gradient vector field on color shaded relief

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_gradient_vector_field_overlay(axes, vec_color='purple', vec_alpha=0.5, vec_scale=30)

Topographic gradient vector field (overlay method)

Args:
TBD (TBD):

TBD

Returns:
TBD: TBD
plot_gridded_data(grid_array, gridded_cmap, window_size_factor=None, fig_name=None, mask_array=None, window_title='', do_flip_cmap=False, do_balance_cmap=True, do_shaded_relief=True, do_colorbar=False, colorbar_title=None, colorbar_aspect=0.07, colorbar_size=4, grid_alpha=None, extent=None)

Streamlines, points on semi-transparent shaded relief

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_hsl(cmap=None, window_size_factor=None, z_min=None, z_max=None, z_pctile=None, do_shaded_relief=None, colorbar_size=None, colorbar_aspect=None, grid_alpha=None)

Streamlines, points on semi-transparent shaded relief

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_hsl_aspect_distribution(window_size_factor=None, cmap=None)

Streamlines, points on semi-transparent shaded relief

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_hsl_contoured(window_size_factor=None, cmap=None, do_colorbar=True, colorbar_title='hillslope length [m]', n_contours=None, contour_interval=None, linewidth=None, z_min=None, z_max=None, z_pctile=None, do_shaded_relief=None, colorbar_size=None, colorbar_aspect=None, contour_label_suffix=None, contour_label_fontsize=None, do_plot_contours=None)

Streamlines, points on semi-transparent shaded relief

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_hsl_distributions(x_stretch=None)

Streamlines, points on semi-transparent shaded relief

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_joint_pdf(bivariate_distribution, mx_distbn=None, my_distbn=None, fig_name=None, title='', swap_xy=False, do_nl_trend=False, x_label='', y_label='', xsym_label='$L_m^{*}$', ysym_label='$\\sqrt{A_e^*}$', do_plot_mode=True, do_overlay_histogram=False, do_legend=True)

TBD

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_joint_pdf_dsla_dslc()

TBD

plot_joint_pdf_dsla_dslt()

TBD

plot_joint_pdf_dsla_usla(do_legend=False)

TBD

plot_joint_pdf_dslt_dsla(do_legend=True)

TBD

plot_joint_pdf_dslt_dslc()

TBD

plot_joint_pdf_usla_uslc()

TBD

plot_joint_pdf_usla_uslt()

TBD

plot_joint_pdf_uslc_dslc()

TBD

plot_joint_pdf_uslt_dslt(swap_xy=False, do_legend=False)

TBD

plot_loops_overlay(axes, color='k', shape='o')

Loop zone pixels

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_maps()

Plot maps

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_marginal_pdf(marginal_distbn, fig_name=None, title='', x_label='', y_label='')

TBD

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_marginal_pdf_dsla()

TBD

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_marginal_pdf_dslc()

TBD

plot_marginal_pdf_dslt()

TBD

plot_marginal_pdf_usla()

TBD

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_marginal_pdf_uslc()

TBD

plot_marginal_pdf_uslt()

TBD

plot_midslope_elevations_pdf()

Plot distribution of midslope as kernel-smoothed pdf

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_roi_shaded_relief(interp_method=None, window_size_factor=None)

Hillshade view of ROI of DTM

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_roi_shaded_relief_overlay(axes, do_plot_color_relief=None, color_alpha=None, hillshade_alpha=None, interp_method=None)

Hillshade view of ROI of DTM (overlay method)

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_seed_points_overlay(axes, color='k', marker=', ', size=2)

Seed points

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_segments(do_shaded_relief=True, window_size_factor=None)

Streamlines, points on semi-transparent shaded relief

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_simple_grid(grid_array, mask_array, axes, cmap='Blues', alpha=0.8, do_vlimit=True, v_min=None, v_max=None)

Streamlines, points on semi-transparent shaded relief

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_slope_angles_pdf()

Plot distribution of slope angles as kernel-smoothed pdf

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_streamlines(window_size_factor=None)

Streamlines, points on semi-transparent shaded relief

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
plot_updownstreamlines_overlay(axes, do_down=True)

Up or downstreamlines

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
static show()
Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD
streamlines.plot.random_colormap(cmap_name='randomized', n_colors=1000, random_seed=1)

Create a cmap with a randomized sequence of bright colors

Parameters:TBD (TBD) –

TBD

Returns:TBD
Return type:TBD

Code

"""
---------------------------------------------------------------------

Module for map and graph plotting.

---------------------------------------------------------------------

Requires Python packages/modules:
  -  :mod:`random`
  -  :mod:`matplotlib.pyplot`
  -  :mod:`matplotlib.gridspec`
  -  :mod:`matplotlib.ticker`
  -  :mod:`matplotlib.colors`
  -  :mod:`matplotlib.patches`
  -  :mod:`mpl_toolkits.axes_grid1 <mpl_toolkits.axes_grid1.axes_divider>`
  -  :mod:`colorsys`
  -  :mod:`scipy.interpolate`
  -  :mod:`scipy.stats`
  -  :mod:`scipy.signal`

Imports :class:`.Core` class.

---------------------------------------------------------------------

.. _matplotlib: https://matplotlib.org/
.. _scipy: https://www.scipy.org/
.. _random: https://docs.python.org/3/library/random.html
.. _mpl_toolkits: https://matplotlib.org/mpl_toolkits/index.html
.. _colorsys: https://docs.python.org/3/library/colorsys.html

"""
# Notes on potentially useful mpl:
#  plt.style.use('classic')
#  axes[0].axhline,axes[0].axvline
#  data=data obj dict
#  cax=fig.add_axes to add custom color bar; cax=cax in fig.colorbar
#  color map range: vmin, vmax; also norm
#  Use fig, (axes1,axes2) =  to access multiple axes 
#  CartoPy
#  mpl_toolkits.axes_grid1

import numpy as np
from   numpy  import pi, arctan, arctan2, sin, cos, sqrt
from   random import shuffle, seed
import matplotlib as mpl
import matplotlib.pyplot   as plt
import matplotlib.gridspec as gridspec
import matplotlib.ticker   as ticker
from   matplotlib.pyplot   import streamplot
from   matplotlib.colors   import LinearSegmentedColormap
from   matplotlib.patches  import ArrowStyle, FancyArrowPatch
from   mpl_toolkits.axes_grid1 import make_axes_locatable
# from   mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable
import colorsys
from   scipy.interpolate import interp1d 
from   scipy.signal      import decimate
from   scipy.stats import norm
from scipy.ndimage.morphology import binary_dilation, generate_binary_structure
                                     
from   os import environ
environ['PYTHONUNBUFFERED']='True'
import warnings
with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    try:
        matplotlib.use('Qt5Agg');  # Worth trying?
    except:
        pass

from streamlines.core import Core

pdebug = print

__all__ = ['Plot', 'random_colormap']
    
# Generate random colormap
def random_colormap(cmap_name='randomized', n_colors=1000,random_seed=1):
    """
    Create a cmap with a randomized sequence of bright colors
    
    Args:
        TBD (TBD): 
    
    TBD

    Returns:
        TBD: 
        TBD
    """
    ru = np.random.uniform
    np.random.seed(random_seed)
    color_palette \
        = [colorsys.hsv_to_rgb(ru(),ru(low=0.9),ru(low=0.9)) for i in range(n_colors)]
    return LinearSegmentedColormap.from_list(cmap_name, color_palette, N=n_colors)

class Plot(Core):       
    """
    Plot maps and distributions.
    
    Args:
        TBD (TBD): 
    
    TBD

    Returns:
        TBD: 
        TBD
    """
    def __init__(self, state, imported_parameters,
                 geodata, preprocess, trace, analysis, mapping):
        """    
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        super().__init__(state,imported_parameters)  
        self.geodata = geodata
        self.preprocess = preprocess
        self.trace = trace
        self.analysis = analysis
        self.mapping = mapping
        self.figs = {}
        
    def _new_figure(self, title=None, window_title=None, pdf=False, 
                    x_pixel_scale=1,y_pixel_scale=1,
                    window_size_factor=None, projection=None):
        """  
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
#         mpl.rc( 'savefig', dpi=300)
        mpl.rc( 'figure', autolayout=False,  titlesize='Large',dpi=self.fig_dpi)
        mpl.rc( 'lines', linewidth=2.0, markersize=10)
        # mpl.rc( 'font', size=14,family='Times New Roman', serif='cm')
        # mpl.rc( 'font', size=14,family='DejaVu Sans', serif='cm')
        mpl.rc( 'font', size=self.general_font_size, family='Arial')
        mpl.rc( 'axes', labelsize=self.axes_font_size) 
        # mpl.rc( 'legend', fontsize=10)
        mpl.rc( 'text', usetex=False)
        
        if title is None:
            title = self.geodata.title
        if window_title is None:
            window_title = self.state.parameters_file
        try:
            self.figure_count += 1
        except:
            self.figure_count = 1
        if not pdf:
            if window_size_factor is None:
                window_size_factor = self.window_size_factor
        else:
            window_size_factor = self.window_pdf_size_factor
        
        if projection is None:
            fig, axes = plt.subplots( 
                            figsize=(self.window_width *window_size_factor,
                                     self.window_height*window_size_factor))
            ticks_x = ticker.FuncFormatter(
                lambda x, pos: '{0:g}'.format(x*x_pixel_scale) )
            axes.xaxis.set_major_formatter(ticks_x)
            ticks_y = ticker.FuncFormatter(
                lambda y, pos: '{0:g}'.format(y*y_pixel_scale) )
            axes.yaxis.set_major_formatter(ticks_y)
        elif projection=='polar':
            fig, axes = plt.subplots( 
                            figsize=(self.window_width *window_size_factor,
                                     self.window_height*window_size_factor),
                            subplot_kw=dict(projection=projection))
        else:
             raise Exception('Projection not understood') 

        axes.set_rasterization_zorder(1)
        fig.canvas.set_window_title(window_title)
        axes.set_title(title, fontsize=self.title_font_size, fontweight='bold')
        
        return fig, axes
          
    def _record_fig(self,fig_name,fig):
        """  
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        self.print('Recording figure "{}"'.format(fig_name))
        self.figs.update({fig_name : fig})  
        return fig_name     

    def _force_display(self, fig):
        """
        TBD
        """
        if self.state.do_display:
            try:
                fig.canvas.manager.show() 
                # this makes sure that the gui window gets shown
                # if this is needed depends on rcparams, this is just to be safe
                fig.canvas.flush_events() 
                # this make sure that if the event loop integration is not 
                # set up by the gui framework the plot will update
            except:
                pass
        else:
            plt.close(fig)

    @staticmethod
    def show():
        """  
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        plt.show()

    def do(self):
        """
        Display all output  
        
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        self.print('\n**Plot all begin**') 
        if self.do_plot_maps:
            self.plot_maps()
        if self.do_plot_distributions:
            self.plot_distributions()
        if self.do_plot_hsl_distributions:
            self.plot_hsl_distributions()

        self.print('**Plot all end**\n')  
        
    def plot_maps(self):
        """
        Plot maps
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        self.print('Plotting maps...')
        # Hillshade view of source DTM with correct axis labeling
        if self.do_plot_dtm:
            self.plot_dtm_shaded_relief()
        # Hillshade view of roi with correct axis labeling of pixels 
        #   but not meters (for non 1m pixels)
        if self.do_plot_roi:
            self.plot_roi_shaded_relief()
        # Streamlines, points  on semi-transparent shaded relief
        if self.do_plot_streamlines:
            self.plot_streamlines()
        # Try to map channels etc
        if self.do_plot_flow_maps:
            self.plot_flow_maps()
        if self.do_plot_segments:
            self.plot_segments()
        if self.do_plot_channels:
            self.plot_channels()
        if self.do_plot_hsl:
            self.plot_hsl()
        if self.do_plot_hsl_contoured:
            self.plot_hsl_contoured()
        if self.do_plot_aspect:
            self.plot_aspect()
        if self.do_plot_hsl_aspect_distribution:
            self.plot_hsl_aspect_distribution()
        self.print('...done')
            
    def plot_dtm_shaded_relief(self, window_size_factor=None):
        """
        Hillshade view of source DTM
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        fig,axes = self._new_figure(x_pixel_scale=self.geodata.roi_pixel_size/self.units,
                                    y_pixel_scale=self.geodata.roi_pixel_size/self.units,
                                    window_size_factor=window_size_factor)
        dtm_hillshade_array = self.generate_hillshade(self.geodata.dtm_array,
                                                      self.hillshade_azimuth,
                                                      self.hillshade_angle)
        axes.imshow(dtm_hillshade_array, cmap='Greys', 
                  extent=(0,self.geodata.dtm_array.shape[0],
                          0,self.geodata.dtm_array.shape[1]))
        self._force_display(fig)
        self._record_fig('dtm_shaded_relief',fig)
    
    def plot_roi_shaded_relief(self, interp_method=None, window_size_factor=None):
        """
        Hillshade view of ROI of DTM
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        fig,axes = self._new_figure(x_pixel_scale=self.geodata.roi_pixel_size/self.units,
                                    y_pixel_scale=self.geodata.roi_pixel_size/self.units,
                                    window_size_factor=window_size_factor)
        self.plot_roi_shaded_relief_overlay(axes, 
                                    color_alpha=self.shaded_relief_color_alpha,
                                    hillshade_alpha=self.shaded_relief_hillshade_alpha,
                                    interp_method=interp_method)
        self._force_display(fig)
        self._record_fig('roi_shaded_relief',fig)

    def plot_roi_shaded_relief_overlay(self, axes, do_plot_color_relief=None,
                                       color_alpha=None, hillshade_alpha=None,
                                       interp_method=None):
        """
        Hillshade view of ROI of DTM (overlay method)
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        if self.geodata.do_basin_masking: 
            if self.geodata.pad_width>0:               
                mask_array = self.state.merge_active_masks()[
                    self.geodata.pad_width:-self.geodata.pad_width,
                    self.geodata.pad_width:-self.geodata.pad_width]
            else:
                mask_array = self.state.merge_active_masks()
        else:
            mask_array = np.zeros_like(self.geodata.roi_array)

        try:
            self.roi_hillshade_array
        except:
            self.roi_hillshade_array = self.generate_hillshade(self.geodata.roi_array,
                                                               self.hillshade_azimuth,
                                                               self.hillshade_angle)
        if interp_method is None:
            interp_method = self.interpolation_method
        
        if do_plot_color_relief is None:
            do_plot_color_relief = self.do_plot_color_shaded_relief
        if do_plot_color_relief:
            if self.geodata.do_basin_masking:                
                masked_array = np.ma.masked_array(self.geodata.roi_array,
                                                  mask=mask_array)
            else:
                masked_array = self.geodata.roi_array
            axes.imshow(np.fliplr(masked_array).T,
                        cmap=self.terrain_cmap, 
                        extent=[*self.geodata.roi_x_bounds,*self.geodata.roi_y_bounds], 
                        alpha=color_alpha,
                        interpolation=interp_method)
        axes.imshow(np.fliplr(self.roi_hillshade_array).T,
                    cmap='Greys', 
                    extent=[*self.geodata.roi_x_bounds,*self.geodata.roi_y_bounds], 
                    alpha=hillshade_alpha,
                    interpolation=interp_method)

    @staticmethod
    def generate_hillshade(array, azimuth_degrees, angle_altitude_degrees):
        """
        Hillshade render DTM topo with light from direction azimuth_degrees and tilt
        angle_attitude_degrees
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        x_grad, y_grad = np.gradient(array)
        slope = pi/2.0 - arctan(np.hypot(x_grad,y_grad))
        aspect = arctan2(-x_grad, y_grad)
         # rotate from np array to true orientation
        azimuth_radians = np.deg2rad(azimuth_degrees+180)
        altitude_radians = np.deg2rad(angle_altitude_degrees)
        shadedImage = (sin(altitude_radians)*sin(slope) + cos(altitude_radians)
                       *cos(slope)*cos(azimuth_radians - aspect) )
        return (255*(shadedImage+1))/2

    def plot_streamlines(self, window_size_factor=None):
        """
        Streamlines, points on semi-transparent shaded relief
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        fig_name='streamlines'
        window_title='streamlines'
        fig,axes = self._new_figure(window_title=window_title,
                                    x_pixel_scale=self.geodata.roi_pixel_size,
                                    y_pixel_scale=self.geodata.roi_pixel_size,
                                    window_size_factor=window_size_factor)
        self.plot_roi_shaded_relief_overlay(axes,
                        color_alpha=self.streamline_shaded_relief_color_alpha,
                        hillshade_alpha=self.streamline_shaded_relief_hillshade_alpha)
        if self.do_plot_flow_vectors:
            self.plot_gradient_vector_field_overlay(axes)
        if self.do_plot_downstreamlines:
            self.plot_updownstreamlines_overlay(axes,do_down=True)
        if self.do_plot_upstreamlines:
            self.plot_updownstreamlines_overlay(axes,do_down=False)
        if self.do_plot_seed_points:
            self.plot_seed_points_overlay(axes)
        if self.do_plot_blockages: 
            self.plot_blockages_overlay(axes)
        if self.do_plot_loops:
            self.plot_loops_overlay(axes)
        # Force map limits to match those of the ROI
        #   - turn this off to check for boundary overflow errors in e.g. streamlines
        axes.set_xlim(left=self.geodata.roi_x_bounds[0],
                      right=self.geodata.roi_x_bounds[1])
        axes.set_ylim(bottom=self.geodata.roi_y_bounds[0],
                      top=self.geodata.roi_y_bounds[1])
        self._force_display(fig)
        self._record_fig(fig_name,fig)
    
    def plot_channels(self, window_size_factor=None, do_reset_mask=True):
        """
        Streamlines, points on semi-transparent shaded relief
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        try:
            self.mapping.mapping_array
        except: 
            self.print('Channels array not computed')
        if do_reset_mask:
            self.state.reset_active_masks()

        cmap = 'bwr'
        fig_name='channels'
        window_title='channels, midslopes & ridges'
        do_flip_cmap=False
        do_balance_cmap=True
                    
        fig,axes = self._new_figure(window_title=window_title,
                                    x_pixel_scale=self.geodata.roi_pixel_size/self.units,
                                    y_pixel_scale=self.geodata.roi_pixel_size/self.units,
                                    window_size_factor=window_size_factor)

        self.plot_roi_shaded_relief_overlay(axes,
                do_plot_color_relief=False, color_alpha=0,
                hillshade_alpha=self.channel_shaded_relief_hillshade_alpha)

        is_channel         = self.mapping.info.is_channel
        is_thinchannel     = self.mapping.info.is_thinchannel
        is_interchannel    = self.mapping.info.is_interchannel
        is_channelhead     = self.mapping.info.is_channelhead
        is_channeltail     = self.mapping.info.is_channeltail
        is_majorconfluence = self.mapping.info.is_majorconfluence
        is_minorconfluence = self.mapping.info.is_minorconfluence
        is_majorinflow     = self.mapping.info.is_majorinflow
        is_minorinflow     = self.mapping.info.is_minorinflow
        is_leftflank       = self.mapping.info.is_leftflank
        is_midslope        = self.mapping.info.is_midslope
        is_ridge           = self.mapping.info.is_ridge
        was_channelhead    = self.mapping.info.was_channelhead
        is_subsegmenthead  = self.mapping.info.is_subsegmenthead
        is_loop            = self.mapping.info.is_loop
        
        active_mask_array = self.state.merge_active_masks()
        
        grid_array = (self.mapping.mapping_array & is_thinchannel).copy().astype(np.bool)
        mask_array = active_mask_array | ~(grid_array)
        self.plot_simple_grid(grid_array, mask_array, axes, cmap='Blues', alpha=0.8)

        grid_array = (self.mapping.mapping_array & is_midslope).copy().astype(np.bool)
        fat_grid_array = np.zeros_like(grid_array, dtype=np.bool)
        dilation_structure = generate_binary_structure(2, 2)
        binary_dilation(grid_array, structure=dilation_structure, 
                        iterations=self.n_midslope_iterations, 
                        output=fat_grid_array)
        mask_array = active_mask_array | ~(fat_grid_array)
        self.plot_simple_grid(fat_grid_array, mask_array, axes, cmap='Greens', alpha=0.8)
        
        grid_array = (self.mapping.mapping_array & is_ridge).copy().astype(np.bool)
        fat_grid_array = np.zeros_like(grid_array, dtype=np.bool)
        dilation_structure = generate_binary_structure(2, 2)
        binary_dilation(grid_array, structure=dilation_structure,
                        iterations=self.n_ridge_iterations, 
                        output=fat_grid_array)
        mask_array = active_mask_array | ~(fat_grid_array)
        self.plot_simple_grid(fat_grid_array, mask_array, axes, cmap='Oranges', alpha=0.8)
        
#         self.plot_compound_markers(axes, is_majorconfluence, ['blue','black'])
#         self.plot_compound_markers(axes, is_loop,     ['pink','black'], msf=2)
        if self.do_plot_tails:
            self.plot_compound_markers(axes, is_channeltail,    ['cyan','black'], msf=1.5)
#         self.plot_compound_markers(axes, is_leftflank,     ['purple','black'], msf=0.2)
        if self.do_plot_heads:
            self.plot_compound_markers(axes, is_channelhead,    ['blue','black'], msf=0.5)
#         self.plot_compound_markers(axes, was_channelhead,   ['orange','black'], msf=0.3)
#         self.plot_compound_markers(axes, is_subsegmenthead,['orange','black'], msf=0.25)
#         self.plot_compound_markers(axes, is_midslope,        ['purple','black'])
#         self.plot_compound_markers(axes, is_ridge,        ['purple','black'])

        self._force_display(fig)
        self._record_fig(fig_name,fig)

    def plot_compound_markers(self, axes, flag, colors, msf=1.0):
        """
        Streamlines, points on semi-transparent shaded relief
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        pad = self.geodata.pad_width
        markers_array = (np.flipud(np.argwhere(
                                        self.mapping.mapping_array & flag
                                        ).astype(np.float32)-pad))
        marker = self.channel_head_marker
        marker_sizes = [ms*msf for ms in self.channel_head_marker_sizes]
        colors = colors
        alpha = self.channel_head_marker_alpha
        axes.plot(markers_array[:,0]+self.geodata.roi_x_origin,
                  markers_array[:,1]+self.geodata.roi_y_origin,
                  marker, ms=marker_sizes[1], 
                  color=colors[1], alpha=alpha, fillstyle='full')
        axes.plot(markers_array[:,0]+self.geodata.roi_x_origin,
                  markers_array[:,1]+self.geodata.roi_y_origin,
                  marker, ms=marker_sizes[0], 
                  color=colors[0], alpha=alpha, fillstyle='full')

    def plot_simple_grid(self,grid_array,mask_array,axes,cmap='Blues',alpha=0.8,
                         do_vlimit=True, v_min=None, v_max=None):
        """
        Streamlines, points on semi-transparent shaded relief
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        grid_array = grid_array[self.geodata.pad_width:-self.geodata.pad_width,
                                self.geodata.pad_width:-self.geodata.pad_width]    
        mask_array = mask_array[self.geodata.pad_width:-self.geodata.pad_width,
                                self.geodata.pad_width:-self.geodata.pad_width]    
        masked_grid_array = np.ma.masked_array(grid_array, mask=mask_array)
        if do_vlimit:
            im = axes.imshow(np.flipud(masked_grid_array.T), 
                      cmap=cmap, 
                      extent=[*self.geodata.roi_x_bounds,*self.geodata.roi_y_bounds],
                      alpha=alpha,
                      interpolation=self.interpolation_method, vmin=0, vmax=1
                      )
        else:
            im = axes.imshow(np.flipud(masked_grid_array.T), 
                      cmap=cmap, 
                      extent=[*self.geodata.roi_x_bounds,*self.geodata.roi_y_bounds],
                      alpha=alpha,
                      interpolation=self.interpolation_method, vmin=v_min, vmax=v_max
                      )
        clim=im.properties()['clim']
        return im
    
    def plot_flow_maps(self, window_size_factor=None): 
        """
        Streamlines, points on semi-transparent shaded relief
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        if self.do_plot_flow_dsla:
            fig_name='dsla'
            window_title='dsla'     
            tmp_array = self.trace.sla_array[:,:,0].copy()
            tmp_array[(~np.isnan(tmp_array)) & (tmp_array>0.0)] \
                = np.sqrt(tmp_array[(~np.isnan(tmp_array)) & (tmp_array>0.0)])
            
            self.plot_gridded_data(tmp_array,
                                   'gist_earth', 
                                   fig_name=fig_name, 
                                   window_size_factor=window_size_factor,
                                   window_title=window_title,
                                   do_flip_cmap=True, do_balance_cmap=False)
            tmp_array = self.trace.slt_array[:,:,0].copy()
            tmp_array[(~np.isnan(tmp_array)) & (tmp_array>0.0)] \
                = np.log(tmp_array[(~np.isnan(tmp_array)) & (tmp_array>0.0)])
            
        if self.do_plot_flow_dslt:
            fig_name='dslt'
            window_title='dslt'     
            self.plot_gridded_data(tmp_array,
                                   'gist_earth', #'seismic', 
                                   fig_name=fig_name, 
                                   window_size_factor=window_size_factor,
                                   window_title=window_title,
                                   do_flip_cmap=True, do_balance_cmap=False)
        
    def plot_segments(self,do_shaded_relief=True, window_size_factor=None):
        """
        Streamlines, points on semi-transparent shaded relief
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        fig_name='label'
        window_title='label'     
        tmp_array = (self.mapping.coarse_subsegment_array.copy().astype(np.int32))
#         pdebug('label', tmp_array.shape,np.unique(tmp_array))
#         tmp_array[tmp_array!=1]=0
        self.state.add_active_mask(
            {'merged_coarse_mask_array':self.mapping.merged_coarse_mask_array})
        mask_array = self.state.merge_active_masks()
#         pdebug('list_active_masks',self.state.list_active_masks())
        self.plot_gridded_data(tmp_array,
                               'randomized', 
                               fig_name=fig_name, window_size_factor=window_size_factor,
                               mask_array=mask_array,
                               window_title=window_title,
                               do_shaded_relief=False, 
                               do_flip_cmap=False, do_balance_cmap=False)

    def plot_hsl(self, cmap=None, window_size_factor=None,
                 z_min=None,z_max=None,z_pctile=None, do_shaded_relief=None, 
                 colorbar_size=None, colorbar_aspect=None, grid_alpha=None):
        """
        Streamlines, points on semi-transparent shaded relief
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        fig_name='hsl'
        window_title='hsl'     
        if cmap is None:
            cmap = self.hsl_cmap
        if colorbar_size is None:
            colorbar_size = self.hsl_colorbar_size
        if colorbar_aspect is None:
            colorbar_aspect = self.hsl_colorbar_aspect
        if do_shaded_relief is None:
            do_shaded_relief = self.hsl_do_shaded_relief
        if z_pctile is None:
            z_pctile = self.hsl_z_pctile
        if z_min is None:
            if self.hsl_z_min=='full':
                z_min = np.percentile(self.mapping.hsl_array, 0.0)
            elif self.hsl_z_min=='auto':
                z_min = np.percentile(self.mapping.hsl_array, 1.0)
            else:
                z_min = self.hsl_z_min
        if z_max is None:
            if self.hsl_z_max=='full':
                z_max = np.percentile(self.mapping.hsl_array,100.0)  
            elif self.hsl_z_max=='auto':
                z_max = np.percentile(self.mapping.hsl_array,z_pctile)  
            else:
                z_max = self.hsl_z_max
        grid_array = np.clip(self.mapping.hsl_array.copy(),z_min,z_max)
#         mask_array = np.zeros_like(grid_array).astype(np.bool)
        mask_array = self.state.merge_active_masks()
        mask_array[grid_array<=5] = True
#         pdebug('list_active_masks',self.state.list_active_masks())
        self.plot_gridded_data(grid_array,
                               cmap,  # rainbow
                               mask_array=mask_array,
                               fig_name=fig_name, window_size_factor=window_size_factor,
                               window_title=window_title,
                               do_flip_cmap=False, do_balance_cmap=False,
                               do_shaded_relief=do_shaded_relief, 
                               do_colorbar=True, 
                               colorbar_title='hillslope length [m]',
                               colorbar_size=colorbar_size,
                               colorbar_aspect=colorbar_aspect,
                               grid_alpha=self.hsl_alpha)
    
    def plot_hsl_contoured(self, window_size_factor=None, cmap=None,
                           do_colorbar=True, colorbar_title='hillslope length [m]',
                           n_contours=None, contour_interval=None, linewidth=None,
                           z_min=None,z_max=None,z_pctile=None, do_shaded_relief=None,
                           colorbar_size=None, colorbar_aspect=None, 
                           contour_label_suffix=None,
                           contour_label_fontsize=None, do_plot_contours=None):
        """
        Streamlines, points on semi-transparent shaded relief
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        fig_name='hsl_contours'
        window_title='hsl contours'    
        fig,axes = self._new_figure(window_title=window_title,
                                    x_pixel_scale=self.geodata.roi_pixel_size,
                                    y_pixel_scale=self.geodata.roi_pixel_size,
                                    window_size_factor=window_size_factor)
        pad = self.geodata.pad_width
        pslice = np.index_exp[pad:-pad,pad:-pad]
        if cmap is None:
            cmap = self.contour_hsl_cmap
        if do_plot_contours is None:
            do_plot_contours = self.do_plot_hsl_contours
        if colorbar_size is None:
            colorbar_size = self.contour_hsl_colorbar_size
        if colorbar_aspect is None:
            colorbar_aspect = self.contour_hsl_colorbar_aspect
        if do_shaded_relief is None:
            do_shaded_relief = self.contour_hsl_do_shaded_relief
        if contour_label_fontsize is None:
            contour_label_fontsize = self.contour_label_fontsize
        if contour_label_suffix is None:
            contour_label_suffix = self.contour_hsl_label_suffix
        if z_pctile is None:
            z_pctile = self.contour_hsl_z_pctile
        if z_min is None:
            if self.contour_hsl_z_min=='full':
                z_min = np.percentile(self.mapping.hsl_smoothed_array[pslice], 0.0)
            elif self.contour_hsl_z_min=='auto':
                z_min = np.percentile(self.mapping.hsl_smoothed_array, 1.0)
            else:
                z_min = self.contour_hsl_z_min
        if z_max is None:
            if self.contour_hsl_z_max=='full':
                z_max = np.percentile(self.mapping.hsl_smoothed_array,100.0)  
            elif self.contour_hsl_z_max=='auto':
                z_max = np.percentile(self.mapping.hsl_array,z_pctile)  
            else:
                z_max = self.contour_hsl_z_max     
        grid_array = np.clip(self.mapping.hsl_smoothed_array[pslice].copy(),z_min,z_max)
#         if n_contours is None and self.contour_hsl_n_contours!='auto':
#                 n_contours = self.contour_hsl_n_contours
        if linewidth is None:
            linewidth = self.contour_hsl_linewidth
                
        mask_array = self.state.merge_active_masks()[pslice]
        mask_array[self.mapping.hsl_array[pslice]<=5] = True
#         pdebug('list_active_masks',self.state.list_active_masks())
#         mask_array &= False
        if do_shaded_relief:
            hillshade_alpha = self.grid_shaded_relief_hillshade_alpha
            hsl_alpha = self.contour_hsl_alpha
        else:
            hillshade_alpha = 0.0
            hsl_alpha = 1.0
        self.plot_roi_shaded_relief_overlay(axes, do_plot_color_relief=False,
                                            hillshade_alpha=hillshade_alpha)
        im = self.plot_simple_grid((grid_array),
                              mask_array,axes,cmap=cmap,alpha=hsl_alpha,do_vlimit=False)
#         if do_colorbar:
#             divider = make_axes_locatable(axes)
#             cax = divider.append_axes("bottom", size="4%", 
#                                       pad=0.5, aspect=colorbar_aspect)
#             cbar = plt.colorbar(im, cax=cax, orientation="horizontal")
#             cbar.set_label(colorbar_title)
            
        if do_colorbar:
            divider = make_axes_locatable(axes)
            cax = divider.append_axes("bottom", size="{}%".format(colorbar_size), 
                                      pad=0.5, aspect=colorbar_aspect)
            cbar = plt.colorbar(im, cax=cax, orientation="horizontal")
            cbar.set_label(colorbar_title)
            
        if do_plot_contours:
            self.plot_contours_overlay(axes,grid_array.T,mask=mask_array,
                                       n_contours=n_contours,
                                       contour_interval=contour_interval,
                                       linewidth=linewidth,
                                       contour_label_suffix=contour_label_suffix, 
                                       contour_label_fontsize=contour_label_fontsize)
        # Force map limits to match those of the ROI
        #   - turn this off to check for boundary overflow errors in e.g. streamlines
        axes.set_xlim(left=self.geodata.roi_x_bounds[0],
                      right=self.geodata.roi_x_bounds[1])
        axes.set_ylim(bottom=self.geodata.roi_y_bounds[0],
                      top=self.geodata.roi_y_bounds[1])
        self._force_display(fig)
        self._record_fig(fig_name,fig)
    
    def plot_aspect(self, window_size_factor=None,cmap=None,do_plot_contours=None,
                    contour_label_suffix=None, contour_label_fontsize=None):
        """
        Streamlines, points on semi-transparent shaded relief
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        fig_name='aspect'
        window_title='aspect'
        try:
            self.mapping.aspect_array
        except: 
            self.print('Aspect array not computed')
        
        if cmap==None:
            cmap = 'RdYlBu' #'seismic'  #bwr
        do_flip_cmap=False
        do_balance_cmap=True
        if do_plot_contours is None:
            do_plot_contours = self.do_plot_aspect_contours
        if contour_label_fontsize is None:
            contour_label_fontsize = self.contour_label_fontsize
        if contour_label_suffix is None:
            contour_label_suffix = self.contour_aspect_label_suffix
                            
        fig,axes = self._new_figure(window_title=window_title,
                                    x_pixel_scale=self.geodata.roi_pixel_size,
                                    y_pixel_scale=self.geodata.roi_pixel_size,
                                    window_size_factor=window_size_factor)

        self.state.add_active_mask({'merged_coarse': 
                                    self.mapping.merged_coarse_mask_array})
        mask_array = self.state.merge_active_masks()
        grid_array = np.rad2deg(self.mapping.aspect_array.copy())
        
        self.plot_roi_shaded_relief_overlay(axes,
                do_plot_color_relief=False, color_alpha=0,
                hillshade_alpha=self.channel_shaded_relief_hillshade_alpha)
        im = self.plot_simple_grid(grid_array, mask_array, axes, cmap=cmap, 
                                   alpha=0.5, do_vlimit=False, v_min=-180, v_max=+180)
        if do_plot_contours:
            pad = self.geodata.pad_width
            pslice = np.index_exp[pad:-pad,pad:-pad]
            self.plot_contours_overlay(axes,
                                       grid_array[pslice].T,
                                       mask=mask_array[pslice],
                                       contour_interval=10,
                                       linewidth=1,
                                       contour_label_suffix=contour_label_suffix, 
                                       contour_label_fontsize=contour_label_fontsize)
        
        divider = make_axes_locatable(axes)
        cax = divider.append_axes("right",  pad=0.3,
                                  size="{}%".format(self.contour_aspect_colorbar_size), 
                                  aspect=self.contour_aspect_colorbar_aspect)
        ticks       = [-180,-90,0,90,180]
        tick_labels = ['W','S','E','N','W']
        # Omit top W tick/label
        cbar = plt.colorbar(im, cax=cax, ticks=ticks[0:-1], 
                            orientation='vertical')
#         cbar.set_label('aspect', rotation=0,y=-0.1,labelpad=-20)
#         cbar.ax.set_yticklabels([r'-180$\degree$',r'-90$\degree$',
#                                  r'0$\degree$ E',
#                                  r'+90$\degree$',r'+180$\degree$'])
        cbar.ax.set_yticklabels(tick_labels[0:-1]) 

        self.state.reset_active_masks()
        self._force_display(fig)
        self._record_fig(fig_name,fig)

    def plot_hsl_aspect_distribution(self, window_size_factor=None, cmap=None):
        """
        Streamlines, points on semi-transparent shaded relief
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        try:
            hsl_aspect_array = self.mapping.hsl_aspect_averages_array
        except: 
            self.print('HSL-aspect array not computed')
            
        fig_name='hsl_aspect_distribution'
        window_title='HSL-aspect distribution'
                    
        fig,axes = self._new_figure(window_title=window_title,
                                    x_pixel_scale=self.geodata.roi_pixel_size,
                                    y_pixel_scale=self.geodata.roi_pixel_size,
                                    window_size_factor=window_size_factor,
                                    projection='polar')
        hsl = hsl_aspect_array[:,0][~np.isnan(hsl_aspect_array[:,0])]
        asp = hsl_aspect_array[:,1][~np.isnan(hsl_aspect_array[:,0])]
        hsl_max = np.max(hsl)
        hsl_min = np.min(hsl)
        n_bins  = hsl.shape[0]
        hsl_north = hsl[asp>=0.0]
        asp_north = asp[asp>=0.0]
        hsl_south = hsl[asp<=0.0]
        asp_south = asp[asp<=0.0]
        if hsl_north!=[] and hsl_south!=[]:
            hsl_north = np.concatenate( 
                (np.array(hsl_north[0:1]), hsl_north, 
                 (np.array(hsl_south[0:1])+np.array(hsl_north[-2:-1]))/2.0 ) )
            asp_north = np.concatenate(
                (np.array([0.0]), asp_north, np.array([np.pi])) )
            hsl_south = np.concatenate(
                 ( (np.array(hsl_south[0:1])+np.array(hsl_north[-2:-1]))/2.0, 
                  hsl_south, np.array(hsl_south[-2:-1])))
            asp_south = np.concatenate(
                 (np.array([-np.pi]), asp_south, np.array([0.0])) )
        if cmap is None:
            cmap = 'Greys'
        cmap = mpl.cm.get_cmap(cmap)
        rgba_north = cmap((self.mapping.hsl_mean_north-hsl_min)/(hsl_max-hsl_min))
        rgba_south = cmap((self.mapping.hsl_mean_south-hsl_min)/(hsl_max-hsl_min))
        axes.fill_between(asp_north, hsl_north, facecolor=rgba_north, alpha=0.4)
        axes.fill_between(asp_south, hsl_south, facecolor=rgba_south, alpha=0.4)
        axes.plot(asp, hsl, 'black', lw=1)
        
#         axes.set_theta_zero_location('N')
#         axes.set_theta_direction(-1)
        c_interval = 5
        while c_interval<=100:
            c_max = np.ceil(hsl_max//c_interval)*c_interval
            c_count = c_max/c_interval
            if c_count<=5:
                break
            c_interval *= 2        
        bands = np.arange(0,c_max+2*c_interval,c_interval).astype(np.uint32)
        band_labels = ['{}m'.format(band) for band in bands]
        axes.set_rgrids(bands, labels=band_labels, color='blue',style=None)
        angles = np.arange(0,360,45).astype(np.uint32)
        spc = u'\N{space}'
        angle_labels = [spc*6+r'0$\degree$ = E',r'45$\degree$',
                        r'90$\degree$ = N',r'135$\degree$',
                        r'$\pm$180$\degree$'+spc*6, r'-135$\degree$'+spc*5,
                        r'-90$\degree$ = S',r'-45$\degree$']
        axes.set_thetagrids(angles, labels=angle_labels)
#         axes.tick_params(pad=8)
        axes.grid(color='blue',alpha=0.5,linestyle='dashed')
        
        mha = np.deg2rad(self.mapping.hsl_mean_azimuth)
        mhm = self.mapping.hsl_mean_magnitude
        mhl = hsl_max/2.0
        head_length = 40   # hack - how to correctly scale??
        arrow_style = ArrowStyle.Fancy(head_length=head_length,head_width=head_length/2,
                                      tail_width=1)
        arrow_patch = FancyArrowPatch((mha-np.pi,mhl/3.0), (mha,mhl),
                                      shrinkA=1, shrinkB=1,
                                      arrowstyle=arrow_style,
                                      facecolor='blue', edgecolor='blue', 
                                      linewidth=4)
        axes.add_patch(arrow_patch)
        axes.plot(mha,mhm,'.',ms=40,color='blue')
        axes.plot(mha,mhm,'.',ms=30,color='lightblue')

        color = 'purple'
        h_position = hsl_max/1.8
        axes.text(+np.deg2rad(145), h_position, 
                  r'$\overline{\mathbf{L}}_\mathbf{N}=$'+'{:2.0f}m'
                    .format(self.mapping.hsl_mean_north),
                  size=18, color=color, fontweight='bold',
                  horizontalalignment='center', verticalalignment='center')
        axes.text(-np.deg2rad(145), h_position, 
                  r'$\overline{\mathbf{L}}_\mathbf{S}=$'+'{:2.0f}m'
                    .format(self.mapping.hsl_mean_south),
                  size=18, color=color, fontweight='bold',
                  horizontalalignment='center', verticalalignment='center')

        self._force_display(fig)
        self._record_fig(fig_name,fig)

    def plot_contours_overlay(self,axes,Z,mask=None, n_contours=None,
                              contour_interval=None, linewidth=None,
                              contour_label_suffix='m', contour_label_fontsize=12):
        """
        Streamlines, points on semi-transparent shaded relief
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        x_pixel_centers_array,y_pixel_centers_array \
            = np.meshgrid(self.geodata.x_roi_n_pixel_centers,
                          self.geodata.y_roi_n_pixel_centers)
        Z = np.ma.array(Z,mask=mask.T)
#         z_min = np.percentile(Z, 1.0)
        z_max = np.percentile(Z,100.0)
        if n_contours is None:
            if contour_interval is None:
                c_interval = 5
            else:
                c_interval = contour_interval
            while c_interval<50:
                c_min = np.floor(np.min(Z)//c_interval)*c_interval
                c_max = np.ceil(z_max//c_interval)*c_interval
                c_count = (c_max-c_min)/c_interval
                if c_count<=17:
                    break
                c_interval *= 2
            n_contours = np.arange(c_min,c_max+c_interval,c_interval)
        contours = axes.contour(x_pixel_centers_array,y_pixel_centers_array,Z,
                                n_contours, colors='k', linewidths=linewidth)
        axes.clabel(contours, fmt='%.0f'+contour_label_suffix, 
                    fontsize=contour_label_fontsize);

    def plot_hsl_distributions(self, x_stretch=None):
        """
        Streamlines, points on semi-transparent shaded relief
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        self.print('Plotting hillslope length distributions...')
        df = self.mapping.hsl_aspect_df    
        
        if x_stretch is None:
            x_stretch = self.mhsl_pdf_x_stretch
        hsl_n = df.hsl[(df.aspect>45) & (df.aspect<135)]
        hsl_s = df.hsl[(df.aspect<-45) & (df.aspect>-135)]
        
        name = 'hsl_nsall_distbn'
        title = 'N-facing vs S-facing hillslope length distributions'
        fig,_ = self._new_figure(window_title=name)
        axes  = df['hsl'].plot.density(figsize=(8,8), title=title, color='k',#style='-.',
                                      label='360°', alpha=0.7, lw=1, secondary_y='360°')
        x_max = df['hsl'].quantile(q=1)*x_stretch
        try:
            axes2 = hsl_n.plot.density(figsize=(8,8),color='steelblue', label='N-facing')
        except:
            axes2 = None
        try:
            axes3 = hsl_s.plot.density(figsize=(8,8),color='darkorange',label='S-facing')
        except:
            axes3 = None
        if axes2 is None:
            axes_ns = axes3
        else:
            axes_ns = axes2
        lines = axes.get_lines()
        axes.legend(lines, [l.get_label() for l in lines], loc='upper right')
        lines = axes_ns.get_lines()
        axes_ns.legend(lines, [l.get_label() for l in lines], loc='upper left')
        x_label = r'distance $L$  [m]'
        y_label = r'probability density  $f(L)$  [m$^{-1}$]'
        axes.set_xlabel(x_label)
        axes.set_ylabel(y_label)
        axes_ns.set_ylabel(y_label)
        axes.set_ylim(0,None)
        axes_ns.set_ylim(0,None)
        axes.set_xlim(0,x_max)
        axes_ns.set_xlim(0,x_max)
        self._force_display(fig)
        self._record_fig(name,fig)
        
        if self.mapping.hsl_ns_min is not None and self.mapping.hsl_ns_max is not None:
            name = 'hsl_ns_qq'
            title = 'Hillslope length-aspect Q-Q plot'
            fig,_ = self._new_figure(window_title=name)
            x_label = r'South-facing HSL $L_S$ percentiles  [m]'
            y_label = r'North-facing HSL $L_N$ percentiles  [m]'
            hsl_ns_min = self.mapping.hsl_ns_min
            hsl_ns_max = self.mapping.hsl_ns_max
            hsls = np.linspace(hsl_ns_min, hsl_ns_max, num=50)
            [plt.plot(self.mapping.hsl_ns_qq_array[0], self.mapping.hsl_ns_qq_array[1], 
                     marker, ms='8', color='darkblue' ) for marker in ('-','.')]
            axes = plt.gca()
            axes.set_xlabel(x_label)
            axes.set_ylabel(y_label)
            # Find plot limits (not tight)
            xy_min = min(axes.get_xlim()[0],axes.get_ylim()[0])
            xy_max = max(axes.get_xlim()[1],axes.get_ylim()[1])
            # Force equal x,y limits
            axes.set_xlim(xy_min,xy_max)
            axes.set_ylim(xy_min,xy_max)
            # Plot a diagonal x=y red dashed line for guidance
            hsls = np.linspace(xy_min, xy_max, num=50)
            plt.plot(hsls,hsls, 'r--', lw=1)
            # Replot to ensure q-q line is on top
            [plt.plot(self.mapping.hsl_ns_qq_array[0], self.mapping.hsl_ns_qq_array[1], 
                     marker, ms='8', color='darkblue' ) for marker in ('-','.')]
            axes.set_title(title)
            self._force_display(fig)
            self._record_fig(name,fig)

        if self.mapping.hsl_ns_min is not None and self.mapping.hsl_ns_max is not None:
            name = 'hsl_ns_pp'
            title = 'Hillslope length-aspect P-P plot'
            fig,_ = self._new_figure(window_title=name)
            x_label = r'North-facing HSL cumulative prob $F(L_N)$  [%]'
            y_label = r'South-facing HSL cumulative prob $F(L_S)$  [%]'
            percents = self.mapping.hsl_ns_pp_array[0]
            plt.plot(percents,percents, 'r--', lw=1)
            axes = plt.gca()
            axes.fill_between(self.mapping.hsl_ns_pp_array[1],
                              self.mapping.hsl_ns_pp_array[2], 
                              self.mapping.hsl_ns_pp_array[1],
                              facecolor='darkblue', alpha=0.1)
            plt.plot(self.mapping.hsl_ns_pp_array[1],self.mapping.hsl_ns_pp_array[2], 
                     color='darkblue')
            axes.set_xlabel(x_label)
            axes.set_ylabel(y_label)
            axes.set_title(title)
    #         plt.autoscale(tight=True)
            axes.set_xlim(0,100.25)
            axes.set_ylim(0,100.25)
            dxy = 0.025
            [plt.text(0.5-ns[1], 0.5+ns[1], 'longer {}-facing'.format(ns[0]), 
                      horizontalalignment='center', verticalalignment='center', 
                      rotation=45, transform=axes.transAxes, color='r') 
                      for ns in (('N',dxy),('S',-dxy))]
            self._force_display(fig)
            self._record_fig(name,fig)    
    
        return
    
        name = 'hsl_means_distbn'
        title = 'Distribution of means of hillslope length'
        fig,_ = self._new_figure(window_title=name)
        df = self.mapping.hsl_stats_df
        # Cut
        df = df[df['count']>1]
        kde_min_labels = 20
        x_label  = r'distance $L$  [m]'
        y_label  = r'probability density  $f(L)$  [m$^{-1}$]'
        y_label2 = r'number  $N(L)$  [$-$]'
        print(df)
        if df.shape[0]>kde_min_labels:
            axes = df['mean [m]'].plot.density(figsize=(8,8), title=title,
                                               label='pdf',secondary_y='pdf')
            axes2 = df['mean [m]'].plot.hist(figsize=(8,8), title=title,alpha=0.2,
                                             color='b')
            axes2.set_ylabel(y_label2)
        else:
            axes = df['mean [m]'].plot.hist(figsize=(8,8), title=title)
        axes.set_xlabel(x_label)
        axes.set_ylabel(y_label)
        axes.set_xlim(0,df['mean [m]'].quantile(q=1)*x_stretch)
        axes.set_ylim(0,None)
        self._force_display(fig)
        self._record_fig(name,fig)

        name = 'hsl_stddevs_distbn'
        fig,_ = self._new_figure(window_title=name)
        title = 'Hillslope length standard deviations'
        if df.shape[0]>kde_min_labels:
            axes = df['stddev [m]'].plot.density(figsize=(8,8), title=title)
        else:
            axes = df['stddev [m]'].plot.hist(figsize=(8,8), title=title)
        axes.set_xlabel(r'distance std deviation $\sigma_L$  [m]')
        axes.set_ylabel(r'probability density  $f(\sigma_L)$  [m$^{-1}$]')
        axes.set_xlim(0,df['stddev [m]'].quantile(q=0.99))
        axes.set_ylim(0,None)
        # axes.legend(['hillslope length std dev'],frameon=False)
        self._force_display(fig)
        self._record_fig(name,fig)
  
        name = 'hsl_counts_distbn'
        try:
            csum = np.int(df['count'].sum())
            cmax = np.int(df['count'].quantile(q=0.99))
            n_bins = min(50,max(10,csum//cmax))
        except:
            n_bins = 20
        fig,_ = self._new_figure(window_title=name)
        title = 'Hillslope length streamline counts'
        axes = df['count'].plot.hist(bins=n_bins,figsize=(8,8), title=title)
        axes.set_xlabel(r'number of streamlines per length average $N_{sl}$   [-]');
        axes.set_ylabel(r'frequency  $n(N_{sl})$  [-]')
        axes.set_xlim(0,df['count'].quantile(q=0.99))
        axes.set_ylim(0,None)
        self._force_display(fig)
        self._record_fig(name,fig)
        
        self.print('...done')

    def plot_gridded_data(self, grid_array, gridded_cmap, 
                          window_size_factor=None, fig_name=None, mask_array=None,
                          window_title='', do_flip_cmap=False, do_balance_cmap=True,
                          do_shaded_relief=True, do_colorbar=False, 
                          colorbar_title=None, colorbar_aspect=0.07,
                          colorbar_size=4, grid_alpha=None, extent=None):
        """
        Streamlines, points on semi-transparent shaded relief
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        if mask_array is None:
            mask_array = np.zeros_like(grid_array).astype(np.bool)
        pad = self.geodata.pad_width
        pslice = np.index_exp[pad:-pad,pad:-pad]
        mask_array = mask_array[pslice]
        if extent is None:
            extent = [*self.geodata.roi_x_bounds,*self.geodata.roi_y_bounds]
        if self.geodata.do_basin_masking:
            mask_array |= self.state.merge_active_masks()[pslice]
            
        if self.seed_point_marker_size==0:
            seed_point_marker = 'g,'
            seed_point_marker_size = 1
        else:
            seed_point_marker = 'gD'     
            seed_point_marker_size = self.seed_point_marker_size
            
        fig,axes = self._new_figure(window_title=window_title,
                                    x_pixel_scale=self.geodata.roi_pixel_size,
                                    y_pixel_scale=self.geodata.roi_pixel_size,
                                    window_size_factor=window_size_factor)

        if do_shaded_relief:
            self.plot_roi_shaded_relief_overlay(axes, do_plot_color_relief=False,
                    color_alpha=self.grid_shaded_relief_color_alpha,
                    hillshade_alpha=self.grid_shaded_relief_hillshade_alpha)
            if grid_alpha is None:
                grid_alpha = self.streamline_density_alpha
        else:
            grid_alpha = 1.0

        grid_array = grid_array[pslice]
#         extra_mask_array = grid_array.astype(np.bool)
#         mask_array = mask_array | (~extra_mask_array)
#         mask_array &= False
        masked_grid_array = np.ma.masked_array(grid_array, mask=mask_array)
        if do_flip_cmap:
            masked_grid_array = -masked_grid_array
            
        vmin = masked_grid_array[ (~np.isnan(masked_grid_array))
                                 & (~np.isinf(masked_grid_array)) ].min()
        vmax = masked_grid_array[ (~np.isnan(masked_grid_array))
                                 & (~np.isinf(masked_grid_array)) ].max()
        if do_balance_cmap:
            if abs(vmin)>abs(vmax):
                vmax = -vmin
            else:
                vmin = -vmax
        if gridded_cmap=='randomized':
            gridded_cmap = random_colormap(cmap_name='randomized', 
                                               random_seed=self.random_cmap_seed)
        im = axes.imshow(np.flipud(masked_grid_array.T), 
                  cmap=gridded_cmap, 
                  extent=extent,
                  alpha=grid_alpha,
                  interpolation=self.interpolation_method,
                  vmin=vmin, vmax=vmax
                  )
        clim=im.properties()['clim']
        if do_colorbar:
            divider = make_axes_locatable(axes)
            cax = divider.append_axes("bottom", size="{}%".format(colorbar_size), 
                                      pad=0.5, aspect=colorbar_aspect)
            cbar = plt.colorbar(im, cax=cax, orientation="horizontal")
            cbar.set_label(colorbar_title)

#         try:
#             is_thinchannel = self.mapping.info.is_thinchannel
#             grid_array = ( self.mapping.mapping_array[pslice] 
#                            & is_thinchannel ).astype(np.bool)
#             im = axes.imshow(np.flipud(grid_array.T), 
#                       cmap='Blues', 
#                       extent=[*self.geodata.roi_x_bounds,*self.geodata.roi_y_bounds],
#                       alpha=0.5,
#                       interpolation=self.interpolation_method
#                       )
#         except:
#             pass

        self._force_display(fig)
        self._record_fig(fig_name,fig)

    def plot_updownstreamlines_overlay(self,axes,do_down=True):
        """
        Up or downstreamlines 
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        if do_down:
            up_or_down_str = 'down'
            streamline_arrays_list = self.trace.streamline_arrays_list[0]
            color = self.downstreamline_color
        else:
            up_or_down_str = 'up'
            streamline_arrays_list = self.trace.streamline_arrays_list[1]
            color = self.upstreamline_color
        marker = self.streamline_point_marker   
        size = self.streamline_point_size
        linewidth = self.streamline_linewidth
        alpha = self.streamline_point_alpha
        
        try:
            n_streamlines = len(streamline_arrays_list)
        except:
            return
        idx_list = list(range(n_streamlines))
        if self.shuffle_rng_seed is not None:
            seed(self.shuffle_rng_seed)
        if self.n_streamlines_limit!='none':
            shuffle(idx_list)
            idx_list = idx_list[0:min(n_streamlines,self.n_streamlines_limit)]

        todo = len(idx_list)
        if self.n_streamlines_limit!='none' and n_streamlines>self.n_streamlines_limit:
            self.print('Plotting {0:,}'.format(todo)+' '
                  +up_or_down_str+'streamlines'
                  +' randomly sampled from a set of {0:,}'.format(n_streamlines))
        else:
            self.print('Plotting all {0:,} {1}-streamlines'.format(todo,up_or_down_str))
        self.print('Progress: ', end='')
        progress = 0    
        if todo*self.trace.max_length>=300000:
            self.print_interval = max(1,todo//100)
        elif todo*self.trace.max_length>=100000:
            self.print_interval = max(1,todo//30)
        else:
            self.print_interval = max(1,todo//10)
        for sidx in range(todo): 
            trajectory = np.concatenate((np.array([[0,0]],dtype=np.float32),
                        streamline_arrays_list[idx_list[sidx]].astype(np.float32)
                                    /np.float32(self.trace.trajectory_resolution)))
            seed_point = self.trace.seed_point_array[idx_list[sidx],:]
            x_vec = ( self.geodata.roi_x_origin
                      + np.cumsum(trajectory.T[0])
                      + seed_point[0] )
            y_vec = ( self.geodata.roi_y_origin
                      + np.cumsum(trajectory.T[1])
                      + seed_point[1] )

            axes.plot(x_vec,y_vec,
                      marker, 
                      color=color,
                      ms=size, 
                      lw=linewidth,
                      alpha=alpha,
                      fillstyle='full')
    
            progress += 1
            if progress%self.print_interval==0:
                prog = int(100*int(0.5+100*progress/todo)//100)
                self.print(str(prog)+'%',end='') 
                if prog!=100:
                    self.print('...', end='')       
 
        self.print('')    
    
    def plot_classical_streamlines(self, window_size_factor=None):
        """
        Classic streamlines on color shaded relief
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        fig,axes = self._new_figure(x_pixel_scale=self.geodata.roi_pixel_size,
                                    y_pixel_scale=self.geodata.roi_pixel_size,
                                    window_size_factor=window_size_factor)
        self.plot_roi_shaded_relief_overlay(axes, 
                        color_alpha=self.streamline_shaded_relief_color_alpha,
                        hillshade_alpha=self.streamline_shaded_relief_hillshade_alpha)
        self.plot_classical_streamlines_overlay(axes)
        axes.set_xlim(left=self.geodata.roi_x_bounds[0],
                      right=self.geodata.roi_x_bounds[1])
        axes.set_ylim(bottom=self.geodata.roi_y_bounds[0],
                      top=self.geodata.roi_y_bounds[1])
        self._force_display(fig)
        self._record_fig('classical_streamlines',fig)

    def plot_classical_streamlines_and_vectors(self, window_size_factor=None, 
                                               sl_color=None,  vec_color=None, 
                                               vec_alpha=None, vec_scale=None):
        """
        Classic streamlines and gradient vector field on color shaded relief
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        fig,axes = self._new_figure(x_pixel_scale=self.geodata.roi_pixel_size,
                                    y_pixel_scale=self.geodata.roi_pixel_size,
                                    window_size_factor=window_size_factor)
        self.plot_roi_shaded_relief_overlay(axes, 
                        color_alpha=self.streamline_shaded_relief_color_alpha,
                        hillshade_alpha=self.streamline_shaded_relief_hillshade_alpha)
        if sl_color is None:
            sl_color = self.downstreamline_color
        if vec_color is None:
            vec_color = self.gradient_vector_color        
        if vec_alpha is None:
            vec_alpha = self.gradient_vector_alpha       
        if vec_scale is None:
            vec_scale = self.gradient_vector_scale    
        self.plot_gradient_vector_field_overlay(axes, vec_color=vec_color,
                                                vec_alpha=vec_alpha, vec_scale=vec_scale)
        self.plot_classical_streamlines_overlay(axes, sl_color=sl_color)
        axes.set_xlim(left=self.geodata.roi_x_bounds[0],
                      right=self.geodata.roi_x_bounds[1])
        axes.set_ylim(bottom=self.geodata.roi_y_bounds[0],
                      top=self.geodata.roi_y_bounds[1])
        self._force_display(fig)
        self._record_fig('classical_streamlines_and_vectors',fig)

    def plot_classical_streamlines_overlay(self,axes, sl_color='blue'):
        """
        Classic streamlines (overlay method)
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        x_pixel_centers_array,y_pixel_centers_array \
            = np.meshgrid(self.geodata.x_roi_n_pixel_centers,
                          self.geodata.y_roi_n_pixel_centers)
        axes.streamplot(x_pixel_centers_array,y_pixel_centers_array,
                      np.ma.array(self.preprocess.uv_array[:,:,0],
                                  mask=self.state.merge_active_masks()).T[
                        self.geodata.pad_width:-self.geodata.pad_width,
                        self.geodata.pad_width:-self.geodata.pad_width],
                      np.ma.array(self.preprocess.uv_array[:,:,1],
                                  mask=self.state.merge_active_masks()).T[
                        self.geodata.pad_width:-self.geodata.pad_width,
                        self.geodata.pad_width:-self.geodata.pad_width], 
                      density=min(1.0,self.classical_streamplot_density), 
                      color=sl_color, linewidth=self.classical_streamline_linewidth)
#                           color=self.geodata.roi_array, cmap='terrain')

    def plot_gradient_vector_field(self, window_size_factor=None, 
                                   vec_color='purple',vec_alpha=0.5, vec_scale=30):
        """
        Topographic gradient vector field on color shaded relief
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        fig,axes = self._new_figure(x_pixel_scale=self.geodata.roi_pixel_size,
                                    y_pixel_scale=self.geodata.roi_pixel_size,
                                    window_size_factor=window_size_factor)
        self.plot_roi_shaded_relief_overlay(axes, 
                        color_alpha=self.streamline_shaded_relief_color_alpha,
                        hillshade_alpha=self.streamline_shaded_relief_hillshade_alpha)
        self.plot_gradient_vector_field_overlay(axes, vec_color=vec_color,
                                                vec_alpha=vec_alpha, vec_scale=vec_scale)
        self._force_display(fig)
        self._record_fig('gradient_vector_field',fig)

    def plot_gradient_vector_field_overlay(self,axes,
                                           vec_color='purple',vec_alpha=0.5,
                                           vec_scale=30):
        """
       Topographic gradient vector field (overlay method)
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        max_dim = max(self.geodata.roi_array.shape)
        if self.gradient_vector_scale!='none':
            scale = self.gradient_vector_scale
        else:
            scale=None
        if max_dim>40:
            decimation_factor = 10*int(max_dim//40/10.0+0.5)
            u = decimate( 
                    decimate(
                            self.preprocess.uv_array[:,:,0],
                                decimation_factor,axis=0,n=2), 
                                    decimation_factor,axis=1,n=2)
            v = decimate( 
                    decimate(
                            self.preprocess.uv_array[:,:,1],
                            decimation_factor,axis=0,n=2), 
                                decimation_factor,axis=1,n=2)
            m = decimate( 
                    decimate(
                        ~self.state.merge_active_masks(),
                            decimation_factor,axis=0,n=2), 
                                decimation_factor,axis=1,n=2)
        else:
            u = self.preprocess.uv_array[:,:,0]
            v = self.preprocess.uv_array[:,:,1]
            m = ~self.state.merge_active_masks()
        # Normalize for clarity
        speed = np.hypot(u,v)
        u = u/speed
        v = v/speed
        x_roi_n_pixel_centers = np.linspace(self.geodata.roi_x_bounds[0]+0.5,
                                          self.geodata.roi_x_bounds[1]-0.5, 
                                          u.shape[0])
        y_roi_n_pixel_centers = np.linspace(self.geodata.roi_y_bounds[0]+0.5,
                                          self.geodata.roi_y_bounds[1]-0.5, 
                                          u.shape[1])
        axes.quiver(*np.meshgrid(x_roi_n_pixel_centers,y_roi_n_pixel_centers), 
                    (u*m).T, 
                    (v*m).T,
                    pivot='mid',
                    color=vec_color,
                    alpha=vec_alpha,
                    scale=vec_scale
                    )
            
    def plot_blockages_overlay(self,axes,color='k',shape='s'):
        """
        Blocked zone pixels
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        # Blocked outflows (narrow diagonal outflows)
        if self.state.noisy:
            self.print('Blockage @', (self.preprocess.where_blockages_array))
        try:
            self.preprocess.where_blockages_array
        except:
            self.print('Unable to plot blockages: no such array')
            return
        axes.plot(self.preprocess.where_blockages_array.T[0]
                            +self.geodata.roi_x_bounds[0]+0.5,
                  self.preprocess.where_blockages_array.T[1]
                            +self.geodata.roi_y_bounds[0]+0.5,
                  # Blockages plotted as hexagons 'H'
                 'kH', ms=self.blockage_marker_size, fillstyle='none')
        # Blocked outflow neighbors
        if self.state.noisy:
            self.print('Blockage neighbor @', (self.preprocess.where_blocked_neighbors_array))
        axes.plot(self.preprocess.where_blocked_neighbors_array.T[0]
                    +self.geodata.roi_x_bounds[0]+0.5,
                  self.preprocess.where_blocked_neighbors_array.T[1]
                    +self.geodata.roi_y_bounds[0]+0.5,
                # Blockage neighbors plotted as squares 's'
                color+shape, ms=self.blockage_marker_size, 
                fillstyle='none')
 
    def plot_loops_overlay(self,axes,color='k',shape='o'):
        """
        Loop zone pixels
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        try:
            self.preprocess.where_looped_array
        except:
            self.print('Unable to plot loops: no such array')
            return
        axes.plot(self.preprocess.where_looped_array.T[0]
                    +self.geodata.roi_x_bounds[0]+0.5,
                  self.preprocess.where_looped_array.T[1]
                    +self.geodata.roi_y_bounds[0]+0.5,
                    # Loops plotted as circles 'o'
                    color+shape, ms=self.loops_marker_size, 
                    fillstyle='none')

    def plot_seed_points_overlay(self,axes,color='k',marker=',',size=2):
        """
        Seed points
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        if self.seed_point_marker_size==0:
            marker = marker
            size = size
            color = color
        else:
            marker = self.seed_point_marker   
            size = self.seed_point_marker_size
            color = self.seed_point_marker_color
        
        x = self.trace.seed_point_array[:,0]+self.geodata.roi_x_origin
        y = self.trace.seed_point_array[:,1]+self.geodata.roi_y_origin
        axes.plot(x, y,
                marker, 
                ms=size, 
                color=color,
                alpha=self.seed_point_marker_alpha,
                fillstyle='full')

    def plot_midslope_elevations_pdf(self):
        """
        Plot distribution of midslope as kernel-smoothed pdf
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        fig_name = 'midslope_elevations_pdf'
        window_title = 'midslope_elevations_pdf'
        title = self.geodata.title+':  Elevation distribution'  
        fig,axes = self._new_figure(window_title=window_title, title=title)

        h_midline_pdf_array  = self.analysis.h_midline_pdf_array
        h_midline_pdf_max1   = self.analysis.h_midline_pdf_max1
        h_midline_pdf_max1_h = self.analysis.h_midline_pdf_max1_h
        h_all_pdf_array      = self.analysis.h_all_pdf_array
        h_array              = self.analysis.h_array
        sf                   = self.analysis.h_pdf_sf        
        plt.plot(h_midline_pdf_array/sf, h_array,label='midline', c='k')
        plt.plot(h_midline_pdf_max1/sf, h_midline_pdf_max1_h, 'o', c='k', ms=8,
                 label='{:4}$\,$m'.format(np.int(np.round(h_midline_pdf_max1_h)))  )
        # plt.plot(h_midline_pdf_max2/sf, h_midline_pdf_max_h2, 's', c='k', ms=8,
        #          label='{:4}$\,$m'.format(np.int(h_midline_pdf_max_h2))  )
        plt.plot(h_all_pdf_array/sf, h_array,label='all', c='b')
        plt.autoscale(enable=True, tight=True, axis='y')
        plt.xlim(-0.002,)
        plt.grid('on',ls=':')
        plt.legend()
#         plt.title(title)
        plt.ylabel('Elevation $h$  [m]')
        plt.xlabel('Density $p(h)$  [rescaled m$^{-1}$]')
        self._record_fig(fig_name,fig)

    def plot_slope_angles_pdf(self):
        """
        Plot distribution of slope angles as kernel-smoothed pdf
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        fig_name='slope_angles_pdf'
        window_title='slope_angles_pdf'
        title = self.geodata.title+':  Midslope angle distribution'   
        fig,axes = self._new_figure(window_title=window_title, title=title)
        
        slope_midline_pdf_array     = self.analysis.slope_midline_pdf_array
        slope_midline_pdf_max       = self.analysis.slope_midline_pdf_max
        slope_midline_pdf_max_slope = self.analysis.slope_midline_pdf_max_slope
        slope_array                 = self.analysis.slope_array        
        plt.plot(slope_midline_pdf_array/slope_midline_pdf_max, slope_array, c='k')
        plt.plot(slope_midline_pdf_max/slope_midline_pdf_max, 
                 slope_midline_pdf_max_slope, 'o', 
                 c='k', ms=8,
                 label='modal slope ={:4}$^\circ$'
                 .format(np.int(np.round(slope_midline_pdf_max_slope)))  )
        plt.autoscale(enable=True, tight=True, axis='y')
        plt.xlim(-0.002,)
        plt.grid('on',ls=':')
        plt.legend()
#         plt.title(title)
        plt.ylabel('Slope angle   $\\mathrm{atan}|\\nabla{h}|$  [$^{\circ}$]')
        plt.xlabel('Density  $p(\\mathrm{atan}|\\nabla{h}|)$  [rescaled 1/$^{\circ}$]')
        # plt.xlabel(
        #     'Relative frequency $p(|\\nabla{h}|)$  [rescaled 1/$^{\circ}$]')
        self._record_fig(fig_name,fig)
        
        
    def plot_distributions(self):
        """
        Plot probability distributions of processed streamline data.
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        self.print('Plotting distributions...')

        # Marginal univariate pdfs
        marginals_list = ['dsla','usla','dslt','uslt','dslc','uslc']
        for marginal in marginals_list:
            marginal_fn = 'plot_marginal_pdf_'+marginal
            do_plot = getattr(self,'do_'+marginal_fn)
            if do_plot:
                try:
                    plot_fn = getattr(self,marginal_fn)
                    plot_fn()
                except Exception as error:
                    self.print('Failed in "plot_distributions":\n', error)

        # Joint bivariate pdfs
        joint_list = ['dsla_usla','usla_uslt','dsla_dslt','dslt_dslc','dslt_dsla',
                      'uslt_dslt','usla_uslc','dsla_dslc','uslc_dslc']
        for joint in joint_list:
            joint_fn = 'plot_joint_pdf_'+joint
            do_plot = getattr(self,'do_'+joint_fn)
            if do_plot:
                try:
                    plot_fn = getattr(self,joint_fn)
                    plot_fn()
                except Exception as error:
                    self.print('Failed in "plot_distributions":\n', error)

        self.print('...done')

    def plot_marginal_pdf(self, marginal_distbn, fig_name=None,
                          title='',x_label='',y_label=''):
        """
        TBD
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        window_title=fig_name

        # Get ready
        x_vec = marginal_distbn.x_vec
        x_min,x_max = x_vec[0], x_vec[-1]
        legend = []
        
        # Generate curves
#         legend += ['']
        legend += ['kde pdf']
        pdf = marginal_distbn.pdf
        pdf_list = [ pdf ]
        y_max = pdf_list[0].max()
        pdf_ysf_list = [ y_max ]
        line_colors = ['crimson']
        line_styles = ['-']
        line_alphas = [1]

        legend += ['Gaussian']
        loc   = np.log(marginal_distbn.mean)
        scale = np.log(marginal_distbn.stddev)
        
        pdf   = norm.pdf(np.log(x_vec),loc,scale)
        pdf_max = pdf.max() 
        pdf_ysf_list += [ pdf_max ]
        pdf_list += [ y_max*pdf/pdf_max ]
        line_colors += ['darkmagenta']
        line_styles += ['-.']
        line_alphas += [0.9]
                                 
        legend += ['detrended']
        pdf = marginal_distbn.pdf
        norm_pdf = norm.pdf(np.log(x_vec),loc,scale)
        detrended_pdf = pdf/norm_pdf
#         detrended_pdf = marginal_distbn.detrended_pdf
        dt_min_idx = marginal_distbn.mode_i//2
        try:
            dt_max_idx = (min(1+1*marginal_distbn.mode_i,
                              marginal_distbn.channel_threshold_i))
        except:
            dt_max_idx = (min(2*marginal_distbn.mode_i,detrended_pdf.shape[0]))
        detrended_pdf /= max(detrended_pdf[dt_min_idx:dt_max_idx])
        pdf_max = norm_pdf[marginal_distbn.mode_i]
        pdf_ysf_list +=  [ pdf_max ]
        pdf_list += [ y_max*detrended_pdf ]
        line_colors += ['darkblue']
        line_styles += ['-']
        line_alphas += [1]
            
        # Create graph
        fig,axes = self._new_figure(window_title=window_title, title=title)
        axes.set_xscale('log')
        
        # Do the plotting
        plt.plot(x_vec, pdf_list[0], color=line_colors[0], alpha=line_alphas[0], 
                  linestyle=line_styles[0] )
        [plt.plot(x_vec, pdf, color=line_colors[idx+1], alpha=line_alphas[idx+1], 
                  linestyle=line_styles[idx+1])  for idx,pdf in enumerate(pdf_list[1:])]
        
        try:
            x= marginal_distbn.channel_threshold_x
            y= y_max*detrended_pdf[marginal_distbn.channel_threshold_i,0] \
                        /pdf_max
            legend += ['thresh={0:2.0f}m'.format(x)]
            plt.plot([x,x],[-0.1,1.0*axes.get_ylim()[1]],'--', 
                     color='blue', linewidth=3, alpha=0.7)
        except:
            self.print('Cannot plot threshold: none found')
        
        # Presentation
        axes.grid(color='gray', linestyle='dotted', linewidth=0.5, which='both')
        x_ticks = self._choose_ticks(x_min,x_max)
        plt.xticks(x_ticks,x_ticks)
        axes.set_xlabel(x_label)
        axes.set_ylabel(y_label)
        loc = 'upper right'
#         loc = 'upper left'
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            axes.legend(legend, loc=loc,fontsize=14,framealpha=0.97)

        axes.set_xlim(left=0.99, right=x_max*1.001)
        axes.set_ylim(bottom=0,top=y_max*1.1)

        # Display & record
        self._force_display(fig)
        return self._record_fig(fig_name,fig)    
        
    def plot_marginal_pdf_dsla(self):
        """
        TBD
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        fig_name = 'marginal_pdf_dsla'
        title = r'Downstreamline length distribution $f(\log(L_{md}))$'
        x_label = r'Downstreamline mean length  $L_{md}$ [meters]'
        y_label = r'Probability density  $f(\log(L_{md}))$'
        try:
            marginal_distbn = self.analysis.mpdf_dsla
        except:
            self.print('"'+title+'" not computed: cannot plot')
            return
        self.plot_marginal_pdf(marginal_distbn, fig_name=fig_name, 
                               title=title, x_label=x_label,y_label=y_label)
        
    def plot_marginal_pdf_usla(self):
        """
        TBD
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        fig_name = 'marginal_pdf_usla'
        title = r'Upstreamline length distribution $f(\log(L_{mu}))$'
        x_label = r'Upstreamline mean length  $L_{mu}$ [m]'
        y_label = r'Probability density  $f(\log(L_{mu}))$'
        try:
            marginal_distbn = self.analysis.mpdf_usla
        except:
            self.print('"'+title+'" not computed: cannot plot')
            return
        self.plot_marginal_pdf(marginal_distbn, fig_name=fig_name, 
                               title=title, x_label=x_label,y_label=y_label)
        
    def plot_marginal_pdf_dslt(self):
        """
        TBD
        """
        fig_name = 'marginal_pdf_dslt'
        title = r'Downstreamline root equiv area distribution $f(\log\sqrt{A_{ed}})$'
        x_label = r'Downstreamline root equiv area  $\sqrt{A_{ed}}$ [m]'
        y_label = r'Probability density  $f(\log\sqrt{A_{ed}})$'
        try:
            marginal_distbn = self.analysis.mpdf_dslt
        except:
            self.print('"'+title+'" not computed: cannot plot')
            return
        return self.plot_marginal_pdf(marginal_distbn, fig_name=fig_name, 
                                      title=title, x_label=x_label,y_label=y_label)
        
    def plot_marginal_pdf_uslt(self):
        """
        TBD
        """
        fig_name = 'marginal_pdf_uslt'
        title = r'Upstreamline root equiv area distribution $f(\log\sqrt{A_{eu}})$'
        x_label = r'Upstreamline root equiv area  $\sqrt{A_{eu}}$ [m]'
        y_label = r'Probability density  $f(\log\sqrt{A_{eu}})$'
        try:
            marginal_distbn = self.analysis.mpdf_uslt
        except:
            self.print('"'+title+'" not computed: cannot plot')
            return
        self.plot_marginal_pdf(marginal_distbn, fig_name=fig_name, 
                               title=title, x_label=x_label,y_label=y_label)

    def plot_marginal_pdf_dslc(self):
        """
        TBD
        """
        fig_name = 'marginal_pdf_dslc'
        title = r'Downstreamline concentration distribution $f(\log(C_{d}))$'
        x_label = r'Downstreamline concentration  $C_{d}$ [lines/m]'
        y_label = r'Probability density  $f(\log(C_{d}))$'
        try:
            marginal_distbn = self.analysis.mpdf_dslc
        except:
            self.print('"'+title+'" not computed: cannot plot')
            return
        self.plot_marginal_pdf(marginal_distbn, fig_name=fig_name, 
                               title=title, x_label=x_label,y_label=y_label)
        
    def plot_marginal_pdf_uslc(self):
        """
        TBD
        """
        fig_name = 'marginal_pdf_uslc'
        title = r'Upstreamline concentration distribution $f(\log(C_{u}))'
        x_label = r'Upstreamline concentration  $C_{u}$ [lines/m]'
        y_label = r'Probability density  $f(\log(C_{u}))$'
        try:
            marginal_distbn = self.analysis.mpdf_uslc
        except:
            self.print('"'+title+'" not computed: cannot plot')
            return
        self.plot_marginal_pdf(marginal_distbn, fig_name=fig_name, 
                               title=title, x_label=x_label,y_label=y_label)

    def plot_joint_pdf(self, bivariate_distribution, mx_distbn=None, my_distbn=None,
                       fig_name=None, title='', swap_xy=False, do_nl_trend=False,
                       x_label='', y_label='', 
                       xsym_label = r'$L_m^{*}$', ysym_label = r'$\sqrt{A_e^*}$', 
                       do_plot_mode=True, do_overlay_histogram=False,
                       do_legend=True):
        """
        TBD
          
        Args:
            TBD (TBD): 
        
        TBD
    
        Returns:
            TBD: 
            TBD
        """
        window_title=fig_name

        # Preparation
        if not swap_xy:
            x_mesh = bivariate_distribution.x_mesh
            y_mesh = bivariate_distribution.y_mesh
            x_vec  = bivariate_distribution.x_mesh[:,0]
            y_vec  = bivariate_distribution.y_mesh[0,:]
        else:
            x_mesh = bivariate_distribution.y_mesh
            y_mesh = bivariate_distribution.x_mesh
            x_vec  = bivariate_distribution.y_mesh[0,:]
            y_vec  = bivariate_distribution.x_mesh[:,0]
        mode_xy = bivariate_distribution.mode_xy
        x_min = x_mesh.min()
        x_max = x_mesh.max()
        y_min = y_mesh.min()
        y_max = y_mesh.max()
        
        # Create mpl figure
        fig,axes = self._new_figure(window_title=window_title, title=title)
        axes.set_xscale('log')
        axes.set_yscale('log')
        legend = []

        # Pdfs
        kde_pdf      = bivariate_distribution.pdf.copy()
        if swap_xy:
            kde_pdf  = kde_pdf.T
        kde_pdf = np.power(kde_pdf,self.joint_distbn_viz_scale)

        if do_overlay_histogram:
            from skimage.transform import downscale_local_mean
            downscale_factor = 2
            kde_hist    = downscale_local_mean(bivariate_distribution.histogram.copy(),
                                               (downscale_factor,downscale_factor))
            kde_hist[kde_hist>0] = 1
            kde_hist = np.ma.masked_where(kde_hist==0,kde_hist)
            n_hist_bins = bivariate_distribution.n_hist_bins
            x_hist_vec  = np.exp(np.linspace(np.log(x_min),np.log(x_max),
                                             n_hist_bins//downscale_factor))
            y_hist_vec  = np.exp(np.linspace(np.log(y_min),np.log(y_max),
                                             n_hist_bins//downscale_factor))        
            if swap_xy:
                kde_hist = kde_hist.T
        
        # Plot bivariate pdf - distorted for emphasis as appropriate
        axes.pcolormesh(x_vec,y_vec,kde_pdf.T, cmap='GnBu', 
                        antialiased=True, shading='gouraud')
        if do_overlay_histogram:
            axes.pcolormesh(x_hist_vec,y_hist_vec,kde_hist.T, cmap='gray', 
                            antialiased=False,alpha=0.5)
        axes.contour(x_vec,y_vec, kde_pdf.T, self.joint_distbn_n_contours,
                     colors='k',linewidths=1,alpha=0.5, antialiased=True)
        
        # Plot thresholds
        if do_legend:
            try:
                legend += [r'threshold '+xsym_label
                           +' = {:.0f}m'.format(np.round(
                            mx_distbn.channel_threshold_x,0))]
                legend += [r'threshold '+ysym_label
                           +' = {:.0f}m'.format(np.round(
                            my_distbn.channel_threshold_x,0))]
                if not swap_xy:
                    axes.plot(x_vec*0+mx_distbn.channel_threshold_x,y_vec,
                              '--', color='blue',linewidth=3,alpha=1.0)
                    axes.plot(x_vec,y_vec*0+my_distbn.channel_threshold_x,
                              ':', color='navy',linewidth=3,alpha=1.0)
                else:
    #                 pdebug('swapping xy')
                    axes.plot(x_vec*0+mx_distbn.channel_threshold_x,y_vec,
                              ':', color='navy',linewidth=3,alpha=1.0)
                    axes.plot(x_vec,y_vec*0+my_distbn.channel_threshold_x,
                              '--', color='blue',linewidth=3,alpha=1.0)
                do_extras = True  
            except:
                print('Problem with channel threshold')
                do_extras = False
        else:
            do_extras = False

        if do_extras:
            # Plot cross @ hillslope mode
            cross_alpha=0.6
            mode_idx = 0
            if not swap_xy:
                mode_xy = mode_xy
            else:
                mode_xy = np.flipud(mode_xy)
            if do_plot_mode:
                (mx,mxc,msx,mewx,mi,msi,mewi,mc,msc,ma) = self.joint_distbn_markers
                if mode_xy is not None:
                    legend += ['_no_legend_']
                    axes.plot(mode_xy[0],mode_xy[1],mi,ms=msx,mew=mewx)
                    legend += ['hillslope mode']
                    axes.plot(mode_xy[0],mode_xy[1],mx,color=mxc,ms=msi,mew=mewi)
                     
            # Linear trend x=y
    #         h_grad = mode_xy[1]/mode_xy[0]
            h_grad = 1.0
            if not swap_xy:
                h_grad_str = '{0:1.1}'.format(h_grad)
                legend += [r'hillslope  $y =$'+'$x$']
                x = x_vec
            else:
                h_grad_str = '{0:0.2}'.format(h_grad)
                legend += [r'hillslope  $y =$'+'$x$']
                x = x_vec
            axes.plot(x,x*h_grad,'-.', color='crimson',linewidth=2,alpha=0.7)
    
            # Nonlinear trend y=x^n
            if do_nl_trend:
                h_grad = mode_xy[1]/mode_xy[0]
                h_grad = 1.0
                if not swap_xy:
                    h_grad_str = '{0:1.1}'.format(h_grad)
                    legend += [r'channel  $y =$'+'$x^{3/2}$']
                    x = x_vec
                    axes.plot(x,np.power(x,1.5),'--', 
                              color='magenta',linewidth=2,alpha=0.7)
                else:
                    h_grad_str = '{0:0.2}'.format(h_grad)
                    legend += [r'channel  $y =$'+'${x^{1/3}$']
                    x = x_vec
                    axes.plot(x,np.power(x,2.0/3),'--', #np.power(1.5,0.5)*
                              color='magenta',linewidth=2,alpha=0.7)

        # Presentation
#         loc = 'lower right'
        if do_legend:
            loc = 'upper left'
            with warnings.catch_warnings():
                warnings.simplefilter("ignore")
                axes.legend(legend, loc=loc,fontsize=12,framealpha=0.97)
        axes.xaxis.set_major_locator(ticker.LogLocator(base=10.0, numticks=15))
        axes.yaxis.set_major_locator(ticker.LogLocator(base=10.0, numticks=15))
        if swap_xy:
            axes.set_xlabel(y_label)
            axes.set_ylabel(x_label)
        else:
            axes.set_xlabel(x_label)
            axes.set_ylabel(y_label)
        x_ticks = self._choose_ticks(x_min,x_max)
        y_ticks = self._choose_ticks(y_min,y_max)
        plt.xticks(x_ticks,x_ticks)
        plt.yticks(y_ticks,y_ticks)
        try:
            axes.set_xlim(left=0.999,right=x_max*1.001)
            axes.set_ylim(bottom=y_min*0.999,top=y_max*1.001)
        except:
            self.print('x,y min,max not provided')
        axes.grid(color='gray', linestyle='dotted', linewidth=0.5, which='both')
        
        # Push to screen
        self._force_display(fig)
        self._record_fig(fig_name,fig)

    def plot_joint_pdf_dsla_usla(self, do_legend=False):
        """
        TBD
        """
        fig_name = 'joint_pdf_dsla_usla'
        title = r'Streamline length distribution$ f(L_{md},L_{mu})$'
        x_label = r'Downstreamline mean length  $L_{md}$ [m]'
        y_label = r'Upstreamline mean length  $L_{mu}$ [m]'
        xsym_label = r'$L_{md}^{*}$'
        ysym_label = r'$L_{mu}^{*}$'
        try:
            joint_distbn = self.analysis.jpdf_dsla_usla
        except:
            self.print('"'+title+'" not computed: cannot plot')
            return
        try:
            mx_distbn    = self.analysis.mpdf_dsla
            my_distbn    = self.analysis.mpdf_usla
        except:
            mx_distbn    = None
            my_distbn    = None

        self.plot_joint_pdf(joint_distbn, mx_distbn=mx_distbn, my_distbn=my_distbn,
                            fig_name=fig_name, 
                            title=title, x_label=x_label, y_label=y_label,
                            xsym_label=xsym_label, ysym_label=ysym_label,
                            do_legend=do_legend)
            
    def plot_joint_pdf_usla_uslt(self):
        """
        TBD
        """
        fig_name = 'joint_pdf_usla_uslt'
        title = r'Upstreamline length distribution $f(\log(L_{mu}),\log\sqrt{A_{eu}})$ '
        x_label = r'Upstreamline mean length  $L_{mu}$ [m]'
        y_label = r'Upstreamline root equiv area  $\sqrt{A_{eu}}$ [m]'
        xsym_label = r'$L_{mu}^{*}$'
        ysym_label = r'$\sqrt{A_{eu}^*}$'
        try:
            joint_distbn = self.analysis.jpdf_usla_uslt
            mx_distbn    = self.analysis.mpdf_usla
            my_distbn    = self.analysis.mpdf_uslt
        except:
            self.print('"'+title+'" not computed: cannot plot')
            return
        self.plot_joint_pdf(joint_distbn, mx_distbn=mx_distbn, my_distbn=my_distbn,
                            fig_name=fig_name, do_nl_trend=True,
                            title=title, x_label=x_label, y_label=y_label,
                            xsym_label=xsym_label, ysym_label=ysym_label,
                            do_plot_mode=True)

    def plot_joint_pdf_dsla_dslt(self):
        """
        TBD
        """
        fig_name = 'joint_pdf_dsla_dslt'
        title = r'Downstreamline length distribution $f(\log(L_{md}),\log\sqrt{A_{ed}})$'
        x_label = r'Downstreamline mean length  $L_{md}$ [m]'
        y_label = r'Downstreamline root equiv area  $\sqrt{A_{ed}}$ [m]'
        xsym_label = r'$L_{md}^{*}$'
        ysym_label = r'$\sqrt{A_{ed}^*}$'
        try:
            joint_distbn = self.analysis.jpdf_dsla_dslt
            mx_distbn    = self.analysis.mpdf_dsla
            my_distbn    = self.analysis.mpdf_dslt
        except:
            self.print('"'+title+'" not computed: cannot plot')
            return
        self.plot_joint_pdf(joint_distbn, mx_distbn=mx_distbn, my_distbn=my_distbn,
                            fig_name=fig_name, swap_xy=False, do_nl_trend=True,
                            title=title, x_label=x_label, y_label=y_label,
                            xsym_label=xsym_label, ysym_label=ysym_label,
                            do_plot_mode=True)

    def plot_joint_pdf_dslt_dsla(self,do_legend=True):
        """
        TBD
        """
        fig_name = 'joint_pdf_dslt_dsla'
        title = r'Downstreamline length distribution $f(\log\sqrt{A_{ed}},\log(L_{md}))$'
        x_label = r'Downstreamline root equiv area  $\sqrt{A_{ed}}$ [m]'
        y_label = r'Downstreamline mean length  $L_{md}$ [m]'
        xsym_label = r'$\sqrt{A_{ed}^*}$'
        ysym_label = r'$L_{md}^{*}$'
        try:
            joint_distbn = self.analysis.jpdf_dsla_dslt
            mx_distbn    = self.analysis.mpdf_dsla
            my_distbn    = self.analysis.mpdf_dslt
        except:
            self.print('"'+title+'" not computed: cannot plot')
            return
        self.plot_joint_pdf(joint_distbn, mx_distbn=mx_distbn, my_distbn=my_distbn,
                            fig_name=fig_name, swap_xy=True,
                            title=title, x_label=x_label, y_label=y_label,
                            xsym_label=xsym_label, ysym_label=ysym_label,
                            do_plot_mode=True, do_legend=do_legend)

    def plot_joint_pdf_uslt_dslt(self,swap_xy=False,do_legend=False):
        """
        TBD
        """
        fig_name = 'joint_pdf_uslt_dslt'
        title = r'Streamline root equiv area distribution $f\sqrt{A_{eu}},\sqrt{A_{ed}}$'
        x_label = r'Upstreamline root equiv area  $\sqrt{A_{eu}}$ [m]'
        y_label = r'Downstreamline root equiv area  $\sqrt{A_{ed}}$ [m]'
        xsym_label = r'$\sqrt{A_{eu}^*}$'
        ysym_label = r'$\sqrt{A_{ed}^*}$'
        try:
            joint_distbn = self.analysis.jpdf_uslt_dslt
            mx_distbn    = self.analysis.mpdf_uslt
            my_distbn    = self.analysis.mpdf_dslt
        except:
            self.print('"'+title+'" not computed: cannot plot')
            return
        self.plot_joint_pdf(joint_distbn, mx_distbn=mx_distbn, my_distbn=my_distbn,
                            fig_name=fig_name,
                            title=title, x_label=x_label, y_label=y_label,
                            xsym_label=xsym_label, ysym_label=ysym_label,
                            swap_xy=swap_xy, do_legend=do_legend)

    def plot_joint_pdf_usla_uslc(self):
        """
        TBD
        """
        fig_name = 'joint_pdf_usla_uslc'
        title = r'Upstreamline distribution $f(\log(L_{mu}),\log(C_{u}))$'
        x_label = r'Upstreamline mean length  $L_{mu}$ [m]'
        y_label = r'Upstreamline concentration  $C_{u}$ [lines/m]'
        xsym_label = r'$L_{mu}^{*}$'
        ysym_label = r'$C_{u}^*$'
        try:
            joint_distbn = self.analysis.jpdf_usla_uslc
            mx_distbn    = self.analysis.mpdf_usla
            my_distbn    = self.analysis.mpdf_uslc
        except:
            self.print('"'+title+'" not computed: cannot plot')
            return
        self.plot_joint_pdf(joint_distbn, mx_distbn=mx_distbn, my_distbn=my_distbn,
                            fig_name=fig_name, do_nl_trend=True,
                            title=title, x_label=x_label, y_label=y_label,
                            xsym_label=xsym_label, ysym_label=ysym_label,
                            do_plot_mode=True)

    def plot_joint_pdf_dsla_dslc(self):
        """
        TBD
        """
        fig_name = 'joint_pdf_dsla_dslc'
        title = r'Downstreamline distribution $f(\log(L_{md}),\log(C_{d}))$'
        x_label = r'Downstreamline mean length  $L_{md}$ [m]'
        y_label = r'Downstreamline concentration  $C_{d}$ [lines/m]'
        xsym_label = r'$L_{md}^{*}$'
        ysym_label = r'$C_{d}^*$'
        try:
            joint_distbn = self.analysis.jpdf_dsla_dslc
            mx_distbn    = self.analysis.mpdf_dsla
            my_distbn    = self.analysis.mpdf_dslc
        except:
            self.print('"'+title+'" not computed: cannot plot')
            return
        self.plot_joint_pdf(joint_distbn, mx_distbn=mx_distbn, my_distbn=my_distbn,
                            fig_name=fig_name, do_nl_trend=True,
                            title=title, x_label=x_label, y_label=y_label,
                            xsym_label=xsym_label, ysym_label=ysym_label,
                            do_plot_mode=True)

    def plot_joint_pdf_dslt_dslc(self):
        """
        TBD
        """
        fig_name = 'joint_pdf_dslt_dslc'
        title = r'Downstreamline distribution $f(\sqrt{A_{ed}},\log(C_{d}))$'
        x_label = r'Downstreamline root equiv area  $\sqrt{A_{ed}}$ [m]'
        y_label = r'Downstreamline concentration  $C_{d}$ [lines/m]'
        xsym_label = r'$\sqrt{A_{ed}^*}$'
        ysym_label = r'$C_{d}^*$'
        try:
            joint_distbn = self.analysis.jpdf_dslt_dslc
            mx_distbn    = self.analysis.mpdf_dslt
            my_distbn    = self.analysis.mpdf_dslc
        except:
            self.print('"'+title+'" not computed: cannot plot')
            return
        self.plot_joint_pdf(joint_distbn, mx_distbn=mx_distbn, my_distbn=my_distbn,
                            fig_name=fig_name,
                            title=title, x_label=x_label, y_label=y_label,
                            xsym_label=xsym_label, ysym_label=ysym_label,
                            do_plot_mode=True)

    def plot_joint_pdf_uslc_dslc(self):
        """
        TBD
        """
        fig_name = 'joint_pdf_uslc_dslc'
        title = r'Streamline concentration distribution $f(\log(C_{u}),\log(C_{d}))$'
        x_label = r'Upstreamline concentration  $C_{u}$ [lines/m]'
        y_label = r'Downstreamline concentration  $C_{d}$ [lines/m]'
        xsym_label = r'$C_{u}^*$'
        ysym_label = r'$C_{d}^*$'
        try:
            joint_distbn = self.analysis.jpdf_uslc_dslc
            mx_distbn    = self.analysis.mpdf_uslc
            my_distbn    = self.analysis.mpdf_dslc
        except:
            self.print('"'+title+'" not computed: cannot plot')
            return
        self.plot_joint_pdf(joint_distbn, mx_distbn=mx_distbn, my_distbn=my_distbn,
                            fig_name=fig_name,
                            title=title, x_label=x_label, y_label=y_label,
                            xsym_label=xsym_label, ysym_label=ysym_label)

    @staticmethod
    def _choose_ticks(x_min,x_max):
        xtick_range= [int(np.round(np.log10(x_min))),int(np.round(np.log10(x_max)))+1] 
        xtick_range3= [int(np.round(np.log10(x_min))),
                       min(3,int(np.round(np.log10(x_max)))+1)] 
        x_ticks = [10**x for x in list(range(*xtick_range,1))] \
                + [( float(str(10**x).replace('1','3'))
                     if x<0 else int(str(10**x).replace('1','3')) )
                                            for x in list(range(*xtick_range3,1))] 
#                 + [( float(str(10**x).replace('1','6'))
#                      if x<0 else int(str(10**x).replace('1','6')) )
#                                             for x in list(range(*xtick_range,1))]
        x_ticks.sort()
        return x_ticks
        self._force_display(fig)
        self._record_fig(fig_name,fig)