# -*- coding: utf-8 -*-
# Copyright 2007-2016 The HyperSpy developers
#
# This file is part of  HyperSpy.
#
#  HyperSpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
#  HyperSpy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with  HyperSpy.  If not, see <http://www.gnu.org/licenses/>.

import copy
import itertools
import textwrap
from traits import trait_base
import matplotlib.pyplot as plt
import matplotlib as mpl
from mpl_toolkits.axes_grid1 import make_axes_locatable
from matplotlib.backend_bases import key_press_handler
import warnings
import numpy as np
from distutils.version import LooseVersion
import logging

import hyperspy as hs

_logger = logging.getLogger(__name__)

def contrast_stretching(data, saturated_pixels):
    """Calculate bounds that leaves out a given percentage of the data.

    Parameters
    ----------
    data: numpy array
    saturated_pixels: scalar, None
        The percentage of pixels that are left out of the bounds.  For example,
        the low and high bounds of a value of 1 are the 0.5% and 99.5%
        percentiles. It must be in the [0, 100] range. If None, set the value
        to 0.

    Returns
    -------
    vmin, vmax: scalar
        The low and high bounds

    Raises
    ------
    ValueError if the value of `saturated_pixels` is out of the valid range.

    """
    # Sanity check
    if saturated_pixels is None:
        saturated_pixels = 0
    if not 0 <= saturated_pixels <= 100:
        raise ValueError(
            "saturated_pixels must be a scalar in the range[0, 100]")
    vmin = np.nanpercentile(data, saturated_pixels / 2.)
    vmax = np.nanpercentile(data, 100 - saturated_pixels / 2.)
    return vmin, vmax

MPL_DIVERGING_COLORMAPS = [
    "BrBG",
    "bwr",
    "coolwarm",
    "PiYG",
    "PRGn",
    "PuOr",
    "RdBu",
    "RdGy",
    "RdYIBu",
    "RdYIGn",
    "seismic",
    "Spectral", ]
# Add reversed colormaps
MPL_DIVERGING_COLORMAPS += [cmap + "_r" for cmap in MPL_DIVERGING_COLORMAPS]

def centre_colormap_values(vmin, vmax):
    """Calculate vmin and vmax to set the colormap midpoint to zero.

    Parameters
    ----------
    vmin, vmax : scalar
        The range of data to display.

    Returns
    -------
    cvmin, cvmax : scalar
        The values to obtain a centre colormap.

    """

    absmax = max(abs(vmin), abs(vmax))
    return -absmax, absmax

def create_figure(window_title=None,
                  _on_figure_window_close=None,
                  disable_xyscale_keys=False,
                  **kwargs):
    """Create a matplotlib figure.

    This function adds the possibility to execute another function
    when the figure is closed and to easily set the window title. Any
    keyword argument is passed to the plt.figure function

    Parameters
    ----------
    window_title : string
    _on_figure_window_close : function
    disable_xyscale_keys : bool, disable the `k`, `l` and `L` shortcuts which
    toggle the x or y axis between linear and log scale.

    Returns
    -------

    fig : plt.figure

    """
    fig = plt.figure(**kwargs)
    if window_title is not None:
        # remove non-alphanumeric characters to prevent file saving problems
        # This is a workaround for:
        #   https://github.com/matplotlib/matplotlib/issues/9056
        reserved_characters = r'<>"/\|?*'
        for c in reserved_characters:
            window_title = window_title.replace(c, '')
        window_title = window_title.replace('\n', ' ')
        window_title = window_title.replace(':', ' -')
        fig.canvas.set_window_title(window_title)
    if disable_xyscale_keys and hasattr(fig.canvas, 'toolbar'):
        # hack the `key_press_handler` to disable the `k`, `l`, `L` shortcuts
        manager = fig.canvas.manager
        fig.canvas.mpl_disconnect(manager.key_press_handler_id)
        manager.key_press_handler_id = manager.canvas.mpl_connect(
            'key_press_event',
            lambda event: key_press_handler_custom(event, manager.canvas))
    if _on_figure_window_close is not None:
        on_figure_window_close(fig, _on_figure_window_close)
    return fig

def key_press_handler_custom(event, canvas):
    if event.key not in ['k', 'l', 'L']:
        key_press_handler(event, canvas, canvas.manager.toolbar)

def on_figure_window_close(figure, function):
    """Connects a close figure signal to a given function.

    Parameters
    ----------

    figure : mpl figure instance
    function : function

    """
    def function_wrapper(evt):
        function()

    figure.canvas.mpl_connect('close_event', function_wrapper)

def plot_RGB_map(im_list, normalization='single', dont_plot=False):
    """Plot 2 or 3 maps in RGB.

    Parameters
    ----------
    im_list : list of Signal2D instances
    normalization : {'single', 'global'}
    dont_plot : bool

    Returns
    -------
    array: RGB matrix

    """
#    from widgets import cursors
    height, width = im_list[0].data.shape[:2]
    rgb = np.zeros((height, width, 3))
    rgb[:, :, 0] = im_list[0].data.squeeze()
    rgb[:, :, 1] = im_list[1].data.squeeze()
    if len(im_list) == 3:
        rgb[:, :, 2] = im_list[2].data.squeeze()
    if normalization == 'single':
        for i in range(len(im_list)):
            rgb[:, :, i] /= rgb[:, :, i].max()
    elif normalization == 'global':
        rgb /= rgb.max()
    rgb = rgb.clip(0, rgb.max())
    if not dont_plot:
        figure = plt.figure()
        ax = figure.add_subplot(111)
        ax.frameon = False
        ax.set_axis_off()
        ax.imshow(rgb, interpolation='nearest')
#        cursors.set_mpl_ax(ax)
        figure.canvas.draw_idle()
    else:
        return rgb

def subplot_parameters(fig):
    """Returns a list of the subplot parameters of a mpl figure.

    Parameters
    ----------
    fig : mpl figure

    Returns
    -------
    tuple : (left, bottom, right, top, wspace, hspace)

    """
    wspace = fig.subplotpars.wspace
    hspace = fig.subplotpars.hspace
    left = fig.subplotpars.left
    right = fig.subplotpars.right
    top = fig.subplotpars.top
    bottom = fig.subplotpars.bottom
    return left, bottom, right, top, wspace, hspace

class ColorCycle:
    _color_cycle = [mpl.colors.colorConverter.to_rgba(color) for color
                    in ('b', 'g', 'r', 'c', 'm', 'y', 'k')]

    def __init__(self):
        self.color_cycle = copy.copy(self._color_cycle)

    def __call__(self):
        if not self.color_cycle:
            self.color_cycle = copy.copy(self._color_cycle)
        return self.color_cycle.pop(0)

def plot_signals(signal_list, sync=True, navigator="auto",
                 navigator_list=None, **kwargs):
    """Plot several signals at the same time.

    Parameters
    ----------
    signal_list : list of BaseSignal instances
        If sync is set to True, the signals must have the
        same navigation shape, but not necessarily the same signal shape.
    sync : True or False, default "True"
        If True: the signals will share navigation, all the signals
        must have the same navigation shape for this to work, but not
        necessarily the same signal shape.
    navigator : {"auto", None, "spectrum", "slider", BaseSignal}, default "auto"
        See signal.plot docstring for full description
    navigator_list : {List of navigator arguments, None}, default None
        Set different navigator options for the signals. Must use valid
        navigator arguments: "auto", None, "spectrum", "slider", or a
        hyperspy Signal. The list must have the same size as signal_list.
        If None, the argument specified in navigator will be used.
    **kwargs
        Any extra keyword arguments are passed to each signal `plot` method.

    Example
    -------

    >>> s_cl = hs.load("coreloss.dm3")
    >>> s_ll = hs.load("lowloss.dm3")
    >>> hs.plot.plot_signals([s_cl, s_ll])

    Specifying the navigator:

    >>> s_cl = hs.load("coreloss.dm3")
    >>> s_ll = hs.load("lowloss.dm3")
    >>> hs.plot.plot_signals([s_cl, s_ll], navigator="slider")

    Specifying the navigator for each signal:

    >>> s_cl = hs.load("coreloss.dm3")
    >>> s_ll = hs.load("lowloss.dm3")
    >>> s_edx = hs.load("edx.dm3")
    >>> s_adf = hs.load("adf.dm3")
    >>> hs.plot.plot_signals(
            [s_cl, s_ll, s_edx], navigator_list=["slider",None,s_adf])

    """

    import hyperspy.signal

    if navigator_list:
        if not (len(signal_list) == len(navigator_list)):
            raise ValueError(
                "signal_list and navigator_list must"
                " have the same size")

    if sync:
        axes_manager_list = []
        for signal in signal_list:
            axes_manager_list.append(signal.axes_manager)

        if not navigator_list:
            navigator_list = []
        if navigator is None:
            navigator_list.extend([None] * len(signal_list))
        elif isinstance(navigator, hyperspy.signal.BaseSignal):
            navigator_list.append(navigator)
            navigator_list.extend([None] * (len(signal_list) - 1))
        elif navigator == "slider":
            navigator_list.append("slider")
            navigator_list.extend([None] * (len(signal_list) - 1))
        elif navigator == "spectrum":
            navigator_list.extend(["spectrum"] * len(signal_list))
        elif navigator == "auto":
            navigator_list.extend(["auto"] * len(signal_list))
        else:
            raise ValueError(
                "navigator must be one of \"spectrum\",\"auto\","
                " \"slider\", None, a Signal instance")

        # Check to see if the spectra have the same navigational shapes
        temp_shape_first = axes_manager_list[0].navigation_shape
        for i, axes_manager in enumerate(axes_manager_list):
            temp_shape = axes_manager.navigation_shape
            if not (temp_shape_first == temp_shape):
                raise ValueError(
                    "The spectra does not have the same navigation shape")
            axes_manager_list[i] = axes_manager.deepcopy()
            if i > 0:
                for axis0, axisn in zip(axes_manager_list[0].navigation_axes,
                                        axes_manager_list[i].navigation_axes):
                    axes_manager_list[i]._axes[axisn.index_in_array] = axis0
            del axes_manager

        for signal, navigator, axes_manager in zip(signal_list,
                                                   navigator_list,
                                                   axes_manager_list):
            signal.plot(axes_manager=axes_manager,
                        navigator=navigator,
                        **kwargs)

    # If sync is False
    else:
        if not navigator_list:
            navigator_list = []
            navigator_list.extend([navigator] * len(signal_list))
        for signal, navigator in zip(signal_list, navigator_list):
            signal.plot(navigator=navigator,
                        **kwargs)

def _make_heatmap_subplot(spectra):
    from hyperspy._signals.signal2d import Signal2D
    im = Signal2D(spectra.data, axes=spectra.axes_manager._get_axes_dicts())
    im.metadata.General.title = spectra.metadata.General.title
    im.plot()
    return im._plot.signal_plot.ax

def set_xaxis_lims(mpl_ax, hs_axis):
    """
    Set the matplotlib axis limits to match that of a HyperSpy axis

    Parameters
    ----------
    mpl_ax : :class:`matplotlib.axis.Axis`
        The ``matplotlib`` axis to change
    hs_axis : :class:`~hyperspy.axes.DataAxis`
        The data axis that contains the values that control the scaling
    """
    x_axis_lower_lim = hs_axis.axis[0]
    x_axis_upper_lim = hs_axis.axis[-1]
    mpl_ax.set_xlim(x_axis_lower_lim, x_axis_upper_lim)

def _make_overlap_plot(spectra, ax, color="blue", line_style='-'):
    if isinstance(color, str):
        color = [color] * len(spectra)
    if isinstance(line_style, str):
        line_style = [line_style] * len(spectra)
    for spectrum_index, (spectrum, color, line_style) in enumerate(
            zip(spectra, color, line_style)):
        x_axis = spectrum.axes_manager.signal_axes[0]
        spectrum = _transpose_if_required(spectrum, 1)
        ax.plot(x_axis.axis, spectrum.data, color=color, ls=line_style)
        set_xaxis_lims(ax, x_axis)
    _set_spectrum_xlabel(spectra if isinstance(spectra, hs.signals.BaseSignal)
                         else spectra[-1], ax)
    ax.set_ylabel('Intensity')
    ax.autoscale(tight=True)

def _make_cascade_subplot(
        spectra, ax, color="blue", line_style='-', padding=1):
    max_value = 0
    for spectrum in spectra:
        spectrum_yrange = (np.nanmax(spectrum.data) -
                           np.nanmin(spectrum.data))
        if spectrum_yrange > max_value:
            max_value = spectrum_yrange
    if isinstance(color, str):
        color = [color] * len(spectra)
    if isinstance(line_style, str):
        line_style = [line_style] * len(spectra)
    for spectrum_index, (spectrum, color, line_style) in enumerate(
            zip(spectra, color, line_style)):
        x_axis = spectrum.axes_manager.signal_axes[0]
        spectrum = _transpose_if_required(spectrum, 1)
        data_to_plot = ((spectrum.data - spectrum.data.min()) /
                        float(max_value) + spectrum_index * padding)
        ax.plot(x_axis.axis, data_to_plot, color=color, ls=line_style)
        set_xaxis_lims(ax, x_axis)
    _set_spectrum_xlabel(spectra if isinstance(spectra, hs.signals.BaseSignal)
                         else spectra[-1], ax)
    ax.set_yticks([])
    ax.autoscale(tight=True)

def _plot_spectrum(spectrum, ax, color="blue", line_style='-'):
    x_axis = spectrum.axes_manager.signal_axes[0]
    ax.plot(x_axis.axis, spectrum.data, color=color, ls=line_style)
    set_xaxis_lims(ax, x_axis)

def _set_spectrum_xlabel(spectrum, ax):
    x_axis = spectrum.axes_manager.signal_axes[0]
    ax.set_xlabel("%s (%s)" % (x_axis.name, x_axis.units))

def _transpose_if_required(signal, expected_dimension):
    # EDS profiles or maps have signal dimension = 0 and navigation dimension
    # 1 or 2. For convenience transpose the signal if possible
    if (signal.axes_manager.signal_dimension == 0 and
        signal.axes_manager.navigation_dimension == expected_dimension):
        return signal.T
    else:
        return signal

def plot_images(images,
                cmap=None,
                no_nans=False,
                per_row=3,
                label='auto',
                labelwrap=30,
                suptitle=None,
                suptitle_fontsize=18,
                colorbar='multi',
                centre_colormap="auto",
                saturated_pixels=0,
                scalebar=None,
                scalebar_color='white',
                axes_decor='all',
                padding=None,
                tight_layout=False,
                aspect='auto',
                min_asp=0.1,
                namefrac_thresh=0.4,
                fig=None,
                vmin=None,
                vmax=None,
                *args,
                **kwargs):
    """Plot multiple images as sub-images in one figure.

    Extra keyword arguments are passed to `matplotlib.figure`.

    Parameters
    ----------
    images : list of Signal2D or BaseSignal
        `images` should be a list of Signals to plot. For `BaseSignal` with
        navigation dimensions 2 and signal dimension 0, the signal will be
        tranposed to form a `Signal2D`.
        Multi-dimensional images will have each plane plotted as a separate
        image.
        If any signal shape is not suitable, a ValueError will be raised.
    cmap : matplotlib colormap, list, or ``'mpl_colors'``, *optional*
        The colormap used for the images, by default read from ``pyplot``.
        A list of colormaps can also be provided, and the images will
        cycle through them. Optionally, the value ``'mpl_colors'`` will
        cause the cmap to loop through the default ``matplotlib``
        colors (to match with the default output of the
        :py:func:`~.drawing.utils.plot_spectra` method.
        Note: if using more than one colormap, using the ``'single'``
        option for ``colorbar`` is disallowed.
    no_nans : bool, optional
        If True, set nans to zero for plotting.
    per_row : int, optional
        The number of plots in each row
    label : None, str, or list of str, optional
        Control the title labeling of the plotted images.
        If None, no titles will be shown.
        If 'auto' (default), function will try to determine suitable titles
        using Signal2D titles, falling back to the 'titles' option if no good
        short titles are detected.
        Works best if all images to be plotted have the same beginning
        to their titles.
        If 'titles', the title from each image's metadata.General.title
        will be used.
        If any other single str, images will be labeled in sequence using
        that str as a prefix.
        If a list of str, the list elements will be used to determine the
        labels (repeated, if necessary).
    labelwrap : int, optional
        integer specifying the number of characters that will be used on
        one line
        If the function returns an unexpected blank figure, lower this
        value to reduce overlap of the labels between each figure
    suptitle : str, optional
        Title to use at the top of the figure. If called with label='auto',
        this parameter will override the automatically determined title.
    suptitle_fontsize : int, optional
        Font size to use for super title at top of figure
    colorbar : {'multi', None, 'single'}
        Controls the type of colorbars that are plotted.
        If None, no colorbar is plotted.
        If 'multi' (default), individual colorbars are plotted for each
        (non-RGB) image
        If 'single', all (non-RGB) images are plotted on the same scale,
        and one colorbar is shown for all
    centre_colormap : {"auto", True, False}
        If True the centre of the color scheme is set to zero. This is
        specially useful when using diverging color schemes. If "auto"
        (default), diverging color schemes are automatically centred.
    saturated_pixels: None, scalar or list of scalar, optional, default: 0
        If list of scalar, the length should match the number of images to
        show. If provide in the list, set the value to 0.
        The percentage of pixels that are left out of the bounds.  For
        example, the low and high bounds of a value of 1 are the 0.5% and
        99.5% percentiles. It must be in the [0, 100] range.
    scalebar : {None, 'all', list of ints}, optional
        If None (or False), no scalebars will be added to the images.
        If 'all', scalebars will be added to all images.
        If list of ints, scalebars will be added to each image specified.
    scalebar_color : str, optional
        A valid MPL color string; will be used as the scalebar color
    axes_decor : {'all', 'ticks', 'off', None}, optional
        Controls how the axes are displayed on each image; default is 'all'
        If 'all', both ticks and axis labels will be shown
        If 'ticks', no axis labels will be shown, but ticks/labels will
        If 'off', all decorations and frame will be disabled
        If None, no axis decorations will be shown, but ticks/frame will
    padding : None or dict, optional
        This parameter controls the spacing between images.
        If None, default options will be used
        Otherwise, supply a dictionary with the spacing options as
        keywords and desired values as values
        Values should be supplied as used in pyplot.subplots_adjust(),
        and can be:
            'left', 'bottom', 'right', 'top', 'wspace' (width),
            and 'hspace' (height)
    tight_layout : bool, optional
        If true, hyperspy will attempt to improve image placement in
        figure using matplotlib's tight_layout
        If false, repositioning images inside the figure will be left as
        an exercise for the user.
    aspect : str or numeric, optional
        If 'auto', aspect ratio is auto determined, subject to min_asp.
        If 'square', image will be forced onto square display.
        If 'equal', aspect ratio of 1 will be enforced.
        If float (or int/long), given value will be used.
    min_asp : float, optional
        Minimum aspect ratio to be used when plotting images
    namefrac_thresh : float, optional
        Threshold to use for auto-labeling. This parameter controls how
        much of the titles must be the same for the auto-shortening of
        labels to activate. Can vary from 0 to 1. Smaller values
        encourage shortening of titles by auto-labeling, while larger
        values will require more overlap in titles before activing the
        auto-label code.
    fig : mpl figure, optional
        If set, the images will be plotted to an existing MPL figure
    vmin, vmax : scalar or list of scalar, optional, default: None
        If list of scalar, the length should match the number of images to
        show.
        A list of scalar is not compatible with a single colorbar.
        See vmin, vmax of matplotlib.imshow() for more details.
    *args, **kwargs, optional
        Additional arguments passed to matplotlib.imshow()

    Returns
    -------
    axes_list : list
        a list of subplot axes that hold the images

    See Also
    --------
    plot_spectra : Plotting of multiple spectra
    plot_signals : Plotting of multiple signals
    plot_histograms : Compare signal histograms

    Notes
    -----
    `interpolation` is a useful parameter to provide as a keyword
    argument to control how the space between pixels is interpolated. A
    value of ``'nearest'`` will cause no interpolation between pixels.

    `tight_layout` is known to be quite brittle, so an option is provided
    to disable it. Turn this option off if output is not as expected,
    or try adjusting `label`, `labelwrap`, or `per_row`

    """
    def __check_single_colorbar(cbar):
        if cbar == 'single':
            raise ValueError('Cannot use a single colorbar with multiple '
                             'colormaps. Please check for compatible '
                             'arguments.')

    from hyperspy.drawing.widgets import ScaleBar
    from hyperspy.misc import rgb_tools
    from hyperspy.signal import BaseSignal

    # Check that we have a hyperspy signal
    im = [images] if not isinstance(images, (list, tuple)) else images
    for image in im:
        if not isinstance(image, BaseSignal):
            raise ValueError("`images` must be a list of image signals or a "
                             "multi-dimensional signal."
                             " " + repr(type(images)) + " was given.")

    # For list of EDS maps, transpose the BaseSignal
    if isinstance(images, (list, tuple)):
        images = [_transpose_if_required(image, 2) for image in images]

    # If input is >= 1D signal (e.g. for multi-dimensional plotting),
    # copy it and put it in a list so labeling works out as (x,y) when plotting
    if isinstance(images,
                  BaseSignal) and images.axes_manager.navigation_dimension > 0:
        images = [images._deepcopy_with_new_data(images.data)]

    n = 0
    for i, sig in enumerate(images):
        if sig.axes_manager.signal_dimension != 2:
            raise ValueError("This method only plots signals that are images. "
                             "The signal dimension must be equal to 2. "
                             "The signal at position " + repr(i) +
                             " was " + repr(sig) + ".")
        # increment n by the navigation size, or by 1 if the navigation size is
        # <= 0
        n += (sig.axes_manager.navigation_size
              if sig.axes_manager.navigation_size > 0
              else 1)

    # If no cmap given, get default colormap from pyplot:
    if cmap is None:
        cmap = [plt.get_cmap().name]
    elif cmap == 'mpl_colors':
        for n_color, c in enumerate(mpl.rcParams['axes.prop_cycle']):
            make_cmap(colors=['#000000', c['color']],
                      name='mpl{}'.format(n_color))
        cmap = ['mpl{}'.format(i) for i in
                range(len(mpl.rcParams['axes.prop_cycle']))]
        __check_single_colorbar(colorbar)
    # cmap is list, tuple, or something else iterable (but not string):
    elif hasattr(cmap, '__iter__') and not isinstance(cmap, str):
        try:
            cmap = [c.name for c in cmap]  # convert colormap to string
        except AttributeError:
            cmap = [c for c in cmap]   # c should be string if not colormap
        __check_single_colorbar(colorbar)
    elif isinstance(cmap, mpl.colors.Colormap):
        cmap = [cmap.name]   # convert single colormap to list with string
    elif isinstance(cmap, str):
        cmap = [cmap]  # cmap is single string, so make it a list
    else:
        # Didn't understand cmap input, so raise error
        raise ValueError('The provided cmap value was not understood. Please '
                         'check input values.')

    # If any of the cmaps given are diverging, and auto-centering, set the
    # appropriate flag:
    if centre_colormap == "auto":
        centre_colormaps = []
        for c in cmap:
            if c in MPL_DIVERGING_COLORMAPS:
                centre_colormaps.append(True)
            else:
                centre_colormaps.append(False)
    # if it was True, just convert to list
    elif centre_colormap:
        centre_colormaps = [True]
    # likewise for false
    elif not centre_colormap:
        centre_colormaps = [False]

    # finally, convert lists to cycle generators for adaptive length:
    centre_colormaps = itertools.cycle(centre_colormaps)
    cmap = itertools.cycle(cmap)

    def _check_arg(arg, default_value, arg_name):
        if isinstance(arg, list):
            if len(arg) != n:
                _logger.warning('The provided {} values are ignored because the '
                                'length of the list does not match the number of '
                                'images'.format(arg_name))
                arg = [default_value] * n
        else:
            arg = [arg] * n
        return arg
    vmin = _check_arg(vmin, None, 'vmin')
    vmax = _check_arg(vmax, None, 'vmax')
    saturated_pixels = _check_arg(saturated_pixels, 0, 'saturated_pixels')

    # Sort out the labeling:
    div_num = 0
    all_match = False
    shared_titles = False
    user_labels = False

    if label is None:
        pass
    elif label == 'auto':
        # Use some heuristics to try to get base string of similar titles

        label_list = [x.metadata.General.title for x in images]

        # Find the shortest common string between the image titles
        # and pull that out as the base title for the sequence of images
        # array in which to store arrays
        res = np.zeros((len(label_list), len(label_list[0]) + 1))
        res[:, 0] = 1

        # j iterates the strings
        for j in range(len(label_list)):
            # i iterates length of substring test
            for i in range(1, len(label_list[0]) + 1):
                # stores whether or not characters in title match
                res[j, i] = label_list[0][:i] in label_list[j]

        # sum up the results (1 is True, 0 is False) and create
        # a substring based on the minimum value (this will be
        # the "smallest common string" between all the titles
        if res.all():
            basename = label_list[0]
            div_num = len(label_list[0])
            all_match = True
        else:
            div_num = int(min(np.sum(res, 1)))
            basename = label_list[0][:div_num - 1]
            all_match = False

        # trim off any '(' or ' ' characters at end of basename
        if div_num > 1:
            while True:
                if basename[len(basename) - 1] == '(':
                    basename = basename[:-1]
                elif basename[len(basename) - 1] == ' ':
                    basename = basename[:-1]
                else:
                    break

        # namefrac is ratio of length of basename to the image name
        # if it is high (e.g. over 0.5), we can assume that all images
        # share the same base
        if len(label_list[0]) > 0:
            namefrac = float(len(basename)) / len(label_list[0])
        else:
            # If label_list[0] is empty, it means there was probably no
            # title set originally, so nothing to share
            namefrac = 0

        if namefrac > namefrac_thresh:
            # there was a significant overlap of label beginnings
            shared_titles = True
            # only use new suptitle if one isn't specified already
            if suptitle is None:
                suptitle = basename

        else:
            # there was not much overlap, so default back to 'titles' mode
            shared_titles = False
            label = 'titles'
            div_num = 0

    elif label == 'titles':
        # Set label_list to each image's pre-defined title
        label_list = [x.metadata.General.title for x in images]

    elif isinstance(label, str):
        # Set label_list to an indexed list, based off of label
        label_list = [label + " " + repr(num) for num in range(n)]

    elif isinstance(label, list) and all(
            isinstance(x, str) for x in label):
        label_list = label
        user_labels = True
        # If list of labels is longer than the number of images, just use the
        # first n elements
        if len(label_list) > n:
            del label_list[n:]
        if len(label_list) < n:
            label_list *= (n // len(label_list)) + 1
            del label_list[n:]

    else:
        raise ValueError("Did not understand input of labels.")

    # Determine appropriate number of images per row
    rows = int(np.ceil(n / float(per_row)))
    if n < per_row:
        per_row = n

    # Set overall figure size and define figure (if not pre-existing)
    if fig is None:
        k = max(plt.rcParams['figure.figsize']) / max(per_row, rows)
        f = plt.figure(figsize=(tuple(k * i for i in (per_row, rows))))
    else:
        f = fig

    # Initialize list to hold subplot axes
    axes_list = []

    # Initialize list of rgb tags
    isrgb = [False] * len(images)

    # Check to see if there are any rgb images in list
    # and tag them using the isrgb list
    for i, img in enumerate(images):
        if rgb_tools.is_rgbx(img.data):
            isrgb[i] = True

    # Determine how many non-rgb Images there are
    non_rgb = list(itertools.compress(images, [not j for j in isrgb]))
    if len(non_rgb) == 0 and colorbar is not None:
        colorbar = None
        warnings.warn("Sorry, colorbar is not implemented for RGB images.")

    # Find global min and max values of all the non-rgb images for use with
    # 'single' scalebar
    if colorbar == 'single':
        # get a g_saturated_pixels from saturated_pixels
        if isinstance(saturated_pixels, list):
            g_saturated_pixels = min(np.array([v for v in saturated_pixels]))
        else:
            g_saturated_pixels = saturated_pixels

        # estimate a g_vmin and g_max from saturated_pixels
        g_vmin, g_vmax = contrast_stretching(np.concatenate(
            [i.data.flatten() for i in non_rgb]), g_saturated_pixels)

        # if vmin and vmax are provided, override g_min and g_max
        if isinstance(vmin, list):
            _logger.warning('vmin have to be a scalar to be compatible with a '
                            'single colorbar')
        else:
            g_vmin = vmin if vmin is not None else g_vmin
        if isinstance(vmax, list):
            _logger.warning('vmax have to be a scalar to be compatible with a '
                            'single colorbar')
        else:
            g_vmax = vmax if vmax is not None else g_vmax
        if next(centre_colormaps):
            g_vmin, g_vmax = centre_colormap_values(g_vmin, g_vmax)

    # Check if we need to add a scalebar for some of the images
    if isinstance(scalebar, list) and all(isinstance(x, int)
                                          for x in scalebar):
        scalelist = True
    else:
        scalelist = False

    idx = 0
    ax_im_list = [0] * len(isrgb)

    # Replot: create a list to store references to the images
    replot_ims = []

    # Loop through each image, adding subplot for each one
    for i, ims in enumerate(images):
        # Get handles for the signal axes and axes_manager
        axes_manager = ims.axes_manager
        if axes_manager.navigation_dimension > 0:
            ims = ims._deepcopy_with_new_data(ims.data)
        for j, im in enumerate(ims):
            ax = f.add_subplot(rows, per_row, idx + 1)
            axes_list.append(ax)
            data = im.data
            centre = next(centre_colormaps)   # get next value for centreing

            # Enable RGB plotting
            if rgb_tools.is_rgbx(data):
                data = rgb_tools.rgbx2regular_array(data, plot_friendly=True)
                l_vmin, l_vmax = None, None
            else:
                data = im.data
                # Find min and max for contrast
                l_vmin, l_vmax = contrast_stretching(
                    data, saturated_pixels[idx])
                l_vmin = vmin[idx] if vmin[idx] is not None else l_vmin
                l_vmax = vmax[idx] if vmax[idx] is not None else l_vmax
                if centre:
                    l_vmin, l_vmax = centre_colormap_values(l_vmin, l_vmax)

            # Remove NaNs (if requested)
            if no_nans:
                data = np.nan_to_num(data)

            # Get handles for the signal axes and axes_manager
            axes_manager = im.axes_manager
            axes = axes_manager.signal_axes

            # Set dimensions of images
            xaxis = axes[0]
            yaxis = axes[1]

            extent = (
                xaxis.low_value,
                xaxis.high_value,
                yaxis.high_value,
                yaxis.low_value,
            )

            if not isinstance(aspect, (int, float)) and aspect not in [
                    'auto', 'square', 'equal']:
                _logger.warning("Did not understand aspect ratio input. "
                                "Using 'auto' as default.")
                aspect = 'auto'

            if aspect == 'auto':
                if float(yaxis.size) / xaxis.size < min_asp:
                    factor = min_asp * float(xaxis.size) / yaxis.size
                elif float(yaxis.size) / xaxis.size > min_asp ** -1:
                    factor = min_asp ** -1 * float(xaxis.size) / yaxis.size
                else:
                    factor = 1
                asp = np.abs(factor * float(xaxis.scale) / yaxis.scale)
            elif aspect == 'square':
                asp = abs(extent[1] - extent[0]) / abs(extent[3] - extent[2])
            elif aspect == 'equal':
                asp = 1
            elif isinstance(aspect, (int, float)):
                asp = aspect
            if 'interpolation' not in kwargs.keys():
                kwargs['interpolation'] = 'nearest'

            # Get colormap for this image:
            cm = next(cmap)

            # Plot image data, using vmin and vmax to set bounds,
            # or allowing them to be set automatically if using individual
            # colorbars
            if colorbar == 'single' and not isrgb[i]:
                axes_im = ax.imshow(data,
                                    cmap=cm,
                                    extent=extent,
                                    vmin=g_vmin, vmax=g_vmax,
                                    aspect=asp,
                                    *args, **kwargs)
                ax_im_list[i] = axes_im
            else:
                axes_im = ax.imshow(data,
                                    cmap=cm,
                                    extent=extent,
                                    vmin=l_vmin,
                                    vmax=l_vmax,
                                    aspect=asp,
                                    *args, **kwargs)
                ax_im_list[i] = axes_im

            # If an axis trait is undefined, shut off :
            if isinstance(xaxis.units, trait_base._Undefined) or  \
                    isinstance(yaxis.units, trait_base._Undefined) or \
                    isinstance(xaxis.name, trait_base._Undefined) or \
                    isinstance(yaxis.name, trait_base._Undefined):
                if axes_decor == 'all':
                    _logger.warning(
                        'Axes labels were requested, but one '
                        'or both of the '
                        'axes units and/or name are undefined. '
                        'Axes decorations have been set to '
                        '\'ticks\' instead.')
                    axes_decor = 'ticks'
            # If all traits are defined, set labels as appropriate:
            else:
                ax.set_xlabel(axes[0].name + " axis (" + axes[0].units + ")")
                ax.set_ylabel(axes[1].name + " axis (" + axes[1].units + ")")

            if label:
                if all_match:
                    title = ''
                elif shared_titles:
                    title = label_list[i][div_num - 1:]
                else:
                    if len(ims) == n:
                        # This is true if we are plotting just 1
                        # multi-dimensional Signal2D
                        title = label_list[idx]
                    elif user_labels:
                        title = label_list[idx]
                    else:
                        title = label_list[i]

                if ims.axes_manager.navigation_size > 1 and not user_labels:
                    title += " %s" % str(ims.axes_manager.indices)

                ax.set_title(textwrap.fill(title, labelwrap))

            # Set axes decorations based on user input
            set_axes_decor(ax, axes_decor)

            # If using independent colorbars, add them
            if colorbar == 'multi' and not isrgb[i]:
                div = make_axes_locatable(ax)
                cax = div.append_axes("right", size="5%", pad=0.05)
                plt.colorbar(axes_im, cax=cax)

            # Add scalebars as necessary
            if (scalelist and idx in scalebar) or scalebar == 'all':
                ax.scalebar = ScaleBar(
                    ax=ax,
                    units=axes[0].units,
                    color=scalebar_color,
                )
            # Replot: store references to the images
            replot_ims.append(im)

            idx += 1

    # If using a single colorbar, add it, and do tight_layout, ensuring that
    # a colorbar is only added based off of non-rgb Images:
    if colorbar == 'single':
        foundim = None
        for i in range(len(isrgb)):
            if (not isrgb[i]) and foundim is None:
                foundim = i

        if foundim is not None:
            f.subplots_adjust(right=0.8)
            cbar_ax = f.add_axes([0.9, 0.1, 0.03, 0.8])
            f.colorbar(ax_im_list[foundim], cax=cbar_ax)
            if tight_layout:
                # tight_layout, leaving room for the colorbar
                plt.tight_layout(rect=[0, 0, 0.9, 1])
        elif tight_layout:
            plt.tight_layout()

    elif tight_layout:
        plt.tight_layout()

    # Set top bounds for shared titles and add suptitle
    if suptitle:
        f.subplots_adjust(top=0.85)
        f.suptitle(suptitle, fontsize=suptitle_fontsize)

    # If we want to plot scalebars, loop through the list of axes and add them
    if scalebar is None or scalebar is False:
        # Do nothing if no scalebars are called for
        pass
    elif scalebar == 'all':
        # scalebars were taken care of in the plotting loop
        pass
    elif scalelist:
        # scalebars were taken care of in the plotting loop
        pass
    else:
        raise ValueError("Did not understand scalebar input. Must be None, "
                         "\'all\', or list of ints.")

    # Adjust subplot spacing according to user's specification
    if padding is not None:
        plt.subplots_adjust(**padding)

    # Replot: connect function
    def on_dblclick(event):
        # On the event of a double click, replot the selected subplot
        if not event.inaxes:
            return
        if not event.dblclick:
            return
        subplots = [axi for axi in f.axes if isinstance(axi, mpl.axes.Subplot)]
        inx = list(subplots).index(event.inaxes)
        im = replot_ims[inx]

        # Use some of the info in the subplot
        cm = subplots[inx].images[0].get_cmap()
        clim = subplots[inx].images[0].get_clim()

        sbar = False
        if (scalelist and inx in scalebar) or scalebar == 'all':
            sbar = True

        im.plot(colorbar=bool(colorbar),
                vmin=clim[0],
                vmax=clim[1],
                no_nans=no_nans,
                aspect=asp,
                scalebar=sbar,
                scalebar_color=scalebar_color,
                cmap=cm)

    f.canvas.mpl_connect('button_press_event', on_dblclick)

    return axes_list

def set_axes_decor(ax, axes_decor):
    if axes_decor == 'off':
        ax.axis('off')
    elif axes_decor == 'ticks':
        ax.set_xlabel('')
        ax.set_ylabel('')
    elif axes_decor == 'all':
        pass
    elif axes_decor is None:
        ax.set_xlabel('')
        ax.set_ylabel('')
        ax.set_xticklabels([])
        ax.set_yticklabels([])

def make_cmap(colors, name='my_colormap', position=None,
              bit=False, register=True):
    """
    Create a matplotlib colormap with customized colors, optionally registering
    it with matplotlib for simplified use.

    Adapted from Chris Slocum's code at:
    https://github.com/CSlocumWX/custom_colormap/blob/master/custom_colormaps.py
    and used under the terms of that code's BSD-3 license

    Parameters
    ----------
    colors : iterable
        list of either tuples containing rgb values, or html strings
        Colors should be arranged so that the first color is the lowest
        value for the colorbar and the last is the highest.
    name : str
        name of colormap to use when registering with matplotlib
    position : None or iterable
        list containing the values (from [0,1]) that dictate the position
        of each color within the colormap. If None (default), the colors
        will be equally-spaced within the colorbar.
    bit : boolean
        True if RGB colors are given in 8-bit [0 to 255] or False if given
        in arithmetic basis [0 to 1] (default)
    register : boolean
        switch to control whether or not to register the custom colormap
        with matplotlib in order to enable use by just the name string
    """
    def _html_color_to_rgb(color_string):
        """ convert #RRGGBB to an (R, G, B) tuple """
        color_string = color_string.strip()
        if color_string[0] == '#':
            color_string = color_string[1:]
        if len(color_string) != 6:
            raise ValueError(
                "input #{} is not in #RRGGBB format".format(color_string))
        r, g, b = color_string[:2], color_string[2:4], color_string[4:]
        r, g, b = [int(n, 16) / 255 for n in (r, g, b)]
        return r, g, b

    bit_rgb = np.linspace(0, 1, 256)

    if position is None:
        position = np.linspace(0, 1, len(colors))
    else:
        if len(position) != len(colors):
            raise ValueError("position length must be the same as colors")
        elif position[0] != 0 or position[-1] != 1:
            raise ValueError("position must start with 0 and end with 1")

    cdict = {'red': [], 'green': [], 'blue': []}

    for pos, color in zip(position, colors):
        if isinstance(color, str):
            color = _html_color_to_rgb(color)

        elif bit:
            color = (bit_rgb[color[0]],
                     bit_rgb[color[1]],
                     bit_rgb[color[2]])

        cdict['red'].append((pos, color[0], color[0]))
        cdict['green'].append((pos, color[1], color[1]))
        cdict['blue'].append((pos, color[2], color[2]))

    cmap = mpl.colors.LinearSegmentedColormap(name, cdict, 256)

    if register:
        mpl.cm.register_cmap(name, cmap)
    return cmap

def plot_spectra(
        spectra,
        style='overlap',
        color=None,
        line_style=None,
        padding=1.,
        legend=None,
        legend_picking=True,
        legend_loc='upper right',
        fig=None,
        ax=None,
        **kwargs):
    """Plot several spectra in the same figure.

    Extra keyword arguments are passed to `matplotlib.figure`.

    Parameters
    ----------
    spectra : list of Signal1D or BaseSignal
        Ordered spectra list of signal to plot. If `style` is "cascade" or
        "mosaic" the spectra can have different size and axes. For `BaseSignal`
        with navigation dimensions 1 and signal dimension 0, the signal will be
        tranposed to form a `Signal1D`.
    style : {'overlap', 'cascade', 'mosaic', 'heatmap'}
        The style of the plot.
    color : matplotlib color or a list of them or `None`
        Sets the color of the lines of the plots (no action on 'heatmap').
        If a list, if its length is less than the number of spectra to plot,
        the colors will be cycled. If `None`, use default matplotlib color
        cycle.
    line_style: matplotlib line style or a list of them or `None`
        Sets the line style of the plots (no action on 'heatmap').
        The main line style are '-','--','steps','-.',':'.
        If a list, if its length is less than the number of
        spectra to plot, line_style will be cycled. If
        If `None`, use continuous lines, eg: ('-','--','steps','-.',':')
    padding : float, optional, default 0.1
        Option for "cascade". 1 guarantees that there is not overlapping.
        However, in many cases a value between 0 and 1 can produce a tighter
        plot without overlapping. Negative values have the same effect but
        reverse the order of the spectra without reversing the order of the
        colors.
    legend: None or list of str or 'auto'
       If list of string, legend for "cascade" or title for "mosaic" is
       displayed. If 'auto', the title of each spectra (metadata.General.title)
       is used.
    legend_picking: bool
        If true, a spectrum can be toggle on and off by clicking on
        the legended line.
    legend_loc : str or int
        This parameter controls where the legend is placed on the figure;
        see the pyplot.legend docstring for valid values
    fig : matplotlib figure or None
        If None, a default figure will be created. Specifying fig will
        not work for the 'heatmap' style.
    ax : matplotlib ax (subplot) or None
        If None, a default ax will be created. Will not work for 'mosaic'
        or 'heatmap' style.
    **kwargs
        remaining keyword arguments are passed to matplotlib.figure() or
        matplotlib.subplots(). Has no effect on 'heatmap' style.

    Example
    -------
    >>> s = hs.load("some_spectra")
    >>> hs.plot.plot_spectra(s, style='cascade', color='red', padding=0.5)

    To save the plot as a png-file

    >>> hs.plot.plot_spectra(s).figure.savefig("test.png")

    Returns
    -------
    ax: matplotlib axes or list of matplotlib axes
        An array is returned when `style` is "mosaic".

    """
    import hyperspy.signal

    def _reverse_legend(ax_, legend_loc_):
        """
        Reverse the ordering of a matplotlib legend (to be more consistent
        with the default ordering of plots in the 'cascade' and 'overlap'
        styles

        Parameters
        ----------
        ax_: matplotlib axes

        legend_loc_: str or int
            This parameter controls where the legend is placed on the
            figure; see the pyplot.legend docstring for valid values
        """
        l = ax_.get_legend()
        labels = [lb.get_text() for lb in list(l.get_texts())]
        handles = l.legendHandles
        ax_.legend(handles[::-1], labels[::-1], loc=legend_loc_)

    # Before v1.3 default would read the value from prefereces.
    if style == "default":
        style = "overlap"

    if color is not None:
        if isinstance(color, str):
            color = itertools.cycle([color])
        elif hasattr(color, "__iter__"):
            color = itertools.cycle(color)
        else:
            raise ValueError("Color must be None, a valid matplotlib color "
                             "string or a list of valid matplotlib colors.")
    else:
        if LooseVersion(mpl.__version__) >= "1.5.3":
            color = itertools.cycle(
                plt.rcParams['axes.prop_cycle'].by_key()["color"])
        else:
            color = itertools.cycle(plt.rcParams['axes.color_cycle'])

    if line_style is not None:
        if isinstance(line_style, str):
            line_style = itertools.cycle([line_style])
        elif hasattr(line_style, "__iter__"):
            line_style = itertools.cycle(line_style)
        else:
            raise ValueError("line_style must be None, a valid matplotlib"
                             " line_style string or a list of valid matplotlib"
                             " line_style.")
    else:
        line_style = ['-'] * len(spectra)

    if legend is not None:
        if isinstance(legend, str):
            if legend == 'auto':
                legend = [spec.metadata.General.title for spec in spectra]
            else:
                raise ValueError("legend must be None, 'auto' or a list of"
                                 " string")

        elif hasattr(legend, "__iter__"):
            legend = itertools.cycle(legend)

    if style == 'overlap':
        if fig is None:
            fig = plt.figure(**kwargs)
        if ax is None:
            ax = fig.add_subplot(111)
        _make_overlap_plot(spectra,
                           ax,
                           color=color,
                           line_style=line_style,)
        if legend is not None:
            ax.legend(legend, loc=legend_loc)
            _reverse_legend(ax, legend_loc)
            if legend_picking is True:
                animate_legend(fig=fig, ax=ax)
    elif style == 'cascade':
        if fig is None:
            fig = plt.figure(**kwargs)
        if ax is None:
            ax = fig.add_subplot(111)
        _make_cascade_subplot(spectra,
                              ax,
                              color=color,
                              line_style=line_style,
                              padding=padding)
        if legend is not None:
            plt.legend(legend, loc=legend_loc)
            _reverse_legend(ax, legend_loc)
    elif style == 'mosaic':
        default_fsize = plt.rcParams["figure.figsize"]
        figsize = (default_fsize[0], default_fsize[1] * len(spectra))
        fig, subplots = plt.subplots(
            len(spectra), 1, figsize=figsize, **kwargs)
        if legend is None:
            legend = [legend] * len(spectra)
        for spectrum, ax, color, line_style, legend in zip(
                spectra, subplots, color, line_style, legend):
            spectrum = _transpose_if_required(spectrum, 1)
            _plot_spectrum(spectrum, ax, color=color, line_style=line_style)
            ax.set_ylabel('Intensity')
            if legend is not None:
                ax.set_title(legend)
            if not isinstance(spectra, hyperspy.signal.BaseSignal):
                _set_spectrum_xlabel(spectrum, ax)
        if isinstance(spectra, hyperspy.signal.BaseSignal):
            _set_spectrum_xlabel(spectrum, ax)
        fig.tight_layout()

    elif style == 'heatmap':
        if not isinstance(spectra, hyperspy.signal.BaseSignal):
            import hyperspy.utils
            spectra = [_transpose_if_required(spectrum, 1) for spectrum in
                       spectra]
            spectra = hyperspy.utils.stack(spectra)
        with spectra.unfolded():
            ax = _make_heatmap_subplot(spectra)
            ax.set_ylabel('Spectra')
    ax = ax if style != "mosaic" else subplots

    return ax

def animate_legend(fig=None, ax=None):
    """Animate the legend of a figure.

    A spectrum can be toggle on and off by clicking on the legended line.

    Parameters
    ----------

    fig: None | matplotlib.figure
        If None pick the current figure using "plt.gcf"
    ax:  None | matplotlib.axes
        If None pick the current axes using "plt.gca".

    Note
    ----

    Code inspired from legend_picking.py in the matplotlib gallery

    """
    if fig is None:
        fig = plt.gcf()
    if ax is None:
        ax = plt.gca()
    lines = ax.lines[::-1]
    lined = dict()
    leg = ax.get_legend()
    for legline, origline in zip(leg.get_lines(), lines):
        legline.set_picker(5)  # 5 pts tolerance
        lined[legline] = origline

    def onpick(event):
        # on the pick event, find the orig line corresponding to the
        # legend proxy line, and toggle the visibility
        legline = event.artist
        if legline.axes == ax:
            origline = lined[legline]
            vis = not origline.get_visible()
            origline.set_visible(vis)
            # Change the alpha on the line in the legend so we can see what lines
            # have been toggled
            if vis:
                legline.set_alpha(1.0)
            else:
                legline.set_alpha(0.2)
            fig.canvas.draw_idle()

    fig.canvas.mpl_connect('pick_event', onpick)

def plot_histograms(signal_list,
                    bins='freedman',
                    range_bins=None,
                    color=None,
                    line_style=None,
                    legend='auto',
                    fig=None,
                    **kwargs):
    """Plot the histogram of every signal in the list in the same figure.

    This function creates a histogram for each signal and plot the list with
    the `utils.plot.plot_spectra` function.

    Parameters
    ----------
    signal_list : iterable
        Ordered spectra list to plot. If `style` is "cascade" or "mosaic"
        the spectra can have different size and axes.
    bins : int or list or str, optional
        If bins is a string, then it must be one of:
        'knuth' : use Knuth's rule to determine bins
        'scotts' : use Scott's rule to determine bins
        'freedman' : use the Freedman-diaconis rule to determine bins
        'blocks' : use bayesian blocks for dynamic bin widths
    range_bins : tuple or None, optional.
        the minimum and maximum range for the histogram. If not specified,
        it will be (x.min(), x.max())
    color : valid matplotlib color or a list of them or `None`, optional.
        Sets the color of the lines of the plots. If a list, if its length is
        less than the number of spectra to plot, the colors will be cycled. If
        If `None`, use default matplotlib color cycle.
    line_style: valid matplotlib line style or a list of them or `None`,
    optional.
        The main line style are '-','--','steps','-.',':'.
        If a list, if its length is less than the number of
        spectra to plot, line_style will be cycled. If
        If `None`, use continuous lines, eg: ('-','--','steps','-.',':')
    legend: None or list of str or 'auto', optional.
       Display a legend. If 'auto', the title of each spectra
       (metadata.General.title) is used.
    legend_picking: bool, optional.
        If true, a spectrum can be toggle on and off by clicking on
        the legended line.
    fig : matplotlib figure or None, optional.
        If None, a default figure will be created.
    **kwargs
        other keyword arguments (weight and density) are described in
        np.histogram().

    Example
    -------
    Histograms of two random chi-square distributions

    >>> img = hs.signals.Signal2D(np.random.chisquare(1,[10,10,100]))
    >>> img2 = hs.signals.Signal2D(np.random.chisquare(2,[10,10,100]))
    >>> hs.plot.plot_histograms([img,img2],legend=['hist1','hist2'])

    Returns
    -------
    ax: matplotlib axes or list of matplotlib axes
        An array is returned when `style` is "mosaic".

    """
    hists = []
    for obj in signal_list:
        hists.append(obj.get_histogram(bins=bins,
                                       range_bins=range_bins, **kwargs))
    if line_style is None:
        line_style = 'steps'
    return plot_spectra(hists, style='overlap', color=color,
                        line_style=line_style, legend=legend, fig=fig)


import numpy as np
import matplotlib.pyplot as plt

data1 = np.loadtxt("expect_random_T1.00.dat")
data2 = np.loadtxt("expect_ordered_T1.00.dat")
data3 = np.loadtxt("expect_random2_T2.40.dat")
data4 = np.loadtxt("expect_ordered2_T2.40.dat")
values1 = data1[0::1]
values2 = data2[0::1]
values3 = data3[0::1]
values4 = data4[0::1]

N1 = len(values1)
x1 = np.linspace(0,N1,N1)

N2 = len(values3)
x2 = np.linspace(0,N2,N2)

figure1 = plt.figure()

labels = figure1.add_subplot(111)

# Turn off axis lines and ticks of the big subplot
labels.spines['top'].set_color('none')
labels.spines['bottom'].set_color('none')
labels.spines['left'].set_color('none')
labels.spines['right'].set_color('none')
labels.tick_params(labelcolor='w', top='off', bottom='off', left='off', right='off')

plt.xlabel("Number of Monte Carlo cycles",fontsize=15)
plt.ylabel("Mean energy per spin",fontsize=15)

#figure1.yaxis.set_ticks_position(right)

#figure1.ylabel.set_ticks_position('left')

#figure1.yaxis.tick_right()

fig1 = figure1.add_subplot(211)

fig1.plot(x1,values1[:,0],label="Random initial spins, T=1")
fig1.plot(x1,values2[:,0],label="Ordered initial spins, T=1")
fig1.tick_params(axis='x', labelsize=15) #HOW TO PUT THIS ON THE RIGHT SIDE?
fig1.tick_params(axis='y', labelsize=15)
fig1.yaxis.tick_right()
#plt.ylabel(r"$\langle E\rangle /L^2$",fontsize=17)
#plt.xlabel("Number of Monte Carlo cycles",fontsize=15)
plt.legend()
plt.axis([0,N1,-3,0])
#plt.show()

fig2 = figure1.add_subplot(212)
fig2.plot(x2,values3[:,0],label="Random initial spins, T=2.4")
fig2.plot(x2,values4[:,0],label="Ordered initial spins, T=2.4")
fig2.tick_params(axis='x', labelsize=15)
fig2.tick_params(axis='y', labelsize=15)
fig2.yaxis.tick_right()
#plt.ylabel(r"$\langle E\rangle /L^2$",fontsize=15)
#plt.xlabel("Number of Monte Carlo cycles",fontsize=15)
plt.legend()
plt.axis([0,50000,-2,-0.4])
plt.show()

figure2 = plt.figure()

labels = figure2.add_subplot(111)
labels.spines['top'].set_color('none')
labels.spines['bottom'].set_color('none')
labels.spines['left'].set_color('none')
labels.spines['right'].set_color('none')
labels.tick_params(labelcolor='w', top='off', bottom='off', left='off', right='off')

plt.xlabel("Number of Monte Carlo cycles",fontsize=15)
plt.ylabel("Absolute magnetization per spin",fontsize=15)

fig1 = figure2.add_subplot(211)
fig1.plot(x1,values1[:,1],label="Random initial spins, T=1")
fig1.plot(x1,values2[:,1],label="Ordered initial spins, T=1")
fig1.tick_params(axis='x', labelsize=15)
fig1.tick_params(axis='y', labelsize=15)
fig1.yaxis.tick_right()
#fig2.ylabel(r"$abs(\langle M \rangle /L^2)$",fontsize=15)
#fig2.xlabel("Number of Monte Carlo cycles",fontsize=15)
plt.legend()
plt.axis([0,N1,0.2,1.6])
#plt.show()

fig2 = figure2.add_subplot(212)
fig2.plot(x2,values3[:,1],label="Random initial spins, T=2.4")
fig2.plot(x2,values4[:,1],label="Ordered initial spins, T=2.4")
fig2.tick_params(axis='x', labelsize=15)
fig2.tick_params(axis='y', labelsize=15)
fig2.yaxis.tick_right()
#plt.ylabel(r"$abs(\langle M\rangle / L^2)$",fontsize=15)
#plt.xlabel("Number of Monte Carlo cycles",fontsize=15)
plt.legend()
#plt.axis([0,8e6,-0.1,1.4])
plt.show()


#!/usr/bin/env python
# -*- coding: utf-8 -*-

import __init__

import numpy as np
from scipy.weave import inline

from sklearn.ensemble import RandomForestClassifier

import cpLib.concept as cp
import utils.skUtils as sku

# PROJECTION
def projCosSim(c1, c2):
    v1 = c1.vect
    v2 = c2.vect
    dimCount = len(v1)

    arr = np.zeros(dimCount, 'f')

    code = """
            for(int i = 0; i < dimCount; i++) {
                float norm_v1 = 0.0;
                float norm_v2 = 0.0;
                float dot_pdt = 0.0;

                for(int j = 0; j < dimCount; j++) {
                    if(i != j) {
                        dot_pdt += v1[j] * v2[j];
                        norm_v1 += v1[j] * v1[j];
                        norm_v2 += v2[j] * v2[j];
                    }
                }

                norm_v1 = sqrtf(norm_v1);
                norm_v2 = sqrtf(norm_v2);

                arr[i] = dot_pdt / norm_v1 / norm_v2;
            }

            return_val = 1;
    """
    inline(code, ['v1', 'v2', 'dimCount', 'arr'], headers = ['<math.h>'], compiler = 'gcc')
    return arr

def projEuclDist(c1, c2):
    v1 = c1.vect
    v2 = c2.vect
    dimCount = len(v1)

    arr = np.zeros(dimCount, 'f')

    code = """
            for(int i = 0; i < dimCount; i++) {
                float dist = 0.0;

                for(int j = 0; j < dimCount; j++) {
                    if(i != j) {
                        dist += pow(v1[j] - v2[j], 2);
                    }
                }

                arr[i] = sqrt(dist);
            }

            return_val = 1;
    """
    inline(code, ['v1', 'v2', 'dimCount', 'arr'], headers = ['<math.h>'], compiler = 'gcc')
    return arr

def projManaDist(c1, c2):
    v1 = c1.vect
    v2 = c2.vect
    dimCount = len(v1)

    arr = np.zeros(dimCount, 'f')

    code = """
            for(int i = 0; i < dimCount; i++) {
                float dist = 0.0;

                for(int j = 0; j < dimCount; j++) {
                    if(i != j) {
                        dist += fabs(v1[i] - v2[i]);
                    }
                }

                arr[i] = dist;
            }

            return_val = 1;
    """
    inline(code, ['v1', 'v2', 'dimCount', 'arr'], headers = ['<math.h>'], compiler = 'gcc')
    return arr

# COMMUTATIVE FEATURE
def subCarth(conceptPair):
    return conceptPair[2].vect - conceptPair[0].vect

def subPolar(conceptPair):
    return conceptPair[2].polarVect() - conceptPair[0].polarVect()

def subAngular(conceptPair):
    return conceptPair[2].angularVect() - conceptPair[0].angularVect()

def concatCarth(conceptPair):
    return np.concatenate((conceptPair[0].vect, conceptPair[2].vect))

def concatPolar(conceptPair):
    return np.concatenate((conceptPair[0].polarVect(), conceptPair[2].polarVect()))

def concatAngular(conceptPair):
    return np.concatenate((conceptPair[0].angularVect(), conceptPair[2].angularVect()))

# NON COMMUATIVE FEATURE
# PROJECTION SIMILARITY
def pCosSim(conceptPair):
    return projCosSim(conceptPair[0], conceptPair[2])

def pEuclDist(conceptPair):
    return projEuclDist(conceptPair[0], conceptPair[2])

def pManaDist(conceptPair):
    return projManaDist(conceptPair[0], conceptPair[2])

# PROJECTION DISSIMILARITY
def _projectionDissimarilty(projectionMetric, globalMetric, conceptPair):
    projectedFeature = projectionMetric(conceptPair[0], conceptPair[2])
    globalFeature = globalMetric(conceptPair[0], conceptPair[2])
    return np.array([(globalFeature - v) for v in projectedFeature])

def pdCosSim(conceptPair):
    return _projectionDissimarilty(projCosSim, cp.cosSim, conceptPair)

def pdEuclDist(conceptPair):
    return _projectionDissimarilty(projEuclDist, cp.euclDist, conceptPair)

def pdManaDist(conceptPair):
    return _projectionDissimarilty(projManaDist, cp.manaDist, conceptPair)

# CLF
class ConceptPairClf(object):
    def __init__(self, clf, featureExtractionFct):
        self.clf = clf
        self.featureExtractionFct = featureExtractionFct

    def fit(self, X, y):
        self.clf.fit([self.featureExtractionFct(x) for x in X], y)
        self.classes_ = self.clf.classes_

    def predict(self, X):
        return self.clf.predict([self.featureExtractionFct(x) for x in X])

    def predict_proba(self, X):
        return self.clf.predict_proba([self.featureExtractionFct(x) for x in X])


#!/usr/bin/python
# -*- coding: utf-8 -*-

# Author: Vincent Dubourg <vincent.dubourg@gmail.com>
#         (mostly translation, see implementation details)
# License: BSD style

import numpy as np
from scipy import linalg, optimize, rand

from ..base import BaseEstimator, RegressorMixin
from ..metrics.pairwise import manhattan_distances
from ..utils import array2d, check_random_state
from ..utils import deprecated
from . import regression_models as regression
from . import correlation_models as correlation

MACHINE_EPSILON = np.finfo(np.double).eps
if hasattr(linalg, 'solve_triangular'):
    # only in scipy since 0.9
    solve_triangular = linalg.solve_triangular
else:
    # slower, but works
    def solve_triangular(x, y, lower=True):
        return linalg.solve(x, y)

def l1_cross_distances(X):
    """
    Computes the nonzero componentwise L1 cross-distances between the vectors
    in X.

    Parameters
    ----------

    X: array_like
        An array with shape (n_samples, n_features)

    Returns
    -------

    D: array with shape (n_samples * (n_samples - 1) / 2, n_features)
        The array of componentwise L1 cross-distances.

    ij: arrays with shape (n_samples * (n_samples - 1) / 2, 2)
        The indices i and j of the vectors in X associated to the cross-
        distances in D: D[k] = np.abs(X[ij[k, 0]] - Y[ij[k, 1]]).
    """
    X = array2d(X)
    n_samples, n_features = X.shape
    n_nonzero_cross_dist = n_samples * (n_samples - 1) / 2
    ij = np.zeros((n_nonzero_cross_dist, 2), dtype=np.int)
    D = np.zeros((n_nonzero_cross_dist, n_features))
    ll_1 = 0
    for k in range(n_samples - 1):
        ll_0 = ll_1
        ll_1 = ll_0 + n_samples - k - 1
        ij[ll_0:ll_1, 0] = k
        ij[ll_0:ll_1, 1] = np.arange(k + 1, n_samples)
        D[ll_0:ll_1] = np.abs(X[k] - X[(k + 1):n_samples])

    return D, ij.astype(np.int)

class GaussianProcess(BaseEstimator, RegressorMixin):
    """The Gaussian Process model class.

    Parameters
    ----------
    regr : string or callable, optional
        A regression function returning an array of outputs of the linear
        regression functional basis. The number of observations n_samples
        should be greater than the size p of this basis.
        Default assumes a simple constant regression trend.
        Available built-in regression models are::

            'constant', 'linear', 'quadratic'

    corr : string or callable, optional
        A stationary autocorrelation function returning the autocorrelation
        between two points x and x'.
        Default assumes a squared-exponential autocorrelation model.
        Built-in correlation models are::

            'absolute_exponential', 'squared_exponential',
            'generalized_exponential', 'cubic', 'linear'

    beta0 : double array_like, optional
        The regression weight vector to perform Ordinary Kriging (OK).
        Default assumes Universal Kriging (UK) so that the vector beta of
        regression weights is estimated using the maximum likelihood
        principle.

    storage_mode : string, optional
        A string specifying whether the Cholesky decomposition of the
        correlation matrix should be stored in the class (storage_mode =
        'full') or not (storage_mode = 'light').
        Default assumes storage_mode = 'full', so that the
        Cholesky decomposition of the correlation matrix is stored.
        This might be a useful parameter when one is not interested in the
        MSE and only plan to estimate the BLUP, for which the correlation
        matrix is not required.

    verbose : boolean, optional
        A boolean specifying the verbose level.
        Default is verbose = False.

    theta0 : double array_like, optional
        An array with shape (n_features, ) or (1, ).
        The parameters in the autocorrelation model.
        If thetaL and thetaU are also specified, theta0 is considered as
        the starting point for the maximum likelihood rstimation of the
        best set of parameters.
        Default assumes isotropic autocorrelation model with theta0 = 1e-1.

    thetaL : double array_like, optional
        An array with shape matching theta0's.
        Lower bound on the autocorrelation parameters for maximum
        likelihood estimation.
        Default is None, so that it skips maximum likelihood estimation and
        it uses theta0.

    thetaU : double array_like, optional
        An array with shape matching theta0's.
        Upper bound on the autocorrelation parameters for maximum
        likelihood estimation.
        Default is None, so that it skips maximum likelihood estimation and
        it uses theta0.

    normalize : boolean, optional
        Input X and observations y are centered and reduced wrt
        means and standard deviations estimated from the n_samples
        observations provided.
        Default is normalize = True so that data is normalized to ease
        maximum likelihood estimation.

    nugget : double or ndarray, optional
        Introduce a nugget effect to allow smooth predictions from noisy
        data.  If nugget is an ndarray, it must be the same length as the
        number of data points used for the fit.
        The nugget is added to the diagonal of the assumed training covariance;
        in this way it acts as a Tikhonov regularization in the problem.  In
        the special case of the squared exponential correlation function, the
        nugget mathematically represents the variance of the input values.
        Default assumes a nugget close to machine precision for the sake of
        robustness (nugget = 10. * MACHINE_EPSILON).

    optimizer : string, optional
        A string specifying the optimization algorithm to be used.
        Default uses 'fmin_cobyla' algorithm from scipy.optimize.
        Available optimizers are::

            'fmin_cobyla', 'Welch'

        'Welch' optimizer is dued to Welch et al., see reference [WBSWM1992]_.
        It consists in iterating over several one-dimensional optimizations
        instead of running one single multi-dimensional optimization.

    random_start : int, optional
        The number of times the Maximum Likelihood Estimation should be
        performed from a random starting point.
        The first MLE always uses the specified starting point (theta0),
        the next starting points are picked at random according to an
        exponential distribution (log-uniform on [thetaL, thetaU]).
        Default does not use random starting point (random_start = 1).

    random_state: integer or numpy.RandomState, optional
        The generator used to shuffle the sequence of coordinates of theta in
        the Welch optimizer. If an integer is given, it fixes the seed.
        Defaults to the global numpy random number generator.

    Attributes
    ----------
    `theta_`: array
        Specified theta OR the best set of autocorrelation parameters (the \
        sought maximizer of the reduced likelihood function).

    `reduced_likelihood_function_value_`: array
        The optimal reduced likelihood function value.

    Examples
    --------
    >>> import numpy as np
    >>> from sklearn.gaussian_process import GaussianProcess
    >>> X = np.array([[1., 3., 5., 6., 7., 8.]]).T
    >>> y = (X * np.sin(X)).ravel()
    >>> gp = GaussianProcess(theta0=0.1, thetaL=.001, thetaU=1.)
    >>> gp.fit(X, y)                                      # doctest: +ELLIPSIS
    GaussianProcess(beta0=None...
            ...

    Notes
    -----
    The presentation implementation is based on a translation of the DACE
    Matlab toolbox, see reference [NLNS2002]_.

    References
    ----------

    .. [NLNS2002] `H.B. Nielsen, S.N. Lophaven, H. B. Nielsen and J.
        Sondergaard.  DACE - A MATLAB Kriging Toolbox.` (2002)
        http://www2.imm.dtu.dk/~hbn/dace/dace.pdf

    .. [WBSWM1992] `W.J. Welch, R.J. Buck, J. Sacks, H.P. Wynn, T.J. Mitchell,
        and M.D.  Morris (1992). Screening, predicting, and computer
        experiments.  Technometrics, 34(1) 15--25.`
        http://www.jstor.org/pss/1269548
    """

    _regression_types = {
        'constant': regression.constant,
        'linear': regression.linear,
        'quadratic': regression.quadratic}

    _correlation_types = {
        'absolute_exponential': correlation.absolute_exponential,
        'squared_exponential': correlation.squared_exponential,
        'generalized_exponential': correlation.generalized_exponential,
        'cubic': correlation.cubic,
        'linear': correlation.linear}

    _optimizer_types = [
        'fmin_cobyla',
        'Welch']

    def __init__(self, regr='constant', corr='squared_exponential', beta0=None,
                 storage_mode='full', verbose=False, theta0=1e-1,
                 thetaL=None, thetaU=None, optimizer='fmin_cobyla',
                 random_start=1, normalize=True,
                 nugget=10. * MACHINE_EPSILON, random_state=None):

        self.regr = regr
        self.corr = corr
        self.beta0 = beta0
        self.storage_mode = storage_mode
        self.verbose = verbose
        self.theta0 = theta0
        self.thetaL = thetaL
        self.thetaU = thetaU
        self.normalize = normalize
        self.nugget = nugget
        self.optimizer = optimizer
        self.random_start = random_start
        self.random_state = random_state

        # Run input checks
        self._check_params()

    def fit(self, X, y):
        """
        The Gaussian Process model fitting method.

        Parameters
        ----------
        X : double array_like
            An array with shape (n_samples, n_features) with the input at which
            observations were made.

        y : double array_like
            An array with shape (n_samples, ) with the observations of the
            scalar output to be predicted.

        Returns
        -------
        gp : self
            A fitted Gaussian Process model object awaiting data to perform
            predictions.
        """
        self.random_state = check_random_state(self.random_state)

        # Force data to 2D numpy.array
        X = array2d(X)
        y = np.asarray(y).ravel()[:, np.newaxis]

        # Check shapes of DOE & observations
        n_samples_X, n_features = X.shape
        n_samples_y = y.shape[0]

        if n_samples_X != n_samples_y:
            raise ValueError("X and y must have the same number of rows.")
        else:
            n_samples = n_samples_X

        # Run input checks
        self._check_params(n_samples)

        # Normalize data or don't
        if self.normalize:
            X_mean = np.mean(X, axis=0)
            X_std = np.std(X, axis=0)
            y_mean = np.mean(y, axis=0)
            y_std = np.std(y, axis=0)
            X_std[X_std == 0.] = 1.
            y_std[y_std == 0.] = 1.
            # center and scale X if necessary
            X = (X - X_mean) / X_std
            y = (y - y_mean) / y_std
        else:
            X_mean = np.zeros(1)
            X_std = np.ones(1)
            y_mean = np.zeros(1)
            y_std = np.ones(1)

        # Calculate matrix of distances D between samples
        D, ij = l1_cross_distances(X)
        if np.min(np.sum(D, axis=1)) == 0. \
                                    and self.corr != correlation.pure_nugget:
            raise Exception("Multiple input features cannot have the same"
                    " value")

        # Regression matrix and parameters
        F = self.regr(X)
        n_samples_F = F.shape[0]
        if F.ndim > 1:
            p = F.shape[1]
        else:
            p = 1
        if n_samples_F != n_samples:
            raise Exception("Number of rows in F and X do not match. Most "
                          + "likely something is going wrong with the "
                          + "regression model.")
        if p > n_samples_F:
            raise Exception(("Ordinary least squares problem is undetermined "
                           + "n_samples=%d must be greater than the "
                           + "regression model size p=%d.") % (n_samples, p))
        if self.beta0 is not None:
            if self.beta0.shape[0] != p:
                raise Exception("Shapes of beta0 and F do not match.")

        # Set attributes
        self.X = X
        self.y = y
        self.D = D
        self.ij = ij
        self.F = F
        self.X_mean, self.X_std = X_mean, X_std
        self.y_mean, self.y_std = y_mean, y_std

        # Determine Gaussian Process model parameters
        if self.thetaL is not None and self.thetaU is not None:
            # Maximum Likelihood Estimation of the parameters
            if self.verbose:
                print("Performing Maximum Likelihood Estimation of the "
                    + "autocorrelation parameters...")
            self.theta_, self.reduced_likelihood_function_value_, par = \
                self._arg_max_reduced_likelihood_function()
            if np.isinf(self.reduced_likelihood_function_value_):
                raise Exception("Bad parameter region. "
                              + "Try increasing upper bound")

        else:
            # Given parameters
            if self.verbose:
                print("Given autocorrelation parameters. "
                    + "Computing Gaussian Process model parameters...")
            self.theta_ = self.theta0
            self.reduced_likelihood_function_value_, par = \
                self.reduced_likelihood_function()
            if np.isinf(self.reduced_likelihood_function_value_):
                raise Exception("Bad point. Try increasing theta0.")

        self.beta = par['beta']
        self.gamma = par['gamma']
        self.sigma2 = par['sigma2']
        self.C = par['C']
        self.Ft = par['Ft']
        self.G = par['G']

        if self.storage_mode == 'light':
            # Delete heavy data (it will be computed again if required)
            # (it is required only when MSE is wanted in self.predict)
            if self.verbose:
                print("Light storage mode specified. "
                    + "Flushing autocorrelation matrix...")
            self.D = None
            self.ij = None
            self.F = None
            self.C = None
            self.Ft = None
            self.G = None

        return self

    def predict(self, X, eval_MSE=False, batch_size=None):
        """
        This function evaluates the Gaussian Process model at x.

        Parameters
        ----------
        X : array_like
            An array with shape (n_eval, n_features) giving the point(s) at
            which the prediction(s) should be made.

        eval_MSE : boolean, optional
            A boolean specifying whether the Mean Squared Error should be
            evaluated or not.
            Default assumes evalMSE = False and evaluates only the BLUP (mean
            prediction).

        batch_size : integer, optional
            An integer giving the maximum number of points that can be
            evaluated simulatneously (depending on the available memory).
            Default is None so that all given points are evaluated at the same
            time.

        Returns
        -------
        y : array_like
            An array with shape (n_eval, ) with the Best Linear Unbiased
            Prediction at x.

        MSE : array_like, optional (if eval_MSE == True)
            An array with shape (n_eval, ) with the Mean Squared Error at x.
        """

        # Check input shapes
        X = array2d(X)
        n_eval, n_features_X = X.shape
        n_samples, n_features = self.X.shape

        # Run input checks
        self._check_params(n_samples)

        if n_features_X != n_features:
            raise ValueError(("The number of features in X (X.shape[1] = %d) "
                           + "should match the sample size used for fit() "
                           + "which is %d.") % (n_features_X, n_features))

        if batch_size is None:
            # No memory management
            # (evaluates all given points in a single batch run)

            # Normalize input
            X = (X - self.X_mean) / self.X_std

            # Initialize output
            y = np.zeros(n_eval)
            if eval_MSE:
                MSE = np.zeros(n_eval)

            # Get pairwise componentwise L1-distances to the input training set
            dx = manhattan_distances(X, Y=self.X, sum_over_features=False)
            # Get regression function and correlation
            f = self.regr(X)
            r = self.corr(self.theta_, dx).reshape(n_eval, n_samples)

            # Scaled predictor
            y_ = np.dot(f, self.beta) + np.dot(r, self.gamma)

            # Predictor
            y = (self.y_mean + self.y_std * y_).ravel()

            # Mean Squared Error
            if eval_MSE:
                C = self.C
                if C is None:
                    # Light storage mode (need to recompute C, F, Ft and G)
                    if self.verbose:
                        print("This GaussianProcess used 'light' storage mode "
                            + "at instanciation. Need to recompute "
                            + "autocorrelation matrix...")
                    reduced_likelihood_function_value, par = \
                        self.reduced_likelihood_function()
                    self.C = par['C']
                    self.Ft = par['Ft']
                    self.G = par['G']

                rt = solve_triangular(self.C, r.T, lower=True)

                if self.beta0 is None:
                    # Universal Kriging
                    u = solve_triangular(self.G.T,
                                         np.dot(self.Ft.T, rt) - f.T)
                else:
                    # Ordinary Kriging
                    u = np.zeros(y.shape)

                MSE = self.sigma2 * (1. - (rt ** 2.).sum(axis=0)
                                        + (u ** 2.).sum(axis=0))

                # Mean Squared Error might be slightly negative depending on
                # machine precision: force to zero!
                MSE[MSE < 0.] = 0.

                return y, MSE

            else:

                return y

        else:
            # Memory management

            if type(batch_size) is not int or batch_size <= 0:
                raise Exception("batch_size must be a positive integer")

            if eval_MSE:

                y, MSE = np.zeros(n_eval), np.zeros(n_eval)
                for k in range(max(1, n_eval / batch_size)):
                    batch_from = k * batch_size
                    batch_to = min([(k + 1) * batch_size + 1, n_eval + 1])
                    y[batch_from:batch_to], MSE[batch_from:batch_to] = \
                        self.predict(X[batch_from:batch_to],
                                     eval_MSE=eval_MSE, batch_size=None)

                return y, MSE

            else:

                y = np.zeros(n_eval)
                for k in range(max(1, n_eval / batch_size)):
                    batch_from = k * batch_size
                    batch_to = min([(k + 1) * batch_size + 1, n_eval + 1])
                    y[batch_from:batch_to] = \
                        self.predict(X[batch_from:batch_to],
                                     eval_MSE=eval_MSE, batch_size=None)

                return y

    def reduced_likelihood_function(self, theta=None):
        """
        This function determines the BLUP parameters and evaluates the reduced
        likelihood function for the given autocorrelation parameters theta.

        Maximizing this function wrt the autocorrelation parameters theta is
        equivalent to maximizing the likelihood of the assumed joint Gaussian
        distribution of the observations y evaluated onto the design of
        experiments X.

        Parameters
        ----------
        theta : array_like, optional
            An array containing the autocorrelation parameters at which the
            Gaussian Process model parameters should be determined.
            Default uses the built-in autocorrelation parameters
            (ie ``theta = self.theta_``).

        Returns
        -------
        reduced_likelihood_function_value : double
            The value of the reduced likelihood function associated to the
            given autocorrelation parameters theta.

        par : dict
            A dictionary containing the requested Gaussian Process model
            parameters:

                sigma2
                        Gaussian Process variance.
                beta
                        Generalized least-squares regression weights for
                        Universal Kriging or given beta0 for Ordinary
                        Kriging.
                gamma
                        Gaussian Process weights.
                C
                        Cholesky decomposition of the correlation matrix [R].
                Ft
                        Solution of the linear equation system : [R] x Ft = F
                G
                        QR decomposition of the matrix Ft.
        """

        if theta is None:
            # Use built-in autocorrelation parameters
            theta = self.theta_

        # Initialize output
        reduced_likelihood_function_value = - np.inf
        par = {}

        # Retrieve data
        n_samples = self.X.shape[0]
        D = self.D
        ij = self.ij
        F = self.F

        if D is None:
            # Light storage mode (need to recompute D, ij and F)
            D, ij = l1_cross_distances(self.X)
            if np.min(np.sum(D, axis=1)) == 0. \
                                    and self.corr != correlation.pure_nugget:
                raise Exception("Multiple X are not allowed")
            F = self.regr(self.X)

        # Set up R
        r = self.corr(theta, D)
        R = np.eye(n_samples) * (1. + self.nugget)
        R[ij[:, 0], ij[:, 1]] = r
        R[ij[:, 1], ij[:, 0]] = r

        # Cholesky decomposition of R
        try:
            C = linalg.cholesky(R, lower=True)
        except linalg.LinAlgError:
            return reduced_likelihood_function_value, par

        # Get generalized least squares solution
        Ft = solve_triangular(C, F, lower=True)
        try:
            Q, G = linalg.qr(Ft, econ=True)
        except:
            #/usr/lib/python2.6/dist-packages/scipy/linalg/decomp.py:1177:
            # DeprecationWarning: qr econ argument will be removed after scipy
            # 0.7. The economy transform will then be available through the
            # mode='economic' argument.
            Q, G = linalg.qr(Ft, mode='economic')
            pass

        sv = linalg.svd(G, compute_uv=False)
        rcondG = sv[-1] / sv[0]
        if rcondG < 1e-10:
            # Check F
            sv = linalg.svd(F, compute_uv=False)
            condF = sv[0] / sv[-1]
            if condF > 1e15:
                raise Exception("F is too ill conditioned. Poor combination "
                              + "of regression model and observations.")
            else:
                # Ft is too ill conditioned, get out (try different theta)
                return reduced_likelihood_function_value, par

        Yt = solve_triangular(C, self.y, lower=True)
        if self.beta0 is None:
            # Universal Kriging
            beta = solve_triangular(G, np.dot(Q.T, Yt))
        else:
            # Ordinary Kriging
            beta = np.array(self.beta0)

        rho = Yt - np.dot(Ft, beta)
        sigma2 = (rho ** 2.).sum(axis=0) / n_samples
        # The determinant of R is equal to the squared product of the diagonal
        # elements of its Cholesky decomposition C
        detR = (np.diag(C) ** (2. / n_samples)).prod()

        # Compute/Organize output
        reduced_likelihood_function_value = - sigma2.sum() * detR
        par['sigma2'] = sigma2 * self.y_std ** 2.
        par['beta'] = beta
        par['gamma'] = solve_triangular(C.T, rho)
        par['C'] = C
        par['Ft'] = Ft
        par['G'] = G

        return reduced_likelihood_function_value, par

    @deprecated("to be removed in 0.14, access ``self.theta_`` etc. directly "
                " after fit.")
    def arg_max_reduced_likelihood_function(self):
        return self._arg_max_reduced_likelihood_function()

    @property
    @deprecated('``theta`` is deprecated and will be removed in 0.14, '
                'please use ``theta_`` instead.')
    def theta(self):
        return self.theta_

    @property
    @deprecated("``reduced_likelihood_function_value`` is deprecated and will"
                "be removed in 0.14, please use "
                "``reduced_likelihood_function_value_`` instead.")
    def reduced_likelihood_function_value(self):
        return self.reduced_likelihood_function_value_

    def _arg_max_reduced_likelihood_function(self):
        """
        This function estimates the autocorrelation parameters theta as the
        maximizer of the reduced likelihood function.
        (Minimization of the opposite reduced likelihood function is used for
        convenience)

        Parameters
        ----------
        self : All parameters are stored in the Gaussian Process model object.

        Returns
        -------
        optimal_theta : array_like
            The best set of autocorrelation parameters (the sought maximizer of
            the reduced likelihood function).

        optimal_reduced_likelihood_function_value : double
            The optimal reduced likelihood function value.

        optimal_par : dict
            The BLUP parameters associated to thetaOpt.
        """

        # Initialize output
        best_optimal_theta = []
        best_optimal_rlf_value = []
        best_optimal_par = []

        if self.verbose:
            print "The chosen optimizer is: " + str(self.optimizer)
            if self.random_start > 1:
                print str(self.random_start) + " random starts are required."

        percent_completed = 0.

        # Force optimizer to fmin_cobyla if the model is meant to be isotropic
        if self.optimizer == 'Welch' and self.theta0.size == 1:
            self.optimizer = 'fmin_cobyla'

        if self.optimizer == 'fmin_cobyla':

            def minus_reduced_likelihood_function(log10t):
                return - self.reduced_likelihood_function(theta=10.
                                                                  ** log10t)[0]

            constraints = []
            for i in range(self.theta0.size):
                constraints.append(lambda log10t: \
                            log10t[i] - np.log10(self.thetaL[0, i]))
                constraints.append(lambda log10t: \
                            np.log10(self.thetaU[0, i]) - log10t[i])

            for k in range(self.random_start):

                if k == 0:
                    # Use specified starting point as first guess
                    theta0 = self.theta0
                else:
                    # Generate a random starting point log10-uniformly
                    # distributed between bounds
                    log10theta0 = np.log10(self.thetaL) \
                        + rand(self.theta0.size).reshape(self.theta0.shape) \
                        * np.log10(self.thetaU / self.thetaL)
                    theta0 = 10. ** log10theta0

                # Run Cobyla
                try:
                    log10_optimal_theta = \
                        optimize.fmin_cobyla(minus_reduced_likelihood_function,
                                np.log10(theta0), constraints, iprint=0)
                except ValueError as ve:
                    print("Optimization failed. Try increasing the ``nugget``")
                    raise ve

                optimal_theta = 10. ** log10_optimal_theta
                optimal_minus_rlf_value, optimal_par = \
                    self.reduced_likelihood_function(theta=optimal_theta)
                optimal_rlf_value = - optimal_minus_rlf_value

                # Compare the new optimizer to the best previous one
                if k > 0:
                    if optimal_rlf_value > best_optimal_rlf_value:
                        best_optimal_rlf_value = optimal_rlf_value
                        best_optimal_par = optimal_par
                        best_optimal_theta = optimal_theta
                else:
                    best_optimal_rlf_value = optimal_rlf_value
                    best_optimal_par = optimal_par
                    best_optimal_theta = optimal_theta
                if self.verbose and self.random_start > 1:
                    if (20 * k) / self.random_start > percent_completed:
                        percent_completed = (20 * k) / self.random_start
                        print "%s completed" % (5 * percent_completed)

            optimal_rlf_value = best_optimal_rlf_value
            optimal_par = best_optimal_par
            optimal_theta = best_optimal_theta

        elif self.optimizer == 'Welch':

            # Backup of the given atrributes
            theta0, thetaL, thetaU = self.theta0, self.thetaL, self.thetaU
            corr = self.corr
            verbose = self.verbose

            # This will iterate over fmin_cobyla optimizer
            self.optimizer = 'fmin_cobyla'
            self.verbose = False

            # Initialize under isotropy assumption
            if verbose:
                print("Initialize under isotropy assumption...")
            self.theta0 = array2d(self.theta0.min())
            self.thetaL = array2d(self.thetaL.min())
            self.thetaU = array2d(self.thetaU.max())
            theta_iso, optimal_rlf_value_iso, par_iso = \
                self._arg_max_reduced_likelihood_function()
            optimal_theta = theta_iso + np.zeros(theta0.shape)

            # Iterate over all dimensions of theta allowing for anisotropy
            if verbose:
                print("Now improving allowing for anisotropy...")
            for i in self.random_state.permutation(theta0.size):
                if verbose:
                    print "Proceeding along dimension %d..." % (i + 1)
                self.theta0 = array2d(theta_iso)
                self.thetaL = array2d(thetaL[0, i])
                self.thetaU = array2d(thetaU[0, i])

                def corr_cut(t, d):
                    return corr(array2d(np.hstack([
                         optimal_theta[0][0:i],
                         t[0],
                         optimal_theta[0][(i + 1)::]])), d)

                self.corr = corr_cut
                optimal_theta[0, i], optimal_rlf_value, optimal_par = \
                    self._arg_max_reduced_likelihood_function()

            # Restore the given atrributes
            self.theta0, self.thetaL, self.thetaU = theta0, thetaL, thetaU
            self.corr = corr
            self.optimizer = 'Welch'
            self.verbose = verbose

        else:

            raise NotImplementedError(("This optimizer ('%s') is not "
                                    + "implemented yet. Please contribute!")
                                    % self.optimizer)

        return optimal_theta, optimal_rlf_value, optimal_par

    def _check_params(self, n_samples=None):

        # Check regression model
        if not callable(self.regr):
            if self.regr in self._regression_types:
                self.regr = self._regression_types[self.regr]
            else:
                raise ValueError(("regr should be one of %s or callable, "
                               + "%s was given.")
                               % (self._regression_types.keys(), self.regr))

        # Check regression weights if given (Ordinary Kriging)
        if self.beta0 is not None:
            self.beta0 = array2d(self.beta0)
            if self.beta0.shape[1] != 1:
                # Force to column vector
                self.beta0 = self.beta0.T

        # Check correlation model
        if not callable(self.corr):
            if self.corr in self._correlation_types:
                self.corr = self._correlation_types[self.corr]
            else:
                raise ValueError(("corr should be one of %s or callable, "
                               + "%s was given.")
                               % (self._correlation_types.keys(), self.corr))

        # Check storage mode
        if self.storage_mode != 'full' and self.storage_mode != 'light':
            raise ValueError("Storage mode should either be 'full' or "
                           + "'light', %s was given." % self.storage_mode)

        # Check correlation parameters
        self.theta0 = array2d(self.theta0)
        lth = self.theta0.size

        if self.thetaL is not None and self.thetaU is not None:
            self.thetaL = array2d(self.thetaL)
            self.thetaU = array2d(self.thetaU)
            if self.thetaL.size != lth or self.thetaU.size != lth:
                raise ValueError("theta0, thetaL and thetaU must have the "
                               + "same length.")
            if np.any(self.thetaL <= 0) or np.any(self.thetaU < self.thetaL):
                raise ValueError("The bounds must satisfy O < thetaL <= "
                               + "thetaU.")

        elif self.thetaL is None and self.thetaU is None:
            if np.any(self.theta0 <= 0):
                raise ValueError("theta0 must be strictly positive.")

        elif self.thetaL is None or self.thetaU is None:
            raise ValueError("thetaL and thetaU should either be both or "
                           + "neither specified.")

        # Force verbose type to bool
        self.verbose = bool(self.verbose)

        # Force normalize type to bool
        self.normalize = bool(self.normalize)

        # Check nugget value
        self.nugget = np.asarray(self.nugget)
        if np.any(self.nugget) < 0.:
            raise ValueError("nugget must be positive or zero.")
        if (n_samples is not None
            and self.nugget.shape not in [(), (n_samples,)]):
            raise ValueError("nugget must be either a scalar "
                             "or array of length n_samples.")

        # Check optimizer
        if not self.optimizer in self._optimizer_types:
            raise ValueError("optimizer should be one of %s"
                           % self._optimizer_types)

        # Force random_start type to int
        self.random_start = int(self.random_start)