def __init__(self, *args, **kwargs):
     super(HelpExplorer, self).__init__(*args, **kwargs)
     self.vbox = vbox = QVBoxLayout()
     self.combo = QComboBox(parent=self)
     vbox.addWidget(self.combo)
     if _viewers:
         self.viewers = _viewers.copy()
         for w in self.viewers.values():
             w.setParent(self)
     else:
         self.viewers = OrderedDict(
             [(key, cls(parent=self)) for key, cls in six.iteritems(
                 self.viewers)])
         # save the UrlHelp because QWebEngineView creates child processes
         # that are not properly closed by PyQt and as such use too much
         # memory
         if is_running_tests():
             for key, val in self.viewers.items():
                 _viewers[key] = val
     for key, ini in six.iteritems(self.viewers):
         self.combo.addItem(key)
         ini.hide()
         vbox.addWidget(ini)
     self.viewer = next(six.itervalues(self.viewers))
     self.viewer.show()
     self.combo.currentIndexChanged[str].connect(self.set_viewer)
     self.setLayout(vbox)
 def __init__(self, *args, **kwargs):
     super(ProjectContent, self).__init__(*args, **kwargs)
     self.lists = OrderedDict()
     for attr in chain(['All'], sorted(Project._registered_plotters)):
         item = self.add_plotterlist(attr, force=(attr == 'All'))
         self.lists[attr] = item
     self.currentChanged.connect(self.update_current_list)
     Project.oncpchange.connect(self.update_lists)
Exemple #3
0
 def make_run_config(self, sp, info):
     for orig in self.names:
         info[orig] = d = OrderedDict()
         for plotter in sp(standard_name=orig).plotters:
             d[plotter.data.pctl if plotter.data.name.
               startswith('all') else int(plotter.data.pctl.values
                                          )] = pctl_d = OrderedDict()
             for key in ['rsquared', 'slope', 'intercept']:
                 val = plotter.plot_data[1].attrs.get(key)
                 if val is not None:
                     pctl_d[key] = float(val)
     return info
Exemple #4
0
    def fill_combos_from_project(self, project):
        """Fill :attr:`group_combo` and :attr:`fmt_combo` from a project

        Parameters
        ----------
        project: psyplot.project.Project
            The project to use"""
        current_text = self.group_combo.currentText()
        with self.no_fmtos_update:
            self.group_combo.clear()
            if project is None or project.is_main or not len(project.plotters):
                self.fmt_combo.clear()
                self.groups = []
                self.fmtos = []
                self.line_edit.setEnabled(False)
                return
            self.line_edit.setEnabled(True)
            # get dimensions
            coords = sorted(project.coords_intersect)
            coords_name = [COORDSGROUP] if coords else []
            coords_verbose = ['Dimensions'] if coords else []
            coords = [coords] if coords else []

            # get formatoptions and group them alphabetically
            grouped_fmts = defaultdict(list)
            for fmto in project._fmtos:
                grouped_fmts[fmto.group].append(fmto)
            for val in six.itervalues(grouped_fmts):
                val.sort(key=self.get_name)
            grouped_fmts = OrderedDict(
                sorted(six.iteritems(grouped_fmts),
                       key=lambda t: psyp.groups.get(t[0], t[0])))
            fmt_groups = list(grouped_fmts.keys())
            # save original names
            self.groups = coords_name + [ALLGROUP] + fmt_groups
            # save verbose group names (which are used in the combo box)
            self.groupnames = coords_verbose + ['All formatoptions'] + list(
                map(lambda s: psyp.groups.get(s, s), fmt_groups))
            # save formatoptions
            fmtos = list(grouped_fmts.values())
            self.fmtos = coords + [sorted(chain(*fmtos), key=self.get_name)
                                   ] + fmtos
            self.group_combo.addItems(self.groupnames)
            ind = self.group_combo.findText(current_text)
            self.group_combo.setCurrentIndex(ind if ind >= 0 else 0)
        self.fill_fmt_combo(self.group_combo.currentIndex())
Exemple #5
0
 def __init__(self, *args, **kwargs):
     super(HelpExplorer, self).__init__(*args, **kwargs)
     self.vbox = vbox = QVBoxLayout()
     self.combo = QComboBox(parent=self)
     vbox.addWidget(self.combo)
     self.viewers = OrderedDict([
         (key, cls(parent=self)) for key, cls in six.iteritems(self.viewers)
     ])
     for key, ini in six.iteritems(self.viewers):
         self.combo.addItem(key)
         ini.hide()
         vbox.addWidget(ini)
     self.viewer = next(six.itervalues(self.viewers))
     self.viewer.show()
     self.combo.currentIndexChanged[str].connect(self.set_viewer)
     self.setLayout(vbox)
Exemple #6
0
    def __init__(self, parent, fmto, project):
        """
        Parameters
        ----------
        parent: psyplot_gui.fmt_widget.FormatoptionWidget
            The formatoption widget where this widget is inserted
        fmto: psyplot.plotter.Formatoption
            The formatoption that is represented by this widget
        project: psyplot.project.Project
            The current psyplot subproject"""
        QWidget.__init__(self, parent)
        hbox = QHBoxLayout()

        # Create a combo box for the rcParams 'labels' key
        label_combo = DictCombo(fmto.rc['labels'], parent, modulo_style=False)
        hbox.addWidget(label_combo)

        # Create a combo for the :attr:`enhanced_attrs`
        attrs = OrderedDict(
            sorted(
                utils.join_dicts([
                    getattr(plotter, fmto.key).enhanced_attrs
                    for plotter in project.plotters
                ],
                                 delimiter=', ').items()))
        attr_combo = DictCombo(attrs, parent)
        hbox.addWidget(attr_combo)

        fmtos = [
            # add a button to change to the properties formatoption
            getattr(fmto.plotter, fmto.key + 'props', None),
            getattr(fmto.plotter, fmto.key + 'size', None),
            getattr(fmto.plotter, fmto.key + 'weight', None)
        ]
        fmtos = list(filter(None, fmtos))
        if fmtos:
            hbox.addWidget(Switch2FmtButton(parent, *fmtos))

        self.setLayout(hbox)
Exemple #7
0
 def run(self, info):
     logger = self.logger
     logger.info('Calculating %s evaluation', self.name)
     quants_info = self.config['evaluation'].get('quants')
     ks_info = self.config['evaluation'].get('ks')
     missing = []
     if quants_info is None:
         missing.append('quants task')
     if ks_info is None:
         missing.append('ks task')
     if missing:
         raise ValueError("%s requires that %s has been run before!" %
                          (self.name, ' and '.join(missing)))
     quantiles = self.task_config.quantiles
     if quantiles is None:
         quantiles = slice(None)
     possible_names = {
         'wind', 'prcp', 'tmin', 'tmax', 'mean_cloud', 'cloud'
     }
     for v, v_ks in ks_info.items():
         if v not in quants_info or v not in possible_names:
             continue
         #: Dataframe with intercept, rsquared and slope on index and
         #: quantiles as columns
         df = pd.DataFrame(quants_info[v]).loc[:, quantiles]
         try:
             del df['All']
         except KeyError:
             pass
         slope = float((1 - np.abs(1 - df.loc['slope'].values)).mean())
         rsquared = float(df.loc['rsquared'].values.mean())
         info[v] = OrderedDict([
             ('rsquared', rsquared), ('slope', slope), ('ks', v_ks / 100.),
             ('quality', float(np.mean([rsquared, slope, v_ks / 100.])))
         ])
     if logger.isEnabledFor(logging.DEBUG):
         logger.debug('Simulation quality:')
         for name, val in info.items():
             logger.debug('    %s: %6.3f' % (name, val['quality']))
Exemple #8
0
    def run(self, info):

        self.__setup = False

        if self.setup_from == 'scratch':
            df = self.data
            # we may use a parallel setup which requires a weighted average
            g = df.groupby(level='station_id')
            total_counts = g.counts.transform("sum")
            df['lat'] = df.counts / total_counts * df.lat
            df['lon'] = df.counts / total_counts * df.lon
            df['lat_std'] = (df.counts / total_counts) * df.lat_std**2
            df['lon_std'] = (df.counts / total_counts) * df.lon_std**2
            eecra = g.agg(
                OrderedDict([('lat', 'sum'), ('lon', 'sum'),
                             ('lat_std', 'sum'), ('lon_std', 'sum'),
                             ('year', ('min', 'max')), ('counts', 'sum')]))
            eecra.columns = [
                'lat', 'lon', 'lat_std', 'lon_std', 'firstyear', 'lastyear',
                'counts'
            ]
            eecra[['lat_std', 'lon_std']] **= 0.5

            use_xstall = self.task_config.xstall

            if use_xstall:
                to_replace = self.xstall_df
                # keep only matching stations
                to_replace = to_replace.join(eecra[[]], how='inner')
                eecra.loc[to_replace.index, ['lat', 'lon']] = to_replace
            self.data = eecra

        if self.task_config.to_csv:
            self.write2file()
        if self.task_config.to_db:
            self.write2db()
Exemple #9
0
from psyplot import rcParams
import psyplot.config as psyc
try:
    from textwrap import indent
except ImportError:

    def indent(text, prefix, predicate=None):  # python2
        return '\n'.join(prefix + s if predicate is None or predicate(s) else s
                         for s in text.splitlines())


docstrings = psyp.docstrings

psyc.setup_logging(osp.join(osp.dirname(__file__), 'logging.yml'))

results = OrderedDict()


class TestFormatoption(psyp.Formatoption):
    @property
    def default(self):
        try:
            return super(TestFormatoption, self).default
        except KeyError:
            return ''

    _validate = str

    def update(self, value):
        key = '%s.%s' % (self.plotter.data.psy.arr_name, self.key)
        if not value:
Exemple #10
0
from functools import partial

if with_qt5:
    from PyQt5.QtWidgets import QSpinBox, QFontDialog, QColorDialog
else:
    from PyQt4.QtGui import QSpinBox, QFontDialog, QColorDialog

if with_qt5:
    weights_mpl2qt = OrderedDict([
        ('ultralight', QtGui.QFont.ExtraLight),
        ('light', QtGui.QFont.Light),
        ('normal', QtGui.QFont.Normal),
        ('regular', QtGui.QFont.Normal),
        ('book', QtGui.QFont.Normal),
        ('medium', QtGui.QFont.Medium),
        ('roman', QtGui.QFont.Medium),
        ('semibold', QtGui.QFont.DemiBold),
        ('demibold', QtGui.QFont.DemiBold),
        ('demi', QtGui.QFont.DemiBold),
        ('bold', QtGui.QFont.Bold),
        ('heavy', QtGui.QFont.Bold),
        ('extra bold', QtGui.QFont.ExtraBold),
        ('black', QtGui.QFont.Black),
    ])
else:
    weights_mpl2qt = OrderedDict([
        ('ultralight', QtGui.QFont.Light),
        ('light', QtGui.QFont.Light),
        ('normal', QtGui.QFont.Normal),
        ('regular', QtGui.QFont.Normal),
        ('book', QtGui.QFont.Normal),
        ('medium', QtGui.QFont.Normal),
Exemple #11
0
 def __getitem__(self, key):
     try:
         return OrderedDict.__getitem__(self, key)
     except KeyError:
         return self.__missing__(key)
Exemple #12
0
 def __repr__(self):
     return 'DefaultOrderedDict(%s, %s)' % (self.default_factory,
                                            OrderedDict.__repr__(self))
Exemple #13
0
 def __init__(self, default_factory=None, *a, **kw):
     if (default_factory is not None and not callable(default_factory)):
         raise TypeError('first argument must be callable')
     OrderedDict.__init__(self, *a, **kw)
     self.default_factory = default_factory
Exemple #14
0
 def __init__(self, *args, **kwargs):
     super(QuantileEvaluation, self).__init__(*args, **kwargs)
     names = self.task_config.names
     if names is not None:
         self.names = OrderedDict(t for t in self.names.items()
                                  if t[0] in names)
Exemple #15
0
class HelpExplorer(QWidget, DockMixin):
    """A widget for showing the documentation. It behaves somewhat similar
    to spyders object inspector plugin and can show restructured text either
    as html (if sphinx is installed) or as plain text. It furthermore has a
    browser to show html content

    Warnings
    --------
    The :class:`HelpBrowser` class is known to crash under PyQt4 when new web
    page domains are loaded. Hence you should disable the browsing to different
    remote websites and even disable intersphinx"""

    #: The viewer classes used by the help explorer. :class:`HelpExplorer`
    #: instances replace this attribute with the corresponding HelpMixin
    #: instance
    viewers = OrderedDict([('HTML help', UrlHelp), ('Plain text', TextHelp)])

    def __init__(self, *args, **kwargs):
        super(HelpExplorer, self).__init__(*args, **kwargs)
        self.vbox = vbox = QVBoxLayout()
        self.combo = QComboBox(parent=self)
        vbox.addWidget(self.combo)
        self.viewers = OrderedDict([
            (key, cls(parent=self)) for key, cls in six.iteritems(self.viewers)
        ])
        for key, ini in six.iteritems(self.viewers):
            self.combo.addItem(key)
            ini.hide()
            vbox.addWidget(ini)
        self.viewer = next(six.itervalues(self.viewers))
        self.viewer.show()
        self.combo.currentIndexChanged[str].connect(self.set_viewer)
        self.setLayout(vbox)

    def set_viewer(self, name):
        """Sets the current documentation viewer

        Parameters
        ----------
        name: str or object
            A string must be one of the :attr:`viewers` attribute. An object
            can be one of the values in the :attr:`viewers` attribute"""
        if isstring(name) and asstring(name) not in self.viewers:
            raise ValueError("Don't have a viewer named %s" % (name, ))
        elif not isstring(name):
            viewer = name
        else:
            name = asstring(name)
            viewer = self.viewers[name]
        self.viewer.hide()
        self.viewer = viewer
        self.viewer.show()
        if (isstring(name) and not self.combo.currentText() == name):
            self.combo.setCurrentIndex(list(self.viewers).index(name))

    @docstrings.dedent
    def show_help(self, obj, oname='', files=None):
        """
        Show the documentaion of the given object

        We first try to use the current viewer based upon it's
        :attr:`HelpMixin.can_document_object` attribute. If this does not work,
        we check the other viewers

        Parameters
        ----------
        %(HelpMixin.show_help.parameters)s"""
        oname = asstring(oname)
        ret = None
        if self.viewer.can_document_object:
            try:
                ret = self.viewer.show_help(obj, oname=oname, files=files)
            except Exception:
                logger.debug("Could not document %s with %s viewer!",
                             oname,
                             self.combo.currentText(),
                             exc_info=True)
        else:
            curr_i = self.combo.currentIndex()
            for i, (viewername,
                    viewer) in enumerate(six.iteritems(self.viewers)):
                if i != curr_i and viewer.can_document_object:
                    self.set_viewer(viewername)
                    self.combo.blockSignals(True)
                    self.combo.setCurrentIndex(i)
                    self.combo.blockSignals(False)
                    try:
                        ret = viewer.show_help(obj, oname=oname, files=files)
                    except Exception:
                        logger.debug("Could not document %s with %s viewer!",
                                     oname,
                                     viewername,
                                     exc_info=True)
        if ret:
            self.parent().raise_()
        return ret

    @docstrings.dedent
    def show_rst(self, text, oname='', files=None):
        """
        Show restructured text

        We first try to use the current viewer based upon it's
        :attr:`HelpMixin.can_show_rst` attribute. If this does not work,
        we check the other viewers

        Parameters
        ----------
        %(HelpMixin.show_rst.parameters)s"""
        ret = None
        if self.viewer.can_show_rst:
            ret = self.viewer.show_rst(text, oname=oname, files=files)
        else:
            for viewer in six.itervalues(self.viewers):
                if viewer.can_show_rst:
                    self.set_viewer(viewer)
                    ret = viewer.show_rst(text, oname=oname, files=files)
                    break
        if ret:
            self.parent().raise_()
        return ret

    @docstrings.dedent
    def show_intro(self, text=''):
        """
        Show an intro text

        We first try to use the current viewer based upon it's
        :attr:`HelpMixin.can_show_rst` attribute. If this does not work,
        we check the other viewers

        Parameters
        ----------
        %(HelpMixin.show_intro.parameters)s"""
        found = False
        for i, viewer in enumerate(six.itervalues(self.viewers)):
            viewer.show_intro(text)
            if not found and viewer.can_show_rst:
                if i:
                    self.set_viewer(viewer)
                found = True

    def close(self, *args, **kwargs):
        self.viewers['HTML help'].close(*args, **kwargs)
        return super(HelpExplorer, self).close(*args, **kwargs)
Exemple #16
0
    def fill_combos_from_project(self, project):
        """Fill :attr:`group_combo` and :attr:`fmt_combo` from a project

        Parameters
        ----------
        project: psyplot.project.Project
            The project to use"""
        if rcParams['fmt.sort_by_key']:
            def sorter(fmto):
                return fmto.key
        else:
            sorter = self.get_name

        current_text = self.group_combo.currentText()
        with self.no_fmtos_update:
            self.group_combo.clear()
            if project is None or project.is_main or not len(project):
                self.fmt_combo.clear()
                self.groups = []
                self.fmtos = []
                self.line_edit.setEnabled(False)
                return
            self.line_edit.setEnabled(True)
            # get dimensions
            it_vars = chain.from_iterable(
                arr.psy.iter_base_variables for arr in project.arrays)
            dims = next(it_vars).dims
            sdims = set(dims)
            for var in it_vars:
                sdims.intersection_update(var.dims)
            coords = [d for d in dims if d in sdims]
            coords_name = [COORDSGROUP] if coords else []
            coords_verbose = ['Dimensions'] if coords else []
            coords = [coords] if coords else []

            if len(project.plotters):
                # get formatoptions and group them alphabetically
                grouped_fmts = defaultdict(list)
                for fmto in project._fmtos:
                    grouped_fmts[fmto.group].append(fmto)
                for val in six.itervalues(grouped_fmts):
                    val.sort(key=sorter)
                grouped_fmts = OrderedDict(
                    sorted(six.iteritems(grouped_fmts),
                           key=lambda t: psyp.groups.get(t[0], t[0])))
                fmt_groups = list(grouped_fmts.keys())
                # save original names
                self.groups = coords_name + [ALLGROUP] + fmt_groups
                # save verbose group names (which are used in the combo box)
                self.groupnames = (
                    coords_verbose + ['All formatoptions'] + list(
                        map(lambda s: psyp.groups.get(s, s), fmt_groups)))
                # save formatoptions
                fmtos = list(grouped_fmts.values())
                self.fmtos = coords + [sorted(
                    chain(*fmtos), key=sorter)] + fmtos
            else:
                self.groups = coords_name
                self.groupnames = coords_verbose
                self.fmtos = coords
            self.group_combo.addItems(self.groupnames)
            ind = self.group_combo.findText(current_text)
            self.group_combo.setCurrentIndex(ind if ind >= 0 else 0)
        self.fill_fmt_combo(self.group_combo.currentIndex())
Exemple #17
0
        return pathlib.Path(fname).as_uri()

except ImportError:

    def file2html(fname):
        return 'file://' + fname


def html2file(url):
    p = urlparse(asstring(url))
    # skip the first '/' on windows platform
    return osp.abspath(osp.join(p.netloc,
                                p.path[int(sys.platform == 'win32'):]))


_viewers = OrderedDict()


logger = logging.getLogger(__name__)


class UrlCombo(QComboBox):
    """A editable ComboBox with autocompletion"""

    def __init__(self, *args, **kwargs):
        super(UrlCombo, self).__init__(*args, **kwargs)
        self.setInsertPolicy(self.InsertAtTop)
        self.setFocusPolicy(Qt.StrongFocus)
        self.setEditable(True)
        self.completer = QCompleter(self)
class ProjectContent(QToolBox):
    """Display the content in the current project

    This toolbox contains several :class:`PlotterList` that show the content
    of the current main and subproject"""

    #: :class:`OrderedDict` containing the :class:`PlotterList` instances
    #: of the different selection attributes
    lists = OrderedDict()

    @property
    def current_names(self):
        return [self.itemText(i) for i in range(self.count())]

    def __init__(self, *args, **kwargs):
        super(ProjectContent, self).__init__(*args, **kwargs)
        self.lists = OrderedDict()
        for attr in chain(['All'], sorted(Project._registered_plotters)):
            item = self.add_plotterlist(attr, force=(attr == 'All'))
            self.lists[attr] = item
        self.currentChanged.connect(self.update_current_list)
        Project.oncpchange.connect(self.update_lists)

    def enable_list(self, list_widget):
        """Enable a given list widget based upon whether it is empty or not"""
        i = self.indexOf(list_widget)
        if i != -1:
            self.setItemEnabled(i, not list_widget.is_empty)

    def add_plotterlist(self, identifier, force=False):
        """Create a :class:`PlotterList` from an identifier from the
        :class:`psyplot.project.Project` class"""
        attr = identifier if identifier != 'All' else None
        item = PlotterList(attr)
        if not item.can_import_plotter:
            return item
        if force or not item.is_empty:
            item.setParent(self)
            item.updated_from_project.connect(self.enable_list)
            self.addItem(item, identifier)
            i = self.indexOf(item)
            self.setItemEnabled(i, not item.is_empty)
        return item

    def update_current_list(self):
        """Update the current list from the current main and sub project"""
        self.currentWidget().update_from_project(gcp(True))
        self.currentWidget().update_from_project(gcp())

    def update_lists(self, p):
        # check new lists
        current_items = self.current_names
        for name, l in self.lists.items():
            if not p.is_main:
                l.update_from_project(p.main)
            l.update_from_project(p)
            if l.is_empty:
                l.disconnect_items()
            if name != 'All' and l.is_empty:
                i = self.indexOf(l)
                self.removeItem(i)
            elif not l.is_empty and name not in current_items:
                self.addItem(l, name)
Exemple #19
0
    def recursive_processing(self, base_dir, target_dir, it):
        """Method to recursivly process the notebooks in the `base_dir`

        Parameters
        ----------
        base_dir: str
            Path to the base example directory (see the `examples_dir`
            parameter for the :class:`Gallery` class)
        target_dir: str
            Path to the output directory for the rst files (see the
            `gallery_dirs` parameter for the :class:`Gallery` class)
        it: iterable
            The iterator over the subdirectories and files in `base_dir`
            generated by the :func:`os.walk` function"""
        try:
            file_dir, dirs, files = next(it)
        except StopIteration:
            return '', []
        readme_files = {'README.md', 'README.rst', 'README.txt'}
        if readme_files.intersection(files):
            foutdir = file_dir.replace(base_dir, target_dir)
            create_dirs(foutdir)
            this_nbps = [
                NotebookProcessor(
                    infile=f,
                    outfile=os.path.join(foutdir, os.path.basename(f)),
                    disable_warnings=self.disable_warnings,
                    preprocess=(
                        (self.preprocess is True or f in self.preprocess) and
                        not (self.dont_preprocess is True or
                             f in self.dont_preprocess)),
                    clear=((self.clear is True or f in self.clear) and not
                           (self.dont_clear is True or f in self.dont_clear)))
                for f in map(lambda f: os.path.join(file_dir, f),
                             filter(self.pattern.match, files))]
            readme_file = next(iter(readme_files.intersection(files)))
        else:
            return '', []
        labels = OrderedDict()
        this_label = 'gallery_' + foutdir.replace(os.path.sep, '_')
        if this_label.endswith('_'):
            this_label = this_label[:-1]
        for d in dirs:
            label, nbps = self.recursive_processing(
                base_dir, target_dir, it)
            if label:
                labels[label] = nbps
        s = ".. _%s:\n\n" % this_label
        with open(os.path.join(file_dir, readme_file)) as f:
            s += f.read().rstrip() + '\n\n'

        s += "\n\n.. toctree::\n\n"
        s += ''.join('    %s\n' % os.path.splitext(os.path.basename(
            nbp.get_out_file()))[0] for nbp in this_nbps)
        for d in dirs:
            findex = os.path.join(d, 'index.rst')
            if os.path.exists(os.path.join(foutdir, findex)):
                s += '    %s\n' % os.path.splitext(findex)[0]

        s += '\n'

        for nbp in this_nbps:
                s += nbp.thumbnail_div + '\n'
        s += "\n.. raw:: html\n\n    <div style='clear:both'></div>\n"
        for label, nbps in labels.items():
            s += '\n.. only:: html\n\n    .. rubric:: :ref:`%s`\n\n' % (
                label)
            for nbp in nbps:
                s += nbp.thumbnail_div + '\n'
            s += "\n.. raw:: html\n\n    <div style='clear:both'></div>\n"

        s += '\n'

        with open(os.path.join(foutdir, 'index.rst'), 'w') as f:
            f.write(s)
        return this_label, list(chain(this_nbps, *labels.values()))
Exemple #20
0
class UrlBrowser(QFrame):
    """Very simple browser with session history and autocompletion based upon
    the :class:`PyQt5.QtWebEngineWidgets.QWebEngineView` class

    Warnings
    --------
    This class is known to crash under PyQt4 when new web page domains are
    loaded. Hence it should be handled with care"""

    completed = _temp_bool_prop(
        'completed',
        "Boolean whether the html page loading is completed.",
        default=True)

    url_like_re = re.compile('^\w+://')

    doc_urls = OrderedDict([
        ('startpage', 'https://startpage.com/'),
        ('psyplot', 'http://psyplot.readthedocs.org/en/latest/'),
        ('pyplot', 'http://matplotlib.org/api/pyplot_api.html'),
        ('seaborn', 'http://stanford.edu/~mwaskom/software/seaborn/api.html'),
        ('cartopy', 'http://scitools.org.uk/cartopy/docs/latest/index.html'),
        ('xarray', 'http://xarray.pydata.org/en/stable/'),
        ('pandas', 'http://pandas.pydata.org/pandas-docs/stable/'),
        ('numpy', 'https://docs.scipy.org/doc/numpy/reference/routines.html'),
    ])

    #: The initial url showed in the webview. If None, nothing will be
    #: displayed
    default_url = None

    #: adress line
    tb_url = None

    #: button to go to previous url
    bt_back = None

    #: button to go to next url
    bt_ahead = None

    #: refresh the current url
    bt_refresh = None

    #: button to go lock to the current url
    bt_lock = None

    #: button to disable browsing in www
    bt_url_lock = None

    #: The upper part of the browser containing all the buttons
    button_box = None

    #: The upper most layout aranging the button box and the html widget
    vbox = None

    def __init__(self, *args, **kwargs):
        super(UrlBrowser, self).__init__(*args, **kwargs)

        # ---------------------------------------------------------------------
        # ---------------------------- upper buttons --------------------------
        # ---------------------------------------------------------------------
        # adress line
        self.tb_url = UrlCombo(self)
        # button to go to previous url
        self.bt_back = QToolButton(self)
        # button to go to next url
        self.bt_ahead = QToolButton(self)
        # refresh the current url
        self.bt_refresh = QToolButton(self)
        # button to go lock to the current url
        self.bt_lock = QToolButton(self)
        # button to disable browsing in www
        self.bt_url_lock = QToolButton(self)

        # ---------------------------- buttons settings -----------------------
        self.bt_back.setIcon(QIcon(get_icon('previous.png')))
        self.bt_back.setToolTip('Go back one page')
        self.bt_ahead.setIcon(QIcon(get_icon('next.png')))
        self.bt_back.setToolTip('Go forward one page')

        self.bt_refresh.setIcon(QIcon(get_icon('refresh.png')))
        self.bt_refresh.setToolTip('Refresh the current page')

        self.bt_lock.setCheckable(True)
        self.bt_url_lock.setCheckable(True)

        if not with_qt5 and rcParams['help_explorer.online'] is None:
            # We now that the browser can crash with Qt4, therefore we disable
            # the browing in the internet
            self.bt_url_lock.click()
            rcParams['help_explorer.online'] = False
        elif rcParams['help_explorer.online'] is False:
            self.bt_url_lock.click()
        elif rcParams['help_explorer.online'] is None:
            rcParams['help_explorer.online'] = True
        rcParams.connect('help_explorer.online', self.update_url_lock_from_rc)

        self.bt_url_lock.clicked.connect(self.toogle_url_lock)
        self.bt_lock.clicked.connect(self.toogle_lock)

        # tooltip and icons of lock and url_lock are set in toogle_lock and
        # toogle_url_lock
        self.toogle_lock()
        self.toogle_url_lock()

        # ---------------------------------------------------------------------
        # --------- initialization and connection of the web view -------------
        # ---------------------------------------------------------------------

        #: The actual widget showing the html content
        self.html = QWebEngineView(parent=self)
        self.html.loadStarted.connect(self.completed)
        self.html.loadFinished.connect(self.completed)

        self.tb_url.currentIndexChanged[str].connect(self.browse)
        self.bt_back.clicked.connect(self.html.back)
        self.bt_ahead.clicked.connect(self.html.forward)
        self.bt_refresh.clicked.connect(self.html.reload)
        self.html.urlChanged.connect(self.url_changed)

        # ---------------------------------------------------------------------
        # ---------------------------- layouts --------------------------------
        # ---------------------------------------------------------------------

        # The upper part of the browser containing all the buttons
        self.button_box = button_box = QHBoxLayout()

        button_box.addWidget(self.bt_back)
        button_box.addWidget(self.bt_ahead)
        button_box.addWidget(self.tb_url)
        button_box.addWidget(self.bt_refresh)
        button_box.addWidget(self.bt_lock)
        button_box.addWidget(self.bt_url_lock)

        # The upper most layout aranging the button box and the html widget
        self.vbox = vbox = QVBoxLayout()
        self.vbox.setContentsMargins(0, 0, 0, 0)
        vbox.addLayout(button_box)

        vbox.addWidget(self.html)

        self.setLayout(vbox)

        if self.default_url is not None:
            self.tb_url.addItem(self.default_url)

    def browse(self, url):
        """Make a web browse on the given url and show the page on the Webview
        widget. """
        if self.bt_lock.isChecked():
            return
        if not self.url_like_re.match(url):
            url = 'https://' + url
        if self.bt_url_lock.isChecked() and url.startswith('http'):
            return
        if not self.completed:
            logger.debug('Stopping current load...')
            self.html.stop()
            self.completed = True
        logger.debug('Loading %s', url)
        # we use :meth:`PyQt5.QtWebEngineWidgets.QWebEngineView.setUrl` instead
        # of :meth:`PyQt5.QtWebEngineWidgets.QWebEngineView.load` because that
        # changes the url directly and is more useful for unittests
        self.html.setUrl(QtCore.QUrl(url))

    def url_changed(self, url):
        """Triggered when the url is changed to update the adress line"""
        try:
            url = url.toString()
        except AttributeError:
            pass
        logger.debug('url changed to %s', url)
        try:
            self.tb_url.setCurrentText(url)
        except AttributeError:  # Qt4
            self.tb_url.setEditText(url)
        self.tb_url.add_text_on_top(url, block=True)

    def update_url_lock_from_rc(self, online):
        if (online and self.bt_url_lock.isChecked()
                or not online and not self.bt_url_lock.isChecked()):
            self.bt_url_lock.click()

    def toogle_url_lock(self):
        """Disable (or enable) the loading of web pages in www"""
        bt = self.bt_url_lock
        offline = bt.isChecked()
        bt.setIcon(QIcon(
            get_icon('world_red.png' if offline else 'world.png')))
        online_message = "Go online"
        if not with_qt5:
            online_message += ("\nWARNING: This mode is unstable under Qt4 "
                               "and might result in a complete program crash!")
        bt.setToolTip(online_message if offline else "Offline mode")
        if rcParams['help_explorer.online'] is offline:
            rcParams['help_explorer.online'] = not offline

    def toogle_lock(self):
        """Disable (or enable) the changing of the current webpage"""
        bt = self.bt_lock
        bt.setIcon(
            QIcon(get_icon('lock.png' if bt.isChecked() else 'lock_open.png')))
        bt.setToolTip("Unlock" if bt.isChecked() else "Lock to current page")
Exemple #21
0
 def __init__(self, *args, **kwargs):
     super(FuncArgParser, self).__init__(*args, **kwargs)
     self.__arguments = OrderedDict()
     self.__funcs = []
     self.__main = None
Exemple #22
0
class FuncArgParser(ArgumentParser):
    """Subclass of an argument parser that get's parts of the information
    from a given function"""

    def __init__(self, *args, **kwargs):
        super(FuncArgParser, self).__init__(*args, **kwargs)
        self.__arguments = OrderedDict()
        self.__funcs = []
        self.__main = None

    def setup_args(self, func):
        """Add the parameters from the given `func` to the parameter settings
        """
        self.__funcs.append(func)
        args_dict = self.__arguments
        args, varargs, varkw, defaults = inspect.getargspec(func)
        full_doc = inspect.getdoc(func)
        doc = docstrings._get_section(full_doc, 'Parameters') + '\n'
        doc += docstrings._get_section(full_doc, 'Other Parameters')
        doc = doc.rstrip()
        default_min = len(args) - len(defaults)
        for i, arg in enumerate(args):
            if arg == 'self' or arg in args_dict:
                continue
            arg_doc = docstrings._keep_params(doc, [arg]) or \
                docstrings._keep_types(doc, [arg])
            args_dict[arg] = d = {'dest': arg, 'short': arg, 'long': arg}
            if arg_doc:
                lines = arg_doc.splitlines()
                d['help'] = '\n'.join(lines[1:])
                metavar = lines[0].split(':', 1)
                if i >= default_min:
                    d['default'] = defaults[i - default_min]
                if len(metavar) > 1:
                    dtype = metavar[1].strip()
                    if dtype == 'bool' and 'default' in d:
                        d['action'] = 'store_false' if d['default'] else \
                            'store_true'
                    else:
                        d['metavar'] = metavar[1].strip()
                else:
                    d['positional'] = True

    def update_arg(self, arg, if_existent=True, **kwargs):
        """Update the `add_argument` data for the given parameter
        """
        if not if_existent:
            self.__arguments.setdefault(arg, kwargs)
        self.__arguments[arg].update(kwargs)

    def pop_key(self, arg, key, *args, **kwargs):
        """Delete a previously defined key for the `add_argument`
        """
        return self.__arguments[arg].pop(key, *args, **kwargs)

    def create_arguments(self):
        """Create and add the arguments"""
        ret = []
        for arg, d in self.__arguments.items():
            try:
                is_positional = d.pop('positional', False)
                short = d.pop('short')
                long_name = d.pop('long', None)
                if short == long_name:
                    long_name = None
                args = [short, long_name] if long_name else [short]
                if not is_positional:
                    for i, arg in enumerate(args):
                        args[i] = '-' * (i + 1) + arg
                else:
                    d.pop('dest', None)
                group = d.pop('group', self)
                ret.append(group.add_argument(*args, **d))
            except Exception:
                print('Error while creating argument %s' % arg)
                raise
        return ret

    @docstrings.get_sectionsf('FuncArgParser.parse_to_func')
    @docstrings.dedent
    def parse_to_func(self, args=None):
        """
        Parse the given arguments to the main function

        Parameters
        ----------
        args: list
            The list of arguments given to the
            :meth:`ArgumentParser.parse_args` function. If None, the sys.argv
            is used."""
        if args is not None:
            (self.__main or self.__funcs[0])(**vars(self.parse_args(args)))
        else:
            (self.__main or self.__funcs[0])(**vars(self.parse_args()))

    @docstrings.dedent
    def parse_known_to_func(self, args=None):
        """
        Parse the known arguments from the given to the main function

        Parameters
        ----------
        %(FuncArgParser.parse_to_func.parameters)s"""
        if args is not None:
            (self.__main or self.__funcs[0])(
                **vars(self.parse_known_args(args)[0]))
        else:
            (self.__main or self.__funcs[0])(
                **vars(self.parse_known_args()[0]))

    def set_main(self, func):
        """Set the function that is called by the :meth:`parse_to_func` and
        :meth:`parse_known_to_func` function"""
        self.__main = func
Exemple #23
0
class QuantileEvaluation(Evaluator):
    """Evaluator to evaluate specific quantiles"""

    name = 'quants'

    summary = 'Compare the quantiles of simulation and observation'

    names = OrderedDict([('prcp', {
        'long_name': 'Precipitation',
        'units': 'mm'
    }), ('tmin', {
        'long_name': 'Min. Temperature',
        'units': 'degC'
    }), ('tmax', {
        'long_name': 'Max. Temperature',
        'units': 'degC'
    }), ('mean_cloud', {
        'long_name': 'Cloud fraction',
        'units': '-'
    }), ('wind', {
        'long_name': 'Wind Speed',
        'units': 'm/s'
    })])

    @property
    def all_variables(self):
        return [[v + '_ref', v + '_sim'] for v in self.names]

    setup_requires = ['prepare', 'output']

    has_run = True

    _datafile = 'quantile_evaluation.csv'

    dbname = 'quantile_evaluation'

    #: default formatoptions for the
    #: :class:`psyplot.plotter.linreg.DensityRegPlotter` plotter
    fmt = kwargs = dict(
        legend={'loc': 'upper left'},
        cmap='w_Reds',
        title='%(pctl)sth percentile',
        xlabel='%(type)s {desc}',
        ylabel='%(type)s {desc}',
        xrange=(['minmax', 1], ['minmax', 99]),
        yrange=(['minmax', 1], ['minmax', 99]),
        legendlabels=['$R^2$ = %(rsquared)s'],
        bounds=['minmax', 11, 0, 99],
        cbar='',
        bins=10,
        ideal=[0, 1],
        id_color='r',
        sym_lims='max',
    )

    @property
    def default_config(self):
        return default_quantile_config()._replace(
            **super(QuantileEvaluation, self).default_config._asdict())

    @property
    def ds(self):
        """The dataset of the quantiles"""
        import xarray as xr
        data = self.data.reset_index()
        ds = xr.Dataset.from_dataframe(
            data.set_index('pctl', append=True).swaplevel())
        full = xr.Dataset.from_dataframe(data).drop(
            list(chain(self.data.index.names, ['pctl'])))
        idx_name = full[next(v for v in self.names) + '_sim'].dims[-1]
        full.rename({idx_name: 'full_index'}, inplace=True)
        for vref, vsim in self.all_variables:
            full.rename({
                vref: 'all_' + vref,
                vsim: 'all_' + vsim
            },
                        inplace=True)
        ds.merge(full, inplace=True)
        for orig, attrs, (vref, vsim) in zip(self.names, self.names.values(),
                                             self.all_variables):
            for prefix in ['', 'all_']:
                ds[prefix + vsim].attrs.update(attrs)
                ds[prefix + vref].attrs.update(attrs)
                ds[prefix + vsim].attrs['standard_name'] = orig
                ds[prefix + vref].attrs['standard_name'] = orig
                ds[prefix + vref].attrs['type'] = 'observed'
                ds[prefix + vsim].attrs['type'] = 'simulated'
            ds['all_' + vsim].attrs['pctl'] = 'All'
            ds['all_' + vsim].attrs['pctl'] = 'All'
        ds.pctl.attrs['long_name'] = 'Percentile'
        return ds

    def __init__(self, *args, **kwargs):
        super(QuantileEvaluation, self).__init__(*args, **kwargs)
        names = self.task_config.names
        if names is not None:
            self.names = OrderedDict(t for t in self.names.items()
                                     if t[0] in names)

    def setup_from_file(self, *args, **kwargs):
        kwargs['index_col'] = ['id', 'year']
        return super(QuantileEvaluation, self).setup_from_file(*args, **kwargs)

    def setup_from_db(self, *args, **kwargs):
        kwargs['index_col'] = ['id', 'year']
        return super(QuantileEvaluation, self).setup_from_db(*args, **kwargs)

    def setup_from_scratch(self):
        df_ref = self.prepare.reference_data
        dfi = self.prepare.input_data.reset_index(['lon', 'lat'])
        # create simulation dataframe
        df_sim = self.output.data
        if len(df_ref) == 0 or len(df_sim) == 0:
            self.logger.debug(
                'Skipping %s because reference data contains no information!',
                self.name)
            return
        names = self.names
        # load observed precision
        if self.task_config.no_rounding:
            for name in names:
                df_sim[name].values[:] = self.round_to_ref_prec(
                    df_ref[name].values, df_sim[name].values)
        # merge reference and simulation into one single dataframe
        df = df_ref.merge(df_sim,
                          left_index=True,
                          right_index=True,
                          suffixes=['_ref', '_sim'])
        if {'mean_cloud', 'wind'}.intersection(names):
            df.reset_index('day', inplace=True)
            df = df.merge(dfi[['mean_cloud', 'wind']],
                          left_index=True,
                          right_index=True)
            # mask out non-complete months for cloud validation and months with
            # 0 or 1 cloud fraction
            if 'mean_cloud' in names:
                df.ix[df['mean_cloud_ref'].isnull().values |
                      (df['mean_cloud'] == 0.0) | (df['mean_cloud'] == 1.0),
                      ['mean_cloud_sim', 'mean_cloud_ref']] = np.nan
            # mask out non-complete wind for wind validation and months with
            # a mean wind speed of 0
            if 'wind' in names:
                df.ix[df['wind_ref'].isnull().values | (df['wind'] == 0.0),
                      ['wind_sim', 'wind_ref']] = np.nan
            df.drop(['mean_cloud', 'wind'], 1, inplace=True)
            df.set_index('day', append=True, inplace=True)

        # transform wind
        if self.task_config.transform_wind and 'wind' in names:
            df['wind_ref'] **= 0.5
            df['wind_sim'] **= 0.5
        # calculate the percentiles for each station and month
        g = df.sort_index().groupby(level=['id', 'year'])
        self.logger.debug('Done with basic setup')
        data = g.apply(self.calc)
        if len(data):
            data.index = data.index.droplevel(2)
        self.data = data

    @classmethod
    def _modify_parser(cls, parser):
        parser.setup_args(default_ks_config)
        parser.setup_args(default_quantile_config)
        parser, setup_grp, run_grp = super(QuantileEvaluation,
                                           cls)._modify_parser(parser)
        parser.update_arg('quantiles',
                          short='q',
                          group=run_grp,
                          type=utils.str_ranges,
                          metavar='f1[,f21[-f22[-f23]]]',
                          help=docstrings.dedents("""
                The quantiles to use for calculating the percentiles.
                %(str_ranges.s_help)s."""))
        parser.pop_key('quantiles', 'nargs', None)
        parser.update_arg('no_rounding', short='nr', group=run_grp)
        parser.update_arg('names',
                          short='n',
                          group=setup_grp,
                          nargs='+',
                          metavar='variable',
                          choices=list(cls.names))
        parser.update_arg('transform_wind', short='tw', group=setup_grp)
        return parser, setup_grp, run_grp

    def create_project(self, ds):
        import psyplot.project as psy
        import seaborn as sns
        sns.set_style('white')
        for name, (vref, vsim) in zip(self.names, self.all_variables):
            self.logger.debug('Creating plots of %s', vsim)
            kwargs = dict(precision=0.1) if vref.startswith('prcp') else {}
            psy.plot.densityreg(ds,
                                name='all_' + vsim,
                                coord='all_' + vref,
                                fmt=self.fmt,
                                title='All percentiles',
                                arr_names=['%s_all' % name],
                                **kwargs)
            arr_names = ['%s_%1.2f' % (name, p) for p in ds.pctl.values]
            psy.plot.densityreg(ds,
                                name=vsim,
                                coord=vref,
                                fmt=self.fmt,
                                arr_names=arr_names,
                                pctl=range(ds.pctl.size),
                                **kwargs)
        return psy.gcp(True)[:]

    def make_run_config(self, sp, info):
        for orig in self.names:
            info[orig] = d = OrderedDict()
            for plotter in sp(standard_name=orig).plotters:
                d[plotter.data.pctl if plotter.data.name.
                  startswith('all') else int(plotter.data.pctl.values
                                             )] = pctl_d = OrderedDict()
                for key in ['rsquared', 'slope', 'intercept']:
                    val = plotter.plot_data[1].attrs.get(key)
                    if val is not None:
                        pctl_d[key] = float(val)
        return info

    def calc(self, group):
        def calc_percentiles(vname):
            arr = group[vname].values
            arr = arr[~np.isnan(arr)]
            if vname.startswith('prcp'):
                arr = arr[arr > 0]
            if len(arr) == 0:
                return np.array([np.nan] * len(quantiles))
            else:
                return np.percentile(arr, quantiles)

        quantiles = self.task_config.quantiles
        df = pd.DataFrame.from_dict(
            dict(
                zip(chain(*self.all_variables),
                    map(calc_percentiles, chain(*self.all_variables)))))
        df['pctl'] = quantiles
        df.set_index('pctl')
        return df

    @staticmethod
    def round_to_ref_prec(ref, sim, func=np.ceil):
        """Round one array to the precision of another

        Parameters
        ----------
        ref: np.ndarray
            The reference array to get the precision from
        sim: np.ndarray
            The simulated array to round
        func: function
            The rounding function to use

        Returns
        -------
        np.ndarray
            Rounded `sim`"""
        ref_sorted = np.unique(ref)
        if len(ref_sorted) < 2:
            return sim
        precision = (ref_sorted[1:] - ref_sorted[:-1]).min()
        return func((sim / precision) * precision)