From 9d2d476a32d2cf017c7863b4b238a469abc83519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 3 Mar 2022 16:19:24 -0500 Subject: [PATCH 01/43] Move recession module within hydrocalc module --- gwhat/{ => hydrocalc}/recession/__init__.py | 0 gwhat/{ => hydrocalc}/recession/recession_calc.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename gwhat/{ => hydrocalc}/recession/__init__.py (100%) rename gwhat/{ => hydrocalc}/recession/recession_calc.py (100%) diff --git a/gwhat/recession/__init__.py b/gwhat/hydrocalc/recession/__init__.py similarity index 100% rename from gwhat/recession/__init__.py rename to gwhat/hydrocalc/recession/__init__.py diff --git a/gwhat/recession/recession_calc.py b/gwhat/hydrocalc/recession/recession_calc.py similarity index 100% rename from gwhat/recession/recession_calc.py rename to gwhat/hydrocalc/recession/recession_calc.py From cc266c5619d39f73386bf9435fe41f8a5ce284bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 3 Mar 2022 16:20:14 -0500 Subject: [PATCH 02/43] Add a new MasterRecessionCalcTool to the recession module --- gwhat/hydrocalc/recession/mainwidget.py | 42 +++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 gwhat/hydrocalc/recession/mainwidget.py diff --git a/gwhat/hydrocalc/recession/mainwidget.py b/gwhat/hydrocalc/recession/mainwidget.py new file mode 100644 index 000000000..8dd5fa619 --- /dev/null +++ b/gwhat/hydrocalc/recession/mainwidget.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright © GWHAT Project Contributors +# https://github.com/jnsebgosselin/gwhat +# +# This file is part of GWHAT (Ground-Water Hydrograph Analysis Toolbox). +# Licensed under the terms of the GNU General Public License. +# ----------------------------------------------------------------------------- + +from __future__ import annotations + +# ---- Standard library imports + +# ---- Third party imports +import pandas as pd +from PyQt5.QtWidgets import ( + QWidget, QComboBox, QTextEdit, QSizePolicy) + +# ---- Local imports +from gwhat.hydrocalc.axeswidgets import WLCalcVSpanSelector +from gwhat.hydrocalc.api import WLCalcTool, wlcalcmethod +from gwhat.utils.qthelpers import create_toolbutton +from gwhat.utils.icons import QToolButtonNormal, get_iconsize +from gwhat.widgets.buttons import OnOffToolButton + + +class MasterRecessionCalcTool(WLCalcTool): + __toolname__ = 'mrc' + __tooltitle__ = 'MRC' + __tooltip__ = ("A tool to evaluate the barometric " + "response function of wells.") + + # Whether it is the first time showEvent is called. + _first_show_event = True + + # The WLCalc instance to which this tool is registered. + wlcalc = None + + _mrc_period_xdata = [] + _mrc_period_axvspans = [] + _mrc_period_memory = [[], ] + From 8540645e0b42ebb2003665bf62cbb4455eac03e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 3 Mar 2022 16:23:12 -0500 Subject: [PATCH 03/43] Move _setup_mrc_tool to MasterRecessionCalcTool --- gwhat/HydroCalc2.py | 89 ------------------------- gwhat/hydrocalc/recession/mainwidget.py | 78 +++++++++++++++++++++- 2 files changed, 76 insertions(+), 91 deletions(-) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index c3e5b10c9..9886f90bd 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -363,95 +363,6 @@ def _setup_toolbar(self): toolbar.addWidget(btn) return toolbar - def _setup_mrc_tool(self): - """Setup the tool to evaluate the MRC.""" - - # Setup the mrc period selector. - self.mrc_selector = WLCalcVSpanSelector( - self.fig.axes[0], self, onselected=self.add_mrcperiod) - self.install_axeswidget(self.mrc_selector) - self._mrc_period_xdata = [] - self._mrc_period_axvspans = [] - self._mrc_period_memory = [[], ] - - # ---- MRC parameters - self.MRC_type = QComboBox() - self.MRC_type.addItems(['Linear', 'Exponential']) - self.MRC_type.setCurrentIndex(1) - - self.MRC_results = QTextEdit() - self.MRC_results.setReadOnly(True) - self.MRC_results.setMinimumHeight(25) - self.MRC_results.setMinimumWidth(100) - - self.MRC_results.setSizePolicy( - QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred)) - - # Setup the MRC toolbar - self.btn_undo = create_toolbutton( - parent=self, - icon='undo', - iconsize=get_iconsize('normal'), - tip='Undo', - triggered=self.undo_mrc_period) - self.btn_undo.setEnabled(False) - - self.btn_clearPeak = create_toolbutton( - parent=self, - icon='clear_changes', - iconsize=get_iconsize('normal'), - tip='Clear all recession periods.', - triggered=self.clear_all_mrcperiods) - - self.btn_addpeak = OnOffToolButton('pencil_add', size='normal') - self.btn_addpeak.sig_value_changed.connect(self.btn_addpeak_isclicked) - self.btn_addpeak.setToolTip( - "Left-click on the graph to add new recession periods.") - self.register_navig_and_select_tool(self.btn_addpeak) - - self.btn_delpeak = OnOffToolButton('pencil_del', size='normal') - self.btn_delpeak.clicked.connect(self.btn_delpeak_isclicked) - self.btn_delpeak.setToolTip( - "Left-click on a recession period to remove it.") - self.register_navig_and_select_tool(self.btn_delpeak) - - self.btn_save_mrc = create_toolbutton( - parent=self, - icon='save', - iconsize=get_iconsize('normal'), - tip='Save calculated MRC to file.', - triggered=lambda: self.save_mrc_tofile()) - - self.btn_MRCalc = QPushButton('Compute MRC') - self.btn_MRCalc.clicked.connect(self.btn_MRCalc_isClicked) - self.btn_MRCalc.setToolTip( - 'Calculate the Master Recession Curve (MRC).') - - mrc_tb = ToolBarWidget() - for btn in [self.btn_undo, self.btn_clearPeak, self.btn_addpeak, - self.btn_delpeak, self.btn_save_mrc]: - mrc_tb.addWidget(btn) - - # Setup the MRC Layout. - self.mrc_eval_widget = QWidget() - mrc_lay = QGridLayout(self.mrc_eval_widget) - - row = 0 - mrc_lay.addWidget(QLabel('MRC Type :'), row, 0) - mrc_lay.addWidget(self.MRC_type, row, 1) - row += 1 - mrc_lay.addWidget(self.MRC_results, row, 0, 1, 3) - row += 1 - mrc_lay.addWidget(mrc_tb, row, 0, 1, 3) - row += 1 - mrc_lay.setRowMinimumHeight(row, 5) - mrc_lay.setRowStretch(row, 100) - row += 1 - mrc_lay.addWidget(self.btn_MRCalc, row, 0, 1, 3) - mrc_lay.setColumnStretch(2, 500) - - return self.mrc_eval_widget - def __initUI__(self): # Setup the left widget. left_widget = QMainWindow() diff --git a/gwhat/hydrocalc/recession/mainwidget.py b/gwhat/hydrocalc/recession/mainwidget.py index 8dd5fa619..85f863ed0 100644 --- a/gwhat/hydrocalc/recession/mainwidget.py +++ b/gwhat/hydrocalc/recession/mainwidget.py @@ -14,14 +14,15 @@ # ---- Third party imports import pandas as pd from PyQt5.QtWidgets import ( - QWidget, QComboBox, QTextEdit, QSizePolicy) + QWidget, QComboBox, QTextEdit, QSizePolicy, QPushButton, QGridLayout, + QLabel) # ---- Local imports from gwhat.hydrocalc.axeswidgets import WLCalcVSpanSelector from gwhat.hydrocalc.api import WLCalcTool, wlcalcmethod from gwhat.utils.qthelpers import create_toolbutton from gwhat.utils.icons import QToolButtonNormal, get_iconsize -from gwhat.widgets.buttons import OnOffToolButton +from gwhat.widgets.buttons import OnOffToolButton, ToolBarWidget class MasterRecessionCalcTool(WLCalcTool): @@ -40,3 +41,76 @@ class MasterRecessionCalcTool(WLCalcTool): _mrc_period_axvspans = [] _mrc_period_memory = [[], ] + def setup(self): + # Setup MRC parameter widgets. + self.MRC_type = QComboBox() + self.MRC_type.addItems(['Linear', 'Exponential']) + self.MRC_type.setCurrentIndex(1) + + self.MRC_results = QTextEdit() + self.MRC_results.setReadOnly(True) + self.MRC_results.setMinimumHeight(25) + self.MRC_results.setMinimumWidth(100) + self.MRC_results.setSizePolicy( + QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred)) + + # Setup the toolbar. + self.btn_undo = create_toolbutton( + parent=self, + icon='undo', + iconsize=get_iconsize('normal'), + tip='Undo', + triggered=self.undo_mrc_period) + self.btn_undo.setEnabled(False) + + self.btn_clearPeak = create_toolbutton( + parent=self, + icon='clear_changes', + iconsize=get_iconsize('normal'), + tip='Clear all recession periods.', + triggered=self.clear_all_mrcperiods) + + self.btn_addpeak = OnOffToolButton('pencil_add', size='normal') + self.btn_addpeak.sig_value_changed.connect(self.btn_addpeak_isclicked) + self.btn_addpeak.setToolTip( + "Left-click on the graph to add new recession periods.") + + self.btn_delpeak = OnOffToolButton('pencil_del', size='normal') + self.btn_delpeak.clicked.connect(self.btn_delpeak_isclicked) + self.btn_delpeak.setToolTip( + "Left-click on a recession period to remove it.") + + self.btn_save_mrc = create_toolbutton( + parent=self, + icon='save', + iconsize=get_iconsize('normal'), + tip='Save calculated MRC to file.', + triggered=lambda: self.save_mrc_tofile()) + + self.btn_MRCalc = QPushButton('Compute MRC') + self.btn_MRCalc.clicked.connect(self.btn_MRCalc_isClicked) + self.btn_MRCalc.setToolTip( + 'Calculate the Master Recession Curve (MRC).') + + mrc_tb = ToolBarWidget() + for btn in [self.btn_undo, self.btn_clearPeak, self.btn_addpeak, + self.btn_delpeak, self.btn_save_mrc]: + mrc_tb.addWidget(btn) + + # Setup the MRC Layout. + layout = QGridLayout(self) + + row = 0 + layout.addWidget(QLabel('MRC Type :'), row, 0) + layout.addWidget(self.MRC_type, row, 1) + row += 1 + layout.addWidget(self.MRC_results, row, 0, 1, 3) + row += 1 + layout.addWidget(mrc_tb, row, 0, 1, 3) + row += 1 + layout.setRowMinimumHeight(row, 5) + layout.setRowStretch(row, 100) + row += 1 + layout.addWidget(self.btn_MRCalc, row, 0, 1, 3) + layout.setColumnStretch(2, 500) + From bea840bd73814edf624a920514fba407324f6905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 3 Mar 2022 16:26:47 -0500 Subject: [PATCH 04/43] Move add_mrcperiod to MasterRecessionCalcTool --- gwhat/HydroCalc2.py | 29 ---------------- gwhat/hydrocalc/recession/mainwidget.py | 44 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index 9886f90bd..1cebab13a 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -482,35 +482,6 @@ def copy_to_clipboard(self): buf.close() # ---- MRC handlers - def add_mrcperiod(self, xdata): - """ - Add a a new mrc period using the provided xdata. - """ - try: - xmin = min(xdata) - xmax = max(xdata) - except TypeError: - return - - for i in reversed(range(len(self._mrc_period_xdata))): - period_xdata = self._mrc_period_xdata[i] - if xmin >= period_xdata[0] and xmax <= period_xdata[1]: - # This means this mrc period is fully enclosed within - # another period previously selected by the user, - # so we discard it completely. - return - - if period_xdata[0] >= xmin and period_xdata[0] <= xmax: - xmax = max(period_xdata[1], xmax) - del self._mrc_period_xdata[i] - elif period_xdata[1] >= xmin and period_xdata[1] <= xmax: - xmin = min(period_xdata[0], xmin) - del self._mrc_period_xdata[i] - - self._mrc_period_xdata.append((xmin, xmax)) - self._mrc_period_memory.append(self._mrc_period_xdata.copy()) - self.draw_mrc() - def remove_mrcperiod(self, xdata): """ Remove the mrc period at xdata if any. diff --git a/gwhat/hydrocalc/recession/mainwidget.py b/gwhat/hydrocalc/recession/mainwidget.py index 85f863ed0..e0b56a936 100644 --- a/gwhat/hydrocalc/recession/mainwidget.py +++ b/gwhat/hydrocalc/recession/mainwidget.py @@ -114,3 +114,47 @@ def setup(self): layout.addWidget(self.btn_MRCalc, row, 0, 1, 3) layout.setColumnStretch(2, 500) + # ---- WLCalc integration + @wlcalcmethod + def _on_period_selected(self, xdata): + """ + Handle when a new period is selected for the MRC calculations. + + Parameters + ---------- + xdata : 2-tuple + A 2-tuple of floats containing the time, in numerical Excel format, + of the new selected recession period. + """ + self.add_mrcperiod(xdata) + self.wlcalc._draw_mrc() + + # ---- MRC Tool Interface + def add_mrcperiod(self, xdata): + """ + Add a a new mrc period using the provided xdata. + """ + try: + xmin = min(xdata) + xmax = max(xdata) + except TypeError: + return + + for i in reversed(range(len(self._mrc_period_xdata))): + period_xdata = self._mrc_period_xdata[i] + if xmin >= period_xdata[0] and xmax <= period_xdata[1]: + # This means this mrc period is fully enclosed within + # another period previously selected by the user, + # so we discard it completely. + return + + if period_xdata[0] >= xmin and period_xdata[0] <= xmax: + xmax = max(period_xdata[1], xmax) + del self._mrc_period_xdata[i] + elif period_xdata[1] >= xmin and period_xdata[1] <= xmax: + xmin = min(period_xdata[0], xmin) + del self._mrc_period_xdata[i] + + self._mrc_period_xdata.append((xmin, xmax)) + self._mrc_period_memory.append(self._mrc_period_xdata.copy()) + From a074a1435c3cdd5984856893ea138eaf3fe995dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 3 Mar 2022 16:52:44 -0500 Subject: [PATCH 05/43] Move method to draw the mrc to MasterRecessionCalcTool --- gwhat/HydroCalc2.py | 45 ------------------- gwhat/hydrocalc/recession/mainwidget.py | 59 +++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 45 deletions(-) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index 1cebab13a..ca397de9e 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -193,10 +193,6 @@ def _setup_mpl_canvas(self): [], [], clip_on=True, ls='none', zorder=10, marker='+', ms=8, mec='red', mew=2, mfc='red') - # Recession. - self._mrc_plt, = ax0.plot([], [], color='red', clip_on=True, - zorder=15, marker='None', linestyle='--') - # Rain. self.h_rain, = ax1.plot([], []) @@ -614,47 +610,6 @@ def clear_all_mrcperiods(self): self._mrc_period_memory.append([]) self.draw_mrc() - def draw_mrc(self): - """ - Draw the periods during which water levels recedes and draw the - water levels that were predicted with the MRC. - """ - self._draw_mrc_wl() - self._draw_mrc_periods() - self.draw() - - def _draw_mrc_wl(self): - """Draw the water levels that were predicted with the MRC.""" - if (self.wldset is not None and self.btn_show_mrc.value() and - self.wldset.mrc_exists()): - self._mrc_plt.set_visible(True) - mrc_data = self.wldset.get_mrc() - self._mrc_plt.set_data( - mrc_data['time'] + self.dt4xls2mpl * self.dformat, - mrc_data['recess']) - else: - self._mrc_plt.set_visible(False) - - def _draw_mrc_periods(self): - """Draw the periods that will be used to compute the MRC.""" - self.btn_undo.setEnabled(len(self._mrc_period_memory) > 1) - for axvspan in self._mrc_period_axvspans: - axvspan.set_visible(False) - if self.wldset is not None and self.btn_show_mrc.value(): - for i, xdata in enumerate(self._mrc_period_xdata): - xmin = xdata[0] + (self.dt4xls2mpl * self.dformat) - xmax = xdata[1] + (self.dt4xls2mpl * self.dformat) - try: - axvspan = self._mrc_period_axvspans[i] - axvspan.set_visible(True) - axvspan.xy = [[xmin, 1], [xmin, 0], - [xmax, 0], [xmax, 1]] - except IndexError: - axvspan = self.fig.axes[0].axvspan( - xmin, xmax, visible=True, color='red', linewidth=1, - ls='-', alpha=0.1) - self._mrc_period_axvspans.append(axvspan) - # ---- Peaks handlers def btn_addpeak_isclicked(self): """Handle when the button add_peak is clicked.""" diff --git a/gwhat/hydrocalc/recession/mainwidget.py b/gwhat/hydrocalc/recession/mainwidget.py index e0b56a936..b29d989c1 100644 --- a/gwhat/hydrocalc/recession/mainwidget.py +++ b/gwhat/hydrocalc/recession/mainwidget.py @@ -129,6 +129,65 @@ def _on_period_selected(self, xdata): self.add_mrcperiod(xdata) self.wlcalc._draw_mrc() + @wlcalcmethod + def _draw_mrc(self): + """ + Draw the periods during which water levels recedes and draw the + water levels that were predicted with the MRC. + """ + self._draw_mrc_wl() + self._draw_mrc_periods() + self.wlcalc.draw() + + @wlcalcmethod + def _draw_mrc_wl(self): + """Draw the water levels that were predicted with the MRC.""" + if (self.wldset is not None and + self.wlcalc.btn_show_mrc.value() and + self.wldset.mrc_exists()): + self._mrc_plt.set_visible(True) + mrc_data = self.wldset.get_mrc() + + x = mrc_data['time'] + self.wlcalc.dt4xls2mpl * self.wlcalc.dformat + y = mrc_data['recess'] + self._mrc_plt.set_data(x, y) + else: + self._mrc_plt.set_visible(False) + + @wlcalcmethod + def _draw_mrc_periods(self): + """Draw the periods that will be used to compute the MRC.""" + self.btn_undo.setEnabled(len(self._mrc_period_memory) > 1) + for axvspan in self._mrc_period_axvspans: + axvspan.set_visible(False) + if self.wldset is not None and self.btn_show_mrc.value(): + for i, xdata in enumerate(self._mrc_period_xdata): + xmin = xdata[0] + ( + self.wlcalc.dt4xls2mpl * self.wlcalc.dformat) + xmax = xdata[1] + ( + self.wlcalc.dt4xls2mpl * self.wlcalc.dformat) + try: + axvspan = self._mrc_period_axvspans[i] + axvspan.set_visible(True) + axvspan.xy = [[xmin, 1], [xmin, 0], + [xmax, 0], [xmax, 1]] + except IndexError: + axvspan = self.wlcalc.fig.axes[0].axvspan( + xmin, xmax, visible=True, color='red', linewidth=1, + ls='-', alpha=0.1) + self._mrc_period_axvspans.append(axvspan) + + # ---- WLCalcTool API + def is_registered(self): + return self.wlcalc is not None + + def register_tool(self, wlcalc: QWidget): + self.wlcalc = wlcalc + + # Init matplotlib artists. + self._mrc_plt, = self.wlcalc.fig.axes[0].plot( + [], [], color='red', clip_on=True, + zorder=15, marker='None', linestyle='--') # ---- MRC Tool Interface def add_mrcperiod(self, xdata): """ From c3b9eb43789a9eb7cf25658d974bd9344405b10b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 3 Mar 2022 16:53:20 -0500 Subject: [PATCH 06/43] Move show_mrc_results to MasterRecessionCalcTool --- gwhat/HydroCalc2.py | 47 ------------------------- gwhat/hydrocalc/recession/mainwidget.py | 33 +++++++++++++++++ 2 files changed, 33 insertions(+), 47 deletions(-) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index ca397de9e..eb83eeefa 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -526,53 +526,6 @@ def btn_MRCalc_isClicked(self): QApplication.restoreOverrideCursor() - def show_mrc_results(self): - """Show MRC results if any.""" - if self.wldset is None: - self.MRC_results.setHtml('') - return - - mrc_data = self.wldset.get_mrc() - - coeffs = mrc_data['params'] - if pd.isnull(coeffs.A): - text = '' - else: - text = ( - "∂h/∂t = -A · h + B
" - "A = {:0.5f} day-1
" - "B = {:0.5f} m/day

" - "were ∂h/∂t is the recession rate in m/day, " - "h is the depth to water table in mbgs, " - "and A and B are the coefficients of the MRC.

" - "Goodness-of-fit statistics :
" - ).format(coeffs.A, coeffs.B) - - fit_stats = { - 'rmse': "RMSE = {} m
", - 'r_squared': "r² = {}
", - 'std_err': "S = {} m"} - for key, label in fit_stats.items(): - value = mrc_data[key] - if value is None: - text += label.format('N/A') - else: - text += label.format('{:0.5f}'.format(value)) - self.MRC_results.setHtml(text) - - def load_mrc_from_wldset(self): - """Load saved MRC results from the project hdf5 file.""" - if self.wldset is not None: - self._mrc_period_xdata = self.wldset.get_mrc()['peak_indx'] - self._mrc_period_memory[0] = self._mrc_period_xdata.copy() - self.btn_save_mrc.setEnabled(True) - else: - self._mrc_period_xdata = [] - self._mrc_period_memory = [[], ] - self.btn_save_mrc.setEnabled(False) - self.show_mrc_results() - self.draw_mrc() - def save_mrc_tofile(self, filename=None): """Save the master recession curve results to a file.""" if filename is None: diff --git a/gwhat/hydrocalc/recession/mainwidget.py b/gwhat/hydrocalc/recession/mainwidget.py index b29d989c1..21124cf88 100644 --- a/gwhat/hydrocalc/recession/mainwidget.py +++ b/gwhat/hydrocalc/recession/mainwidget.py @@ -217,3 +217,36 @@ def add_mrcperiod(self, xdata): self._mrc_period_xdata.append((xmin, xmax)) self._mrc_period_memory.append(self._mrc_period_xdata.copy()) + def show_mrc_results(self): + """Show MRC results if any.""" + if self.wldset is None: + self.MRC_results.setHtml('') + return + + mrc_data = self.wldset.get_mrc() + + coeffs = mrc_data['params'] + if pd.isnull(coeffs.A): + text = '' + else: + text = ( + "∂h/∂t = -A · h + B
" + "A = {:0.5f} day-1
" + "B = {:0.5f} m/day

" + "were ∂h/∂t is the recession rate in m/day, " + "h is the depth to water table in mbgs, " + "and A and B are the coefficients of the MRC.

" + "Goodness-of-fit statistics :
" + ).format(coeffs.A, coeffs.B) + + fit_stats = { + 'rmse': "RMSE = {} m
", + 'r_squared': "r² = {}
", + 'std_err': "S = {} m"} + for key, label in fit_stats.items(): + value = mrc_data[key] + if value is None: + text += label.format('N/A') + else: + text += label.format('{:0.5f}'.format(value)) + self.MRC_results.setHtml(text) From 50d3b246fd11bc59c4ee9d7ba3b65b532adbc7e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 3 Mar 2022 16:54:15 -0500 Subject: [PATCH 07/43] WLCalc: emir signal when wldset is set --- gwhat/HydroCalc2.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index eb83eeefa..442672db6 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -41,14 +41,14 @@ # ---- Local imports from gwhat.hydrocalc.axeswidgets import WLCalcVSpanSelector -from gwhat.recession.recession_calc import calculate_mrc +from gwhat.hydrocalc.recession.recession_calc import calculate_mrc from gwhat.brf_mod import BRFManager from gwhat.config.gui import FRAME_SYLE from gwhat.config.main import CONF from gwhat.gwrecharge.gwrecharge_gui import RechgEvalWidget +from gwhat.utils.qthelpers import create_toolbutton from gwhat.utils import icons from gwhat.utils.icons import QToolButtonNormal, get_iconsize -from gwhat.utils.qthelpers import create_toolbutton from gwhat.widgets.buttons import ToolBarWidget from gwhat.widgets.buttons import OnOffToolButton, OnOffPushButton from gwhat.widgets.layout import VSep @@ -63,6 +63,7 @@ class WLCalc(QWidget, SaveFileMixin): MRC and ultimately estimate groundwater recharge. """ sig_new_mrc = QSignal() + sig_wldset_changed = QSignal() def __init__(self, datamanager, parent=None): QWidget.__init__(self, parent) @@ -448,7 +449,8 @@ def set_wldset(self, wldset): self.setup_hydrograph() self.toolbar.update() - self.load_mrc_from_wldset() + + self.sig_wldset_changed.emit(wldset) def set_wxdset(self, wxdset): """Set the weather dataset.""" From d4b5e67091e993fe895b2d94b5c35be62ce3effb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 3 Mar 2022 16:54:39 -0500 Subject: [PATCH 08/43] MasterRecessionCalcTool: connect sig_wldset_changed to _on_wldset_changed method --- gwhat/hydrocalc/recession/mainwidget.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gwhat/hydrocalc/recession/mainwidget.py b/gwhat/hydrocalc/recession/mainwidget.py index 21124cf88..02aab88b6 100644 --- a/gwhat/hydrocalc/recession/mainwidget.py +++ b/gwhat/hydrocalc/recession/mainwidget.py @@ -115,6 +115,15 @@ def setup(self): layout.setColumnStretch(2, 500) # ---- WLCalc integration + @property + def wldset(self): + return None if self.wlcalc is None else self.wlcalc.wldset + + @wlcalcmethod + def _on_wldset_changed(self): + self.load_mrc_from_wldset() + self._draw_mrc() + @wlcalcmethod def _on_period_selected(self, xdata): """ @@ -184,6 +193,9 @@ def is_registered(self): def register_tool(self, wlcalc: QWidget): self.wlcalc = wlcalc + wlcalc.sig_wldset_changed.connect(self._on_wldset_changed) + + # Init matplotlib artists. self._mrc_plt, = self.wlcalc.fig.axes[0].plot( [], [], color='red', clip_on=True, From 6471d875ffcf9bbdd8ee9bf77b3afa9589ed2ea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 3 Mar 2022 16:54:56 -0500 Subject: [PATCH 09/43] Register navigation and select tools --- gwhat/hydrocalc/recession/mainwidget.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gwhat/hydrocalc/recession/mainwidget.py b/gwhat/hydrocalc/recession/mainwidget.py index 02aab88b6..10661b2e1 100644 --- a/gwhat/hydrocalc/recession/mainwidget.py +++ b/gwhat/hydrocalc/recession/mainwidget.py @@ -192,6 +192,8 @@ def is_registered(self): def register_tool(self, wlcalc: QWidget): self.wlcalc = wlcalc + wlcalc.register_navig_and_select_tool(self.btn_addpeak) + wlcalc.register_navig_and_select_tool(self.btn_delpeak) wlcalc.sig_wldset_changed.connect(self._on_wldset_changed) From a880c3879fcc979a7fee7c8c22e5c542ce08e4c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 3 Mar 2022 16:55:14 -0500 Subject: [PATCH 10/43] Setup the mrc selector --- gwhat/hydrocalc/recession/mainwidget.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gwhat/hydrocalc/recession/mainwidget.py b/gwhat/hydrocalc/recession/mainwidget.py index 10661b2e1..69979128f 100644 --- a/gwhat/hydrocalc/recession/mainwidget.py +++ b/gwhat/hydrocalc/recession/mainwidget.py @@ -197,6 +197,9 @@ def register_tool(self, wlcalc: QWidget): wlcalc.sig_wldset_changed.connect(self._on_wldset_changed) + self.mrc_selector = WLCalcVSpanSelector( + self.wlcalc.fig.axes[0], self, onselected=self._on_period_selected) + self.install_axeswidget(self.mrc_selector) # Init matplotlib artists. self._mrc_plt, = self.wlcalc.fig.axes[0].plot( From 8e2af0e75792e7992205902327f89ee6700eff81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 3 Mar 2022 16:55:30 -0500 Subject: [PATCH 11/43] Add load_mrc_from_wldset method --- gwhat/hydrocalc/recession/mainwidget.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gwhat/hydrocalc/recession/mainwidget.py b/gwhat/hydrocalc/recession/mainwidget.py index 69979128f..fa8a88f09 100644 --- a/gwhat/hydrocalc/recession/mainwidget.py +++ b/gwhat/hydrocalc/recession/mainwidget.py @@ -206,6 +206,18 @@ def register_tool(self, wlcalc: QWidget): [], [], color='red', clip_on=True, zorder=15, marker='None', linestyle='--') # ---- MRC Tool Interface + def load_mrc_from_wldset(self): + """Load saved MRC results from the project hdf5 file.""" + if self.wldset is not None: + self._mrc_period_xdata = self.wldset.get_mrc()['peak_indx'] + self._mrc_period_memory[0] = self._mrc_period_xdata.copy() + self.btn_save_mrc.setEnabled(True) + else: + self._mrc_period_xdata = [] + self._mrc_period_memory = [[], ] + self.btn_save_mrc.setEnabled(False) + self.show_mrc_results() + def add_mrcperiod(self, xdata): """ Add a a new mrc period using the provided xdata. From a59262f3aafac96bb9883a445433f420cf180dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 3 Mar 2022 16:58:13 -0500 Subject: [PATCH 12/43] Install MRC tool in WLCalc --- gwhat/HydroCalc2.py | 13 +++++-------- gwhat/hydrocalc/recession/mainwidget.py | 4 ++-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index 442672db6..e2c73cc4a 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -29,7 +29,6 @@ QMessageBox, QFileDialog) import matplotlib as mpl -import matplotlib.dates as mdates from matplotlib.figure import Figure as MplFigure from matplotlib.patches import Rectangle from matplotlib.transforms import ScaledTranslation @@ -40,7 +39,7 @@ from xlrd.xldate import xldate_from_date_tuple # ---- Local imports -from gwhat.hydrocalc.axeswidgets import WLCalcVSpanSelector +from gwhat.hydrocalc.recession.mainwidget import MasterRecessionCalcTool from gwhat.hydrocalc.recession.recession_calc import calculate_mrc from gwhat.brf_mod import BRFManager from gwhat.config.gui import FRAME_SYLE @@ -49,7 +48,6 @@ from gwhat.utils.qthelpers import create_toolbutton from gwhat.utils import icons from gwhat.utils.icons import QToolButtonNormal, get_iconsize -from gwhat.widgets.buttons import ToolBarWidget from gwhat.widgets.buttons import OnOffToolButton, OnOffPushButton from gwhat.widgets.layout import VSep from gwhat.widgets.fileio import SaveFileMixin @@ -112,6 +110,9 @@ def __init__(self, datamanager, parent=None): self.brf_eval_widget = BRFManager(parent=self) self.install_tool(self.brf_eval_widget) + self.mrc_eval_widget = MasterRecessionCalcTool(parent=self) + self.install_tool(self.mrc_eval_widget) + self.tools_tabwidget.setCurrentIndex( CONF.get('hydrocalc', 'current_tool_index')) @@ -374,11 +375,7 @@ def __initUI__(self): # Setup the tools tab area. self.tools_tabwidget = QTabWidget() - self.mrc_eval_widget = self._setup_mrc_tool() - self.tools_tabwidget.addTab(self.mrc_eval_widget, 'MRC') - self.tools_tabwidget.setTabToolTip( - 0, ("

A tool to evaluate the master recession curve" - " of the hydrograph.

")) + self.tools_tabwidget.addTab(self.rechg_eval_widget, 'Recharge') self.tools_tabwidget.setTabToolTip( 1, ("

A tool to evaluate groundwater recharge and its" diff --git a/gwhat/hydrocalc/recession/mainwidget.py b/gwhat/hydrocalc/recession/mainwidget.py index fa8a88f09..e15454b31 100644 --- a/gwhat/hydrocalc/recession/mainwidget.py +++ b/gwhat/hydrocalc/recession/mainwidget.py @@ -28,8 +28,8 @@ class MasterRecessionCalcTool(WLCalcTool): __toolname__ = 'mrc' __tooltitle__ = 'MRC' - __tooltip__ = ("A tool to evaluate the barometric " - "response function of wells.") + __tooltip__ = ("A tool to evaluate the master recession curve " + "of the hydrograph.") # Whether it is the first time showEvent is called. _first_show_event = True From c9490f62d377fc8d691b8f9523c7e1250f7ead40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 3 Mar 2022 17:00:10 -0500 Subject: [PATCH 13/43] Complete MRC tool installation --- gwhat/HydroCalc2.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index e2c73cc4a..0aad25919 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -106,13 +106,13 @@ def __init__(self, datamanager, parent=None): self.btn_pan.setValue(True) self.setup_ax_margins(None) - # Setup BRF calculation tool. - self.brf_eval_widget = BRFManager(parent=self) - self.install_tool(self.brf_eval_widget) - + # Setup wlcalc tool. self.mrc_eval_widget = MasterRecessionCalcTool(parent=self) self.install_tool(self.mrc_eval_widget) + self.brf_eval_widget = BRFManager(parent=self) + self.install_tool(self.brf_eval_widget) + self.tools_tabwidget.setCurrentIndex( CONF.get('hydrocalc', 'current_tool_index')) @@ -438,7 +438,6 @@ def set_wldset(self, wldset): """Set the namespace for the water level dataset.""" self._wldset = wldset self.rechg_eval_widget.set_wldset(wldset) - self.mrc_eval_widget.setEnabled(self.wldset is not None) # Setup BRF widget. for tool in self.tools.values(): From c0cc44360e423edd0359a717c02806709bdd54de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 3 Mar 2022 17:03:41 -0500 Subject: [PATCH 14/43] WLCalc: remove is_all_btn_raised This method is not used anywhere --- gwhat/HydroCalc2.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index 0aad25919..30e276152 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -983,15 +983,6 @@ def _draw_rect_selection(self, x2, y2): self.fig.axes[0].draw_artist(self._rect_selector) # ----- Mouse Event Handlers - def is_all_btn_raised(self): - """ - Return whether all of the tool buttons that can block the panning and - zooming of the graph are raised. - """ - return(self.btn_delpeak.autoRaise() and - self.btn_addpeak.autoRaise() and - not self.brf_eval_widget.is_brfperiod_selection_toggled()) - def on_fig_leave(self, event): """Handle when the mouse cursor leaves the graph.""" self.draw() From c5dde7d7a02535f67e1330b114d241c96b3efb75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 09:03:52 -0500 Subject: [PATCH 15/43] Create a new WLCalcVSpanHighlighter --- gwhat/hydrocalc/axeswidgets.py | 101 +++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/gwhat/hydrocalc/axeswidgets.py b/gwhat/hydrocalc/axeswidgets.py index bc910df6d..ec955c3de 100644 --- a/gwhat/hydrocalc/axeswidgets.py +++ b/gwhat/hydrocalc/axeswidgets.py @@ -21,6 +21,107 @@ from matplotlib.widgets import AxesWidget +class WLCalcVSpanHighlighter(AxesWidget, QObject): + sig_span_clicked = QSignal(float) + + def __init__(self, ax: Axes, wlcalc: QWidget, tracked_axvspans: list, + useblit: bool = True, onclicked: Callable = None, + axvspan_color: str = 'red', axvline_color: str = 'black', + ): + AxesWidget.__init__(self, ax) + QObject.__init__(self) + self.visible = True + self.useblit = useblit and self.canvas.supports_blit + self.wlcalc = wlcalc + self.tracked_axvspans = tracked_axvspans + + if onclicked is not None: + self.sig_span_clicked.connect(onclicked) + + # Axes span highlight. + self.axvspan_highlight = ax.axvspan( + 0, 1, visible=False, color='red', linewidth=1, + ls='-', alpha=0.3) + + def set_active(self, active): + """ + Set whether the selector is active. + """ + self.axvspan_highlight.xy = [ + [np.inf, 1], [np.inf, 0], [np.inf, 0], [np.inf, 1]] + self.axvspan_highlight.set_visible(active) + super().set_active(active) + self.wlcalc.draw() + + def clear(self): + """ + Clear the selector. + + This method must be called by the canvas BEFORE making a copy of + the canvas background. + """ + self.__axvspan_highlight_visible = self.axvspan_highlight.get_visible() + self.axvspan_highlight.set_visible(False) + + def restore(self): + """ + Restore the selector. + + This method must be called by the canvas AFTER a copy has been made + of the canvas background. + """ + self.axvspan_highlight.set_visible(self.__axvspan_highlight_visible) + self.ax.draw_artist(self.axvspan_highlight) + + def onmove(self, event): + """Handler to draw the selector when the mouse cursor moves.""" + if self.ignore(event): + return + if not self.canvas.widgetlock.available(self): + return + if not self.visible: + return + + if event.xdata is None: + self.axvspan_highlight.set_visible(False) + self._update() + return + + for axvspan in self.tracked_axvspans: + if not axvspan.get_visible(): + continue + + xy_data = axvspan.xy + x_data = [xy[0] for xy in xy_data] + xdata_min = min(x_data) + xdata_max = max(x_data) + if event.xdata >= xdata_min and event.xdata <= xdata_max: + self.axvspan_highlight.set_visible(True) + self.axvspan_highlight.xy = [[xdata_min, 1], + [xdata_min, 0], + [xdata_max, 0], + [xdata_max, 1]] + break + else: + self.axvspan_highlight.set_visible(False) + self._update() + + def onpress(self, event): + """Handler for the button_press_event event.""" + if event.button != 1 or not event.xdata: + return + self.sig_span_clicked.emit( + event.xdata - self.wlcalc.dt4xls2mpl * self.wlcalc.dformat) + self.onmove(event) + + def onrelease(self, event): + pass + + def _update(self): + self.ax.draw_artist(self.axvspan_highlight) + return False + + class WLCalcVSpanSelector(AxesWidget, QObject): sig_span_selected = QSignal(tuple) From e0ff853e4416afdb99db32cc95edcc8afc3d21a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 09:08:26 -0500 Subject: [PATCH 16/43] Move all MRC logic to MasterRecessionCalcTool --- gwhat/HydroCalc2.py | 186 +++++++------------------------------------- 1 file changed, 27 insertions(+), 159 deletions(-) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index 30e276152..cb5c53057 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -9,24 +9,19 @@ # ---- Standard library imports import io -from time import perf_counter -import csv -import os import os.path as osp import datetime from typing import Any, Callable # ---- Third party imports import numpy as np -import pandas as pd from qtpy.QtGui import QImage from PyQt5.QtCore import Qt from PyQt5.QtCore import pyqtSlot as QSlot from PyQt5.QtCore import pyqtSignal as QSignal from PyQt5.QtWidgets import ( - QGridLayout, QComboBox, QTextEdit, QSizePolicy, QPushButton, QLabel, - QTabWidget, QApplication, QWidget, QMainWindow, QToolBar, QFrame, - QMessageBox, QFileDialog) + QGridLayout, QTabWidget, QApplication, QWidget, QMainWindow, + QToolBar, QFrame, QMessageBox, QFileDialog) import matplotlib as mpl from matplotlib.figure import Figure as MplFigure @@ -40,7 +35,6 @@ # ---- Local imports from gwhat.hydrocalc.recession.mainwidget import MasterRecessionCalcTool -from gwhat.hydrocalc.recession.recession_calc import calculate_mrc from gwhat.brf_mod import BRFManager from gwhat.config.gui import FRAME_SYLE from gwhat.config.main import CONF @@ -60,7 +54,6 @@ class WLCalc(QWidget, SaveFileMixin): i.e. display the data as a continuous line or individual dot, perform a MRC and ultimately estimate groundwater recharge. """ - sig_new_mrc = QSignal() sig_wldset_changed = QSignal() def __init__(self, datamanager, parent=None): @@ -166,7 +159,6 @@ def _setup_mpl_canvas(self): ax1.tick_params(axis='y', direction='out') # ---- Setup axis labels - ax0.set_ylabel('Water level (mbgs)', fontsize=14, labelpad=25, va='top', color='black') ax0.set_xlabel('Time (days)', fontsize=14, labelpad=25, @@ -222,11 +214,6 @@ def _setup_mpl_canvas(self): 1, 0, '', ha='right', transform=ax0.transAxes + offset) self.xycoord.set_visible(False) - # Axes span highlight. - self.axvspan_highlight = self.fig.axes[0].axvspan( - 0, 1, visible=False, color='red', linewidth=1, - ls='-', alpha=0.3) - def _setup_toolbar(self): """Setup the main toolbar of the water level calc tool.""" @@ -239,8 +226,8 @@ def _setup_toolbar(self): shortcut='Ctrl+C') # ---- Navigate data. - self.toolbar = NavigationToolbar2QT(self.canvas, parent=self) - self.toolbar.hide() + self._navig_toolbar = NavigationToolbar2QT(self.canvas, parent=self) + self._navig_toolbar.hide() self.btn_home = QToolButtonNormal(icons.get_icon('home')) self.btn_home.setToolTip('Reset original view.') @@ -292,14 +279,6 @@ def _setup_toolbar(self): self.btn_show_weather.setValue( CONF.get('hydrocalc', 'show_weather', True), silent=True) - self.btn_show_mrc = OnOffToolButton('mrc_calc', size='normal') - self.btn_show_mrc.setToolTip( - "Show or hide water levels predicted with the MRC.") - self.btn_show_mrc.sig_value_changed.connect( - self.btn_show_mrc_isclicked) - self.btn_show_mrc.setValue( - CONF.get('hydrocalc', 'show_mrc', True), silent=True) - self.btn_show_meas_wl = OnOffToolButton( 'manual_measures', size='normal') self.btn_show_meas_wl.setToolTip( @@ -351,7 +330,7 @@ def _setup_toolbar(self): self.btn_zoom_to_rect, None, self.btn_wl_style, self.btn_dateFormat, None, self.btn_show_glue, self.btn_show_weather, - self.btn_show_mrc, self.btn_show_meas_wl, None, + self.btn_show_meas_wl, None, self.btn_rect_select, self.btn_clear_select, self.btn_del_select, self.btn_undo_changes, self.btn_clear_changes, self.btn_commit_changes]: @@ -365,12 +344,12 @@ def __initUI__(self): # Setup the left widget. left_widget = QMainWindow() - toolbar = self._setup_toolbar() - toolbar.setStyleSheet("QToolBar {border: 0px; spacing:1px;}") - toolbar.setFloatable(False) - toolbar.setMovable(False) - toolbar.setIconSize(get_iconsize('normal')) - left_widget.addToolBar(Qt.TopToolBarArea, toolbar) + self.toolbar = self._setup_toolbar() + self.toolbar.setStyleSheet("QToolBar {border: 0px; spacing:1px;}") + self.toolbar.setFloatable(False) + self.toolbar.setMovable(False) + self.toolbar.setIconSize(get_iconsize('normal')) + left_widget.addToolBar(Qt.TopToolBarArea, self.toolbar) left_widget.setCentralWidget(self.fig_frame_widget) # Setup the tools tab area. @@ -444,9 +423,9 @@ def set_wldset(self, wldset): tool.set_wldset(wldset) self.setup_hydrograph() - self.toolbar.update() + self._navig_toolbar.update() - self.sig_wldset_changed.emit(wldset) + self.sig_wldset_changed.emit() def set_wxdset(self, wxdset): """Set the weather dataset.""" @@ -457,8 +436,6 @@ def close(self): """Close this groundwater level calc window.""" CONF.set('hydrocalc', 'current_tool_index', self.tools_tabwidget.currentIndex()) - - CONF.set('hydrocalc', 'show_mrc', self.btn_show_mrc.value()) CONF.set('hydrocalc', 'show_weather', self.btn_show_weather.value()) CONF.set('hydrocalc', 'show_glue', self.btn_show_glue.value()) CONF.set('hydrocalc', 'show_meas_wl', self.btn_show_meas_wl.value()) @@ -476,54 +453,6 @@ def copy_to_clipboard(self): buf.close() # ---- MRC handlers - def remove_mrcperiod(self, xdata): - """ - Remove the mrc period at xdata if any. - """ - for i, period_xdata in enumerate(self._mrc_period_xdata): - period_xmin = period_xdata[0] + (self.dt4xls2mpl * self.dformat) - period_xmax = period_xdata[1] + (self.dt4xls2mpl * self.dformat) - if xdata >= period_xmin and xdata <= period_xmax: - del self._mrc_period_xdata[i] - self._mrc_period_memory.append(self._mrc_period_xdata.copy()) - self.draw_mrc() - break - - def btn_show_mrc_isclicked(self): - """Handle when the button to draw of hide the mrc is clicked.""" - if self.btn_show_mrc.value() is False: - self.btn_addpeak.setValue(False) - self.btn_delpeak.setValue(False) - self.draw_mrc() - - def btn_MRCalc_isClicked(self): - if self.wldset is None: - return - QApplication.setOverrideCursor(Qt.WaitCursor) - - coeffs, hp, std_err, r_squared, rmse = calculate_mrc( - self.time, self.water_lvl, self._mrc_period_xdata, - self.MRC_type.currentIndex()) - A = coeffs.A - B = coeffs.B - print('MRC Parameters: A={}, B={}'.format( - 'None' if pd.isnull(A) else '{:0.3f}'.format(coeffs.A), - 'None' if pd.isnull(B) else '{:0.3f}'.format(coeffs.B))) - - # Store and plot the results. - print('Saving MRC interpretation in dataset...') - self.wldset.set_mrc( - A, B, self._mrc_period_xdata, - self.time, hp, - std_err, r_squared, rmse) - - self.show_mrc_results() - self.btn_save_mrc.setEnabled(True) - self.draw_mrc() - self.sig_new_mrc.emit() - - QApplication.restoreOverrideCursor() - def save_mrc_tofile(self, filename=None): """Save the master recession curve results to a file.""" if filename is None: @@ -544,38 +473,6 @@ def save_mrc_tofile(self, filename=None): self.save_mrc_tofile(filename) QApplication.restoreOverrideCursor() - def undo_mrc_period(self): - """ - Undo the last operation performed by the user on the selection - of mrc periods. - """ - if len(self._mrc_period_memory) > 1: - self._mrc_period_xdata = self._mrc_period_memory[-2].copy() - del self._mrc_period_memory[-1] - self.draw_mrc() - - def clear_all_mrcperiods(self): - """Clear all mrc periods from the graph.""" - if len(self._mrc_period_xdata) > 0: - self._mrc_period_xdata = [] - self._mrc_period_memory.append([]) - self.draw_mrc() - - # ---- Peaks handlers - def btn_addpeak_isclicked(self): - """Handle when the button add_peak is clicked.""" - if self.btn_addpeak.value(): - self.toggle_navig_and_select_tools(self.btn_addpeak) - self.btn_show_mrc.setValue(True) - self.mrc_selector.set_active(self.btn_addpeak.value()) - - def btn_delpeak_isclicked(self): - """Handle when the button btn_delpeak is clicked.""" - if self.btn_delpeak.value(): - self.toggle_navig_and_select_tools(self.btn_delpeak) - self.btn_show_mrc.setValue(True) - self.draw() - # ---- Navigation and selection tools def register_navig_and_select_tool(self, tool): """ @@ -612,11 +509,11 @@ def zoom_is_active_changed(self, zoom_is_active): """Handle when the state of the button to zoom to rectangle changes.""" if self.zoom_is_active: self.toggle_navig_and_select_tools(self.btn_zoom_to_rect) - if self.toolbar._active is None: - self.toolbar.zoom() + if self._navig_toolbar._active is None: + self._navig_toolbar.zoom() else: - if self.toolbar._active == 'ZOOM': - self.toolbar.zoom() + if self._navig_toolbar._active == 'ZOOM': + self._navig_toolbar.zoom() @property def pan_is_active(self): @@ -628,11 +525,11 @@ def pan_is_active_changed(self, pan_is_active): """Handle when the state of the button to pan the graph changes.""" if self.pan_is_active: self.toggle_navig_and_select_tools(self.btn_pan) - if self.toolbar._active is None: - self.toolbar.pan() + if self._navig_toolbar._active is None: + self._navig_toolbar.pan() else: - if self.toolbar._active == 'PAN': - self.toolbar.pan() + if self._navig_toolbar._active == 'PAN': + self._navig_toolbar.pan() @property def rect_select_is_active(self): @@ -655,7 +552,7 @@ def clear_selected_wl(self, draw=True): def home(self): """Reset the orgininal view of the figure.""" - self.toolbar.home() + self._navig_toolbar.home() if self.dformat == 0: ax0 = self.fig.axes[0] xfmt = mpl.ticker.ScalarFormatter() @@ -706,10 +603,6 @@ def _update_edit_toolbar_state(self): # ---- Drawing methods def setup_hydrograph(self): """Setup the hydrograph after a new wldset has been set.""" - self._mrc_period_xdata = [] - self._mrc_period_memory = [[], ] - self.btn_undo.setEnabled(False) - self.clear_selected_wl() self._update_edit_toolbar_state() @@ -835,7 +728,6 @@ def setup_wl_style(self): def draw(self): """Draw the canvas and save a snapshot of the background figure.""" self.xycoord.set_visible(False) - self.axvspan_highlight.set_visible(False) for widget in self._axes_widgets: widget.clear() @@ -990,11 +882,11 @@ def on_fig_leave(self, event): def on_axes_enter(self, event): """Handle when the mouse cursor enters a new axe.""" if self.rect_select_is_active: - self.toolbar.set_cursor(2) + self._navig_toolbar.set_cursor(2) def on_axes_leave(self, event): """Handle when the mouse cursor leaves an axe.""" - self.toolbar.set_cursor(1) + self._navig_toolbar.set_cursor(1) def on_rect_select(self): """ @@ -1052,25 +944,6 @@ def onmove(self, event): if self.rect_select_is_active and self.__mouse_btn_is_pressed: self._draw_rect_selection(x, y) - # Draw mrc period highlight. - if self.btn_delpeak.value() and len(self._mrc_period_axvspans) > 0: - if event.xdata: - for xdata in self._mrc_period_xdata: - xdata_min = xdata[0] + (self.dt4xls2mpl * self.dformat) - xdata_max = xdata[1] + (self.dt4xls2mpl * self.dformat) - if event.xdata >= xdata_min and event.xdata <= xdata_max: - self.axvspan_highlight.set_visible(True) - self.axvspan_highlight.xy = [[xdata_min, 1], - [xdata_min, 0], - [xdata_max, 0], - [xdata_max, 1]] - break - else: - self.axvspan_highlight.set_visible(False) - else: - self.axvspan_highlight.set_visible(False) - ax0.draw_artist(self.axvspan_highlight) - # Update all axes widget. for widget in self._axes_widgets: if widget.get_active(): @@ -1088,9 +961,9 @@ def onrelease(self, event): # Disconnect the pan and zoom callback before drawing the canvas again. if self.pan_is_active: - self.toolbar.release_pan(event) + self._navig_toolbar.release_pan(event) if self.zoom_is_active: - self.toolbar.release_zoom(event) + self._navig_toolbar.release_zoom(event) if self.rect_select_is_active: self._rect_selection[1] = (event.xdata, event.ydata) self._rect_selector.set_visible(False) @@ -1108,12 +981,7 @@ def onpress(self, event): self.__mouse_btn_is_pressed = True if event.x is None or event.y is None or self.wldset is None: return - - # Remove mrc period. - if self.btn_delpeak.value() and len(self._mrc_period_xdata) > 0: - self.axvspan_highlight.set_visible(False) - self.remove_mrcperiod(event.xdata) - elif self.rect_select_is_active: + if self.rect_select_is_active: self._rect_selection[0] = (event.xdata, event.ydata) # Update all axes widget. From 6a0b1fda1f2c8f28d1b55e87b09059f6893cdbc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 09:09:46 -0500 Subject: [PATCH 17/43] Rename a file --- .../{mainwidget.py => recession_tool.py} | 163 ++++++++++++++++-- 1 file changed, 152 insertions(+), 11 deletions(-) rename gwhat/hydrocalc/recession/{mainwidget.py => recession_tool.py} (63%) diff --git a/gwhat/hydrocalc/recession/mainwidget.py b/gwhat/hydrocalc/recession/recession_tool.py similarity index 63% rename from gwhat/hydrocalc/recession/mainwidget.py rename to gwhat/hydrocalc/recession/recession_tool.py index e15454b31..d309905e0 100644 --- a/gwhat/hydrocalc/recession/mainwidget.py +++ b/gwhat/hydrocalc/recession/recession_tool.py @@ -13,12 +13,16 @@ # ---- Third party imports import pandas as pd +from PyQt5.QtCore import Qt +from PyQt5.QtCore import pyqtSignal as QSignal from PyQt5.QtWidgets import ( QWidget, QComboBox, QTextEdit, QSizePolicy, QPushButton, QGridLayout, - QLabel) + QLabel, QApplication) # ---- Local imports -from gwhat.hydrocalc.axeswidgets import WLCalcVSpanSelector +from gwhat.hydrocalc.recession.recession_calc import calculate_mrc +from gwhat.hydrocalc.axeswidgets import ( + WLCalcVSpanSelector, WLCalcVSpanHighlighter) from gwhat.hydrocalc.api import WLCalcTool, wlcalcmethod from gwhat.utils.qthelpers import create_toolbutton from gwhat.utils.icons import QToolButtonNormal, get_iconsize @@ -41,6 +45,12 @@ class MasterRecessionCalcTool(WLCalcTool): _mrc_period_axvspans = [] _mrc_period_memory = [[], ] + sig_new_mrc = QSignal() + + def __init__(self, parent=None): + super().__init__(parent) + self.setup() + def setup(self): # Setup MRC parameter widgets. self.MRC_type = QComboBox() @@ -71,12 +81,14 @@ def setup(self): triggered=self.clear_all_mrcperiods) self.btn_addpeak = OnOffToolButton('pencil_add', size='normal') - self.btn_addpeak.sig_value_changed.connect(self.btn_addpeak_isclicked) + self.btn_addpeak.sig_value_changed.connect( + lambda: self._btn_addpeak_isclicked()) self.btn_addpeak.setToolTip( "Left-click on the graph to add new recession periods.") self.btn_delpeak = OnOffToolButton('pencil_del', size='normal') - self.btn_delpeak.clicked.connect(self.btn_delpeak_isclicked) + self.btn_delpeak.clicked.connect( + lambda: self._btn_delpeak_isclicked()) self.btn_delpeak.setToolTip( "Left-click on a recession period to remove it.") @@ -88,7 +100,7 @@ def setup(self): triggered=lambda: self.save_mrc_tofile()) self.btn_MRCalc = QPushButton('Compute MRC') - self.btn_MRCalc.clicked.connect(self.btn_MRCalc_isClicked) + self.btn_MRCalc.clicked.connect(self._btn_MRCalc_isClicked) self.btn_MRCalc.setToolTip( 'Calculate the Master Recession Curve (MRC).') @@ -114,6 +126,15 @@ def setup(self): layout.addWidget(self.btn_MRCalc, row, 0, 1, 3) layout.setColumnStretch(2, 500) + # This button needs to be added to WCalc toolbar. + self.btn_show_mrc = OnOffToolButton('mrc_calc', size='normal') + self.btn_show_mrc.setToolTip( + "Show or hide water levels predicted with the MRC.") + self.btn_show_mrc.sig_value_changed.connect( + self.btn_show_mrc_isclicked) + self.btn_show_mrc.setValue( + self.get_option('show_mrc', True), silent=True) + # ---- WLCalc integration @property def wldset(self): @@ -121,6 +142,10 @@ def wldset(self): @wlcalcmethod def _on_wldset_changed(self): + self._mrc_period_xdata = [] + self._mrc_period_memory = [[], ] + self.btn_undo.setEnabled(False) + self.setEnabled(self.wldset is not None) self.load_mrc_from_wldset() self._draw_mrc() @@ -136,7 +161,6 @@ def _on_period_selected(self, xdata): of the new selected recession period. """ self.add_mrcperiod(xdata) - self.wlcalc._draw_mrc() @wlcalcmethod def _draw_mrc(self): @@ -152,7 +176,7 @@ def _draw_mrc(self): def _draw_mrc_wl(self): """Draw the water levels that were predicted with the MRC.""" if (self.wldset is not None and - self.wlcalc.btn_show_mrc.value() and + self.btn_show_mrc.value() and self.wldset.mrc_exists()): self._mrc_plt.set_visible(True) mrc_data = self.wldset.get_mrc() @@ -163,6 +187,22 @@ def _draw_mrc_wl(self): else: self._mrc_plt.set_visible(False) + @wlcalcmethod + def _btn_addpeak_isclicked(self): + """Handle when the button add_peak is clicked.""" + if self.btn_addpeak.value(): + self.wlcalc.toggle_navig_and_select_tools(self.btn_addpeak) + self.btn_show_mrc.setValue(True) + self.mrc_selector.set_active(self.btn_addpeak.value()) + + @wlcalcmethod + def _btn_delpeak_isclicked(self): + """Handle when the button btn_delpeak is clicked.""" + if self.btn_delpeak.value(): + self.wlcalc.toggle_navig_and_select_tools(self.btn_delpeak) + self.btn_show_mrc.setValue(True) + self.wlcalc.draw() + @wlcalcmethod def _draw_mrc_periods(self): """Draw the periods that will be used to compute the MRC.""" @@ -186,25 +226,95 @@ def _draw_mrc_periods(self): ls='-', alpha=0.1) self._mrc_period_axvspans.append(axvspan) + @wlcalcmethod + def _btn_MRCalc_isClicked(self): + if self.wldset is None: + return + QApplication.setOverrideCursor(Qt.WaitCursor) + + coeffs, hp, std_err, r_squared, rmse = calculate_mrc( + self.wlcalc.time, self.wlcalc.water_lvl, self._mrc_period_xdata, + self.MRC_type.currentIndex()) + A = coeffs.A + B = coeffs.B + print('MRC Parameters: A={}, B={}'.format( + 'None' if pd.isnull(A) else '{:0.3f}'.format(coeffs.A), + 'None' if pd.isnull(B) else '{:0.3f}'.format(coeffs.B))) + + # Store and plot the results. + print('Saving MRC interpretation in dataset...') + self.wldset.set_mrc( + A, B, self._mrc_period_xdata, + self.wlcalc.time, hp, + std_err, r_squared, rmse) + + self.show_mrc_results() + self.btn_save_mrc.setEnabled(True) + self._draw_mrc() + self.sig_new_mrc.emit() + + QApplication.restoreOverrideCursor() + + def btn_show_mrc_isclicked(self): + """Handle when the button to draw of hide the mrc is clicked.""" + if self.btn_show_mrc.value() is False: + self.btn_addpeak.setValue(False) + self.btn_delpeak.setValue(False) + self._draw_mrc() + # ---- WLCalcTool API def is_registered(self): return self.wlcalc is not None def register_tool(self, wlcalc: QWidget): + # Setup wlcalc. self.wlcalc = wlcalc + index = wlcalc.tools_tabwidget.addTab(self, self.title()) + wlcalc.tools_tabwidget.setTabToolTip(index, self.tooltip()) + # wlcalc.tools_tabwidget.currentChanged.connect( + # lambda: self.toggle_brfperiod_selection(False)) + wlcalc.sig_wldset_changed.connect(self._on_wldset_changed) + wlcalc.sig_new_mrc = self.sig_new_mrc + + # Add "Show MRC" button to WLCalc toolbar. + before_widget = wlcalc.btn_show_meas_wl + for action in wlcalc.toolbar.actions(): + if wlcalc.toolbar.widgetForAction(action) == before_widget: + wlcalc.toolbar.insertWidget( + action, self.btn_show_mrc) + + # Setup the axes widget to add and remove recession periods. wlcalc.register_navig_and_select_tool(self.btn_addpeak) wlcalc.register_navig_and_select_tool(self.btn_delpeak) - wlcalc.sig_wldset_changed.connect(self._on_wldset_changed) - self.mrc_selector = WLCalcVSpanSelector( - self.wlcalc.fig.axes[0], self, onselected=self._on_period_selected) - self.install_axeswidget(self.mrc_selector) + self.wlcalc.fig.axes[0], wlcalc, + onselected=self._on_period_selected) + wlcalc.install_axeswidget(self.mrc_selector) + + self.mrc_remover = WLCalcVSpanHighlighter( + self.wlcalc.fig.axes[0], wlcalc, self._mrc_period_axvspans, + onclicked=self.remove_mrcperiod) + wlcalc.install_axeswidget(self.mrc_remover) # Init matplotlib artists. self._mrc_plt, = self.wlcalc.fig.axes[0].plot( [], [], color='red', clip_on=True, zorder=15, marker='None', linestyle='--') + + self.load_mrc_from_wldset() + self._draw_mrc() + + def close_tool(self): + self.set_option('show_mrc', self.btn_show_mrc.value()) + super().close() + + def set_wldset(self, wldset): + pass + + def set_wxdset(self, wxdset): + pass + # ---- MRC Tool Interface def load_mrc_from_wldset(self): """Load saved MRC results from the project hdf5 file.""" @@ -245,6 +355,37 @@ def add_mrcperiod(self, xdata): self._mrc_period_xdata.append((xmin, xmax)) self._mrc_period_memory.append(self._mrc_period_xdata.copy()) + self._draw_mrc() + + def undo_mrc_period(self): + """ + Undo the last operation performed by the user on the selection + of mrc periods. + """ + if len(self._mrc_period_memory) > 1: + self._mrc_period_xdata = self._mrc_period_memory[-2].copy() + del self._mrc_period_memory[-1] + self._draw_mrc() + + def clear_all_mrcperiods(self): + """Clear all mrc periods from the graph.""" + if len(self._mrc_period_xdata) > 0: + self._mrc_period_xdata = [] + self._mrc_period_memory.append([]) + self._draw_mrc() + + def remove_mrcperiod(self, xdata): + """ + Remove the mrc period at xdata if any. + """ + for i, period_xdata in enumerate(self._mrc_period_xdata): + period_xmin = period_xdata[0] + period_xmax = period_xdata[1] + if xdata >= period_xmin and xdata <= period_xmax: + del self._mrc_period_xdata[i] + self._mrc_period_memory.append(self._mrc_period_xdata.copy()) + self._draw_mrc() + break def show_mrc_results(self): """Show MRC results if any.""" From 79bbaa7a907ec4b65a756eb76ab682a0876e007f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 09:37:59 -0500 Subject: [PATCH 18/43] Add a clear API for WLCalcAxesWidget --- gwhat/hydrocalc/axeswidgets.py | 165 ++++++++++++++++++--------------- 1 file changed, 89 insertions(+), 76 deletions(-) diff --git a/gwhat/hydrocalc/axeswidgets.py b/gwhat/hydrocalc/axeswidgets.py index ec955c3de..1637362a5 100644 --- a/gwhat/hydrocalc/axeswidgets.py +++ b/gwhat/hydrocalc/axeswidgets.py @@ -9,6 +9,7 @@ # ---- Standard library imports from typing import Any, Callable +from abc import abstractmethod # ---- Third party imports import numpy as np @@ -21,38 +22,35 @@ from matplotlib.widgets import AxesWidget -class WLCalcVSpanHighlighter(AxesWidget, QObject): - sig_span_clicked = QSignal(float) +class WLCalcAxesWidgetBase(AxesWidget, QObject): + """ + Basic functionality for WLCalc axes widgets. - def __init__(self, ax: Axes, wlcalc: QWidget, tracked_axvspans: list, - useblit: bool = True, onclicked: Callable = None, - axvspan_color: str = 'red', axvline_color: str = 'black', - ): + WARNING: Don't override any methods or attributes present here unless you + know what you are doing. + """ + + _artists = [] + _visible = {} + + def __init__(self, ax: Axes, wlcalc: QWidget): AxesWidget.__init__(self, ax) QObject.__init__(self) + self.useblit = self.canvas.supports_blit self.visible = True - self.useblit = useblit and self.canvas.supports_blit self.wlcalc = wlcalc - self.tracked_axvspans = tracked_axvspans - - if onclicked is not None: - self.sig_span_clicked.connect(onclicked) - - # Axes span highlight. - self.axvspan_highlight = ax.axvspan( - 0, 1, visible=False, color='red', linewidth=1, - ls='-', alpha=0.3) def set_active(self, active): - """ - Set whether the selector is active. - """ - self.axvspan_highlight.xy = [ - [np.inf, 1], [np.inf, 0], [np.inf, 0], [np.inf, 1]] - self.axvspan_highlight.set_visible(active) + """Set whether the axes widget is active.""" + self.set_axeswidget_active(active) super().set_active(active) self.wlcalc.draw() + def register_artist(self, artist): + """Register given artist.""" + self._artists.append(artist) + self._visible[artist] = False + def clear(self): """ Clear the selector. @@ -60,8 +58,9 @@ def clear(self): This method must be called by the canvas BEFORE making a copy of the canvas background. """ - self.__axvspan_highlight_visible = self.axvspan_highlight.get_visible() - self.axvspan_highlight.set_visible(False) + for artist in self._artists: + self._visible[artist] = artist.get_visible() + artist.set_visible(False) def restore(self): """ @@ -70,8 +69,65 @@ def restore(self): This method must be called by the canvas AFTER a copy has been made of the canvas background. """ - self.axvspan_highlight.set_visible(self.__axvspan_highlight_visible) - self.ax.draw_artist(self.axvspan_highlight) + for artist in self._artists: + artist.set_visible(self._visible[artist]) + self.ax.draw_artist(artist) + + def _update(self): + for artist in self._artists: + self.ax.draw_artist(artist) + return False + + +class WLCalcAxesWidget(WLCalcAxesWidgetBase): + """ + WLCalc axes widget class. + + All axes widgets *must* inherit this class and reimplement its interface. + """ + + @abstractmethod + def set_axeswidget_active(active): + pass + + @abstractmethod + def onmove(self, event): + """Handler that is called when the mouse cursor moves.""" + pass + + @abstractmethod + def onpress(self, event): + """Handler that is called when a mouse button is pressed.""" + pass + + @abstractmethod + def onrelease(self, event): + """Handler that is called when a mouse button is released.""" + pass + + +class WLCalcVSpanHighlighter(WLCalcAxesWidget): + sig_span_clicked = QSignal(float) + + def __init__(self, ax: Axes, wlcalc: QWidget, tracked_axvspans: list, + onclicked: Callable = None, highlight_color: str = 'red'): + super().__init__(ax, wlcalc) + self.tracked_axvspans = tracked_axvspans + + if onclicked is not None: + self.sig_span_clicked.connect(onclicked) + + # Axes span highlight. + self.axvspan_highlight = ax.axvspan( + 0, 1, visible=False, color=highlight_color, linewidth=1, + ls='-', alpha=0.3) + self.register_artist(self.axvspan_highlight) + + # ---- WLCalcAxesWidgetBase interface + def set_axeswidget_active(self, active): + self.axvspan_highlight.xy = [ + [np.inf, 1], [np.inf, 0], [np.inf, 0], [np.inf, 1]] + self.axvspan_highlight.set_visible(active) def onmove(self, event): """Handler to draw the selector when the mouse cursor moves.""" @@ -117,22 +173,13 @@ def onpress(self, event): def onrelease(self, event): pass - def _update(self): - self.ax.draw_artist(self.axvspan_highlight) - return False - -class WLCalcVSpanSelector(AxesWidget, QObject): +class WLCalcVSpanSelector(WLCalcAxesWidget): sig_span_selected = QSignal(tuple) - def __init__(self, ax: Axes, wlcalc: QWidget, - useblit: bool = True, onselected: Callable = None, + def __init__(self, ax: Axes, wlcalc: QWidget, onselected: Callable = None, axvspan_color: str = 'red', axvline_color: str = 'black'): - AxesWidget.__init__(self, ax) - QObject.__init__(self) - self.visible = True - self.useblit = useblit and self.canvas.supports_blit - self.wlcalc = wlcalc + super().__init__(ax, wlcalc) if onselected is not None: self.sig_span_selected.connect(onselected) @@ -141,20 +188,20 @@ def __init__(self, ax: Axes, wlcalc: QWidget, ax.get_xbound()[0], ax.get_xbound()[0], visible=False, color=axvspan_color, linewidth=1, ls='-', animated=self.useblit, alpha=0.1) + self.register_artist(self.axvspan) self.axvline = ax.axvline( ax.get_ybound()[0], visible=False, color=axvline_color, linewidth=1, ls='--', animated=self.useblit) + self.register_artist(self.axvline) self._onpress_xdata = [] self._onpress_button = None self._onrelease_xdata = [] super().set_active(False) - def set_active(self, active): - """ - Set whether the selector is active. - """ + # ---- WLCalcAxesWidgetBase interface + def set_axeswidget_active(self, active): self._onpress_xdata = [] self._onpress_button = None self._onrelease_xdata = [] @@ -168,36 +215,7 @@ def set_active(self, active): [np.inf, 1]] self.axvspan.set_visible(active) - super().set_active(active) - self.wlcalc.draw() - - def clear(self): - """ - Clear the selector. - - This method must be called by the canvas BEFORE making a copy of - the canvas background. - """ - self.__axvspan_visible = self.axvspan.get_visible() - self.__axvline_visible = self.axvline.get_visible() - self.axvspan.set_visible(False) - self.axvline.set_visible(False) - - def restore(self): - """ - Restore the selector. - - This method must be called by the canvas AFTER a copy has been made - of the canvas background. - """ - self.axvspan.set_visible(self.__axvspan_visible) - self.ax.draw_artist(self.axvspan) - - self.axvline.set_visible(self.__axvline_visible) - self.ax.draw_artist(self.axvline) - def onpress(self, event): - """Handler for the button_press_event event.""" if event.button == 1 and event.xdata: if self._onpress_button in [None, event.button]: self._onpress_button = event.button @@ -280,8 +298,3 @@ def onmove(self, event): self.axvline.set_visible(False) self.axvspan.set_visible(False) self._update() - - def _update(self): - self.ax.draw_artist(self.axvline) - self.ax.draw_artist(self.axvspan) - return False From 21c5ae62a778c3d3dad3798b013bae1eeb8936ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 09:55:51 -0500 Subject: [PATCH 19/43] Move save_mrc_tofile to MasterRecessionCalcTool --- gwhat/HydroCalc2.py | 21 ------------ gwhat/hydrocalc/recession/recession_tool.py | 38 ++++++++++++++++++--- gwhat/widgets/fileio.py | 9 ++--- 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index cb5c53057..bef3fc31b 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -452,27 +452,6 @@ def copy_to_clipboard(self): QApplication.clipboard().setImage(QImage.fromData(buf.getvalue())) buf.close() - # ---- MRC handlers - def save_mrc_tofile(self, filename=None): - """Save the master recession curve results to a file.""" - if filename is None: - filename = osp.join( - self.dialog_dir, - "Well_{}_mrc_results.csv".format(self.wldset['Well'])) - - filename, filetype = QFileDialog.getSaveFileName( - self, "Save MRC results", filename, 'Text CSV (*.csv)') - - if filename: - QApplication.setOverrideCursor(Qt.WaitCursor) - QApplication.processEvents() - try: - self.wldset.save_mrc_tofile(filename) - except PermissionError: - self.show_permission_error() - self.save_mrc_tofile(filename) - QApplication.restoreOverrideCursor() - # ---- Navigation and selection tools def register_navig_and_select_tool(self, tool): """ diff --git a/gwhat/hydrocalc/recession/recession_tool.py b/gwhat/hydrocalc/recession/recession_tool.py index d309905e0..e732b58b2 100644 --- a/gwhat/hydrocalc/recession/recession_tool.py +++ b/gwhat/hydrocalc/recession/recession_tool.py @@ -10,6 +10,7 @@ from __future__ import annotations # ---- Standard library imports +import os.path as osp # ---- Third party imports import pandas as pd @@ -17,7 +18,7 @@ from PyQt5.QtCore import pyqtSignal as QSignal from PyQt5.QtWidgets import ( QWidget, QComboBox, QTextEdit, QSizePolicy, QPushButton, QGridLayout, - QLabel, QApplication) + QLabel, QApplication, QFileDialog) # ---- Local imports from gwhat.hydrocalc.recession.recession_calc import calculate_mrc @@ -25,11 +26,12 @@ WLCalcVSpanSelector, WLCalcVSpanHighlighter) from gwhat.hydrocalc.api import WLCalcTool, wlcalcmethod from gwhat.utils.qthelpers import create_toolbutton -from gwhat.utils.icons import QToolButtonNormal, get_iconsize +from gwhat.utils.icons import get_iconsize from gwhat.widgets.buttons import OnOffToolButton, ToolBarWidget +from gwhat.widgets.fileio import SaveFileMixin -class MasterRecessionCalcTool(WLCalcTool): +class MasterRecessionCalcTool(WLCalcTool, SaveFileMixin): __toolname__ = 'mrc' __tooltitle__ = 'MRC' __tooltip__ = ("A tool to evaluate the master recession curve " @@ -49,6 +51,8 @@ class MasterRecessionCalcTool(WLCalcTool): def __init__(self, parent=None): super().__init__(parent) + WLCalcTool.__init__(self, parent) + SaveFileMixin.__init__(self) self.setup() def setup(self): @@ -315,7 +319,33 @@ def set_wldset(self, wldset): def set_wxdset(self, wxdset): pass - # ---- MRC Tool Interface + # ---- MRC Tool Public Interface + def save_mrc_tofile(self, filename=None): + """Save the master recession curve results to a file.""" + if self.wldset is not None: + return + + if filename is None: + filename = osp.join( + self.dialog_dir, + "mrc_results ({}).csv".format(self.wldset['Well'])) + + filename, filetype = QFileDialog.getSaveFileName( + self.parent() or self, + "Save MRC results", + filename, + 'Text CSV (*.csv)') + + if filename: + QApplication.setOverrideCursor(Qt.WaitCursor) + QApplication.processEvents() + try: + self.wldset.save_mrc_tofile(filename) + except PermissionError: + self.show_permission_error(widget=self.parent()) + self.save_mrc_tofile(filename) + QApplication.restoreOverrideCursor() + def load_mrc_from_wldset(self): """Load saved MRC results from the project hdf5 file.""" if self.wldset is not None: diff --git a/gwhat/widgets/fileio.py b/gwhat/widgets/fileio.py index c9afb9d81..e7a3b5262 100644 --- a/gwhat/widgets/fileio.py +++ b/gwhat/widgets/fileio.py @@ -27,9 +27,10 @@ def set_dialog_dir(self, dirname): """Set the default dialog directory to dirname.""" set_select_file_dialog_dir(dirname) - def select_savefilename(self, title, fname, ffmat): + def select_savefilename(self, title, fname, ffmat, widget=None): """Open a dialog where the user can select a file name.""" - fname, ftype = QFileDialog.getSaveFileName(self, title, fname, ffmat) + fname, ftype = QFileDialog.getSaveFileName( + widget or self, title, fname, ffmat) if fname: ftype = ftype.replace('*', '') fname = fname if fname.endswith(ftype) else fname + ftype @@ -38,11 +39,11 @@ def select_savefilename(self, title, fname, ffmat): else: return None - def show_permission_error(self): + def show_permission_error(self, widget=None): """ Show a warning message telling the user that the saving operation has failed. """ QApplication.restoreOverrideCursor() msg = "The file is in use by another application or user." - QMessageBox.warning(self, 'Warning', msg, QMessageBox.Ok) + QMessageBox.warning(widget or self, 'Warning', msg, QMessageBox.Ok) From d1541c16c7e84f1aa21dc2071c0fca1741f92536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 10:03:10 -0500 Subject: [PATCH 20/43] Update imports --- gwhat/HydroCalc2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index bef3fc31b..2f8e7542b 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -34,7 +34,7 @@ from xlrd.xldate import xldate_from_date_tuple # ---- Local imports -from gwhat.hydrocalc.recession.mainwidget import MasterRecessionCalcTool +from gwhat.hydrocalc.recession.recession_tool import MasterRecessionCalcTool from gwhat.brf_mod import BRFManager from gwhat.config.gui import FRAME_SYLE from gwhat.config.main import CONF @@ -99,7 +99,7 @@ def __init__(self, datamanager, parent=None): self.btn_pan.setValue(True) self.setup_ax_margins(None) - # Setup wlcalc tool. + # Setup wlcalc tools. self.mrc_eval_widget = MasterRecessionCalcTool(parent=self) self.install_tool(self.mrc_eval_widget) From b8270dae3b10b5bf7e39673414947bc2297d50f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 10:05:29 -0500 Subject: [PATCH 21/43] Change wlcalc tools installation order --- gwhat/HydroCalc2.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index 2f8e7542b..6ba2e5b38 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -100,11 +100,18 @@ def __init__(self, datamanager, parent=None): self.setup_ax_margins(None) # Setup wlcalc tools. + self.brf_eval_widget = BRFManager(parent=self) + self.install_tool(self.brf_eval_widget) + self.mrc_eval_widget = MasterRecessionCalcTool(parent=self) self.install_tool(self.mrc_eval_widget) - self.brf_eval_widget = BRFManager(parent=self) - self.install_tool(self.brf_eval_widget) + index = self.tools_tabwidget.addTab(self.rechg_eval_widget, 'Recharge') + self.tools_tabwidget.setTabToolTip( + index, + ("A tool to evaluate groundwater recharge and its " + "uncertainty from observed water levels and daily " + "weather data.")) self.tools_tabwidget.setCurrentIndex( CONF.get('hydrocalc', 'current_tool_index')) @@ -355,12 +362,6 @@ def __initUI__(self): # Setup the tools tab area. self.tools_tabwidget = QTabWidget() - self.tools_tabwidget.addTab(self.rechg_eval_widget, 'Recharge') - self.tools_tabwidget.setTabToolTip( - 1, ("

A tool to evaluate groundwater recharge and its" - " uncertainty from observed water levels and daily " - " weather data.

")) - # Setup the right panel. self.right_panel = QFrame() right_panel_layout = QGridLayout(self.right_panel) From d0a5d78aa11ce234dab227c2ef9eabdc86039e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 10:20:20 -0500 Subject: [PATCH 22/43] Fix draw mrc when date format changes --- gwhat/HydroCalc2.py | 3 ++- gwhat/hydrocalc/recession/recession_tool.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index 6ba2e5b38..35f1531f0 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -55,6 +55,7 @@ class WLCalc(QWidget, SaveFileMixin): MRC and ultimately estimate groundwater recharge. """ sig_wldset_changed = QSignal() + sig_date_format_changed = QSignal() def __init__(self, datamanager, parent=None): QWidget.__init__(self, parent) @@ -684,9 +685,9 @@ def switch_date_format(self): self._draw_obs_wl() self.draw_meas_wl() - self.draw_mrc() self.draw_weather() self.draw_glue_wl() + self.sig_date_format_changed.emit() self.draw() def setup_wl_style(self): diff --git a/gwhat/hydrocalc/recession/recession_tool.py b/gwhat/hydrocalc/recession/recession_tool.py index e732b58b2..8d6895372 100644 --- a/gwhat/hydrocalc/recession/recession_tool.py +++ b/gwhat/hydrocalc/recession/recession_tool.py @@ -278,6 +278,7 @@ def register_tool(self, wlcalc: QWidget): # wlcalc.tools_tabwidget.currentChanged.connect( # lambda: self.toggle_brfperiod_selection(False)) wlcalc.sig_wldset_changed.connect(self._on_wldset_changed) + wlcalc.sig_date_format_changed.connect(self._draw_mrc) wlcalc.sig_new_mrc = self.sig_new_mrc # Add "Show MRC" button to WLCalc toolbar. From 36610677d9f462fdbd6c3bef1d19671f017b6686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 10:20:53 -0500 Subject: [PATCH 23/43] Rename btn_MRCalc -> btn_calc_mrc --- gwhat/hydrocalc/recession/recession_tool.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gwhat/hydrocalc/recession/recession_tool.py b/gwhat/hydrocalc/recession/recession_tool.py index 8d6895372..d5a23e6d1 100644 --- a/gwhat/hydrocalc/recession/recession_tool.py +++ b/gwhat/hydrocalc/recession/recession_tool.py @@ -103,9 +103,9 @@ def setup(self): tip='Save calculated MRC to file.', triggered=lambda: self.save_mrc_tofile()) - self.btn_MRCalc = QPushButton('Compute MRC') - self.btn_MRCalc.clicked.connect(self._btn_MRCalc_isClicked) - self.btn_MRCalc.setToolTip( + self.btn_calc_mrc = QPushButton('Compute MRC') + self.btn_calc_mrc.clicked.connect(self.calculate_mrc) + self.btn_calc_mrc.setToolTip( 'Calculate the Master Recession Curve (MRC).') mrc_tb = ToolBarWidget() @@ -127,7 +127,7 @@ def setup(self): layout.setRowMinimumHeight(row, 5) layout.setRowStretch(row, 100) row += 1 - layout.addWidget(self.btn_MRCalc, row, 0, 1, 3) + layout.addWidget(self.btn_calc_mrc, row, 0, 1, 3) layout.setColumnStretch(2, 500) # This button needs to be added to WCalc toolbar. From 3115ced1ced887e85c9d6d3ce22c54214edd2d1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 10:21:07 -0500 Subject: [PATCH 24/43] Rename _btn_MRCalc_isClicked -> calculate_mrc --- gwhat/hydrocalc/recession/recession_tool.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gwhat/hydrocalc/recession/recession_tool.py b/gwhat/hydrocalc/recession/recession_tool.py index d5a23e6d1..38de5925d 100644 --- a/gwhat/hydrocalc/recession/recession_tool.py +++ b/gwhat/hydrocalc/recession/recession_tool.py @@ -230,8 +230,7 @@ def _draw_mrc_periods(self): ls='-', alpha=0.1) self._mrc_period_axvspans.append(axvspan) - @wlcalcmethod - def _btn_MRCalc_isClicked(self): + def calculate_mrc(self): if self.wldset is None: return QApplication.setOverrideCursor(Qt.WaitCursor) From 44e6c78508178065e7f357da5d66a3d82b943fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 10:27:02 -0500 Subject: [PATCH 25/43] Move calculate_mrc and fix save_mrc_tofile --- gwhat/hydrocalc/recession/recession_tool.py | 61 +++++++++++---------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/gwhat/hydrocalc/recession/recession_tool.py b/gwhat/hydrocalc/recession/recession_tool.py index 38de5925d..65e459a57 100644 --- a/gwhat/hydrocalc/recession/recession_tool.py +++ b/gwhat/hydrocalc/recession/recession_tool.py @@ -230,34 +230,6 @@ def _draw_mrc_periods(self): ls='-', alpha=0.1) self._mrc_period_axvspans.append(axvspan) - def calculate_mrc(self): - if self.wldset is None: - return - QApplication.setOverrideCursor(Qt.WaitCursor) - - coeffs, hp, std_err, r_squared, rmse = calculate_mrc( - self.wlcalc.time, self.wlcalc.water_lvl, self._mrc_period_xdata, - self.MRC_type.currentIndex()) - A = coeffs.A - B = coeffs.B - print('MRC Parameters: A={}, B={}'.format( - 'None' if pd.isnull(A) else '{:0.3f}'.format(coeffs.A), - 'None' if pd.isnull(B) else '{:0.3f}'.format(coeffs.B))) - - # Store and plot the results. - print('Saving MRC interpretation in dataset...') - self.wldset.set_mrc( - A, B, self._mrc_period_xdata, - self.wlcalc.time, hp, - std_err, r_squared, rmse) - - self.show_mrc_results() - self.btn_save_mrc.setEnabled(True) - self._draw_mrc() - self.sig_new_mrc.emit() - - QApplication.restoreOverrideCursor() - def btn_show_mrc_isclicked(self): """Handle when the button to draw of hide the mrc is clicked.""" if self.btn_show_mrc.value() is False: @@ -320,9 +292,40 @@ def set_wxdset(self, wxdset): pass # ---- MRC Tool Public Interface + def calculate_mrc(self): + if self.wldset is None: + return + + QApplication.setOverrideCursor(Qt.WaitCursor) + + coeffs, hp, std_err, r_squared, rmse = calculate_mrc( + self.wldset.xldates, + self.wldset.waterlevels, + self._mrc_period_xdata, + self.MRC_type.currentIndex()) + A = coeffs.A + B = coeffs.B + print('MRC Parameters: A={}, B={}'.format( + 'None' if pd.isnull(A) else '{:0.3f}'.format(coeffs.A), + 'None' if pd.isnull(B) else '{:0.3f}'.format(coeffs.B))) + + # Store and plot the results. + print('Saving MRC interpretation in dataset...') + self.wldset.set_mrc( + A, B, self._mrc_period_xdata, + self.wldset.xldates, hp, + std_err, r_squared, rmse) + + self.show_mrc_results() + self.btn_save_mrc.setEnabled(True) + self._draw_mrc() + self.sig_new_mrc.emit() + + QApplication.restoreOverrideCursor() + def save_mrc_tofile(self, filename=None): """Save the master recession curve results to a file.""" - if self.wldset is not None: + if self.wldset is None: return if filename is None: From c63548df31807f32a6ecb526aba397518fb9672e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 10:27:12 -0500 Subject: [PATCH 26/43] Update test_hydrocalc --- gwhat/tests/test_hydrocalc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gwhat/tests/test_hydrocalc.py b/gwhat/tests/test_hydrocalc.py index 590d19faa..2dd03b40a 100644 --- a/gwhat/tests/test_hydrocalc.py +++ b/gwhat/tests/test_hydrocalc.py @@ -108,7 +108,7 @@ def test_calc_mrc(hydrocalc, tmp_path, qtbot, mocker): (41440.604166666664, 41447.697916666664), (41543.958333333336, 41552.541666666664)] for coord in coordinates: - hydrocalc.add_mrcperiod(coord) + hydrocalc.tools['mrc'].add_mrcperiod(coord) # Calcul the MRC. mrc_data = hydrocalc.wldset.get_mrc() @@ -117,7 +117,7 @@ def test_calc_mrc(hydrocalc, tmp_path, qtbot, mocker): assert len(mrc_data['recess']) == 0 assert len(mrc_data['time']) == 0 - hydrocalc.btn_MRCalc_isClicked() + hydrocalc.tools['mrc'].calculate_mrc() mrc_data = hydrocalc.wldset.get_mrc() assert abs(mrc_data['params'][0] - 0.07004324034418882) < 10**-5 @@ -136,7 +136,7 @@ def test_calc_mrc(hydrocalc, tmp_path, qtbot, mocker): return_value=(outfile, ffilter)) assert not osp.exists(outfile + '.csv') - hydrocalc.save_mrc_tofile() + hydrocalc.tools['mrc'].save_mrc_tofile() assert osp.exists(outfile + '.csv') assert qfdialog_patcher.call_count == 1 From d2e6858c2c42ba134f0ba8b69d4ed02a813e472f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 11:05:47 -0500 Subject: [PATCH 27/43] WLCalc: fix xycoord draw --- gwhat/HydroCalc2.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index 35f1531f0..1bc31fc0d 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -708,6 +708,7 @@ def setup_wl_style(self): def draw(self): """Draw the canvas and save a snapshot of the background figure.""" + xycoord_is_visible = self.xycoord.get_visible() self.xycoord.set_visible(False) for widget in self._axes_widgets: widget.clear() @@ -715,6 +716,8 @@ def draw(self): self.canvas.draw() self._figbckground = self.fig.canvas.copy_from_bbox(self.fig.bbox) + self.xycoord.set_visible(xycoord_is_visible) + self.fig.axes[0].draw_artist(self.xycoord) for widget in self._axes_widgets: widget.restore() @@ -969,7 +972,6 @@ def onpress(self, event): for widget in self._axes_widgets: if widget.get_active(): widget.onpress(event) - self.draw() From 456432c15b9d5337f14acc373558114e20baba3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 11:06:09 -0500 Subject: [PATCH 28/43] WLCalcAxesWidgetBase: set active to False in init --- gwhat/hydrocalc/axeswidgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gwhat/hydrocalc/axeswidgets.py b/gwhat/hydrocalc/axeswidgets.py index 1637362a5..5cc9566c8 100644 --- a/gwhat/hydrocalc/axeswidgets.py +++ b/gwhat/hydrocalc/axeswidgets.py @@ -39,6 +39,7 @@ def __init__(self, ax: Axes, wlcalc: QWidget): self.useblit = self.canvas.supports_blit self.visible = True self.wlcalc = wlcalc + super().set_active(False) def set_active(self, active): """Set whether the axes widget is active.""" @@ -198,7 +199,6 @@ def __init__(self, ax: Axes, wlcalc: QWidget, onselected: Callable = None, self._onpress_xdata = [] self._onpress_button = None self._onrelease_xdata = [] - super().set_active(False) # ---- WLCalcAxesWidgetBase interface def set_axeswidget_active(self, active): From c7681765134eb5f93c8d4f4ce90a339dc627eaf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 11:06:25 -0500 Subject: [PATCH 29/43] Fix remove mrc axes widget activation --- gwhat/hydrocalc/recession/recession_tool.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gwhat/hydrocalc/recession/recession_tool.py b/gwhat/hydrocalc/recession/recession_tool.py index 65e459a57..ff80f2ce1 100644 --- a/gwhat/hydrocalc/recession/recession_tool.py +++ b/gwhat/hydrocalc/recession/recession_tool.py @@ -91,7 +91,7 @@ def setup(self): "Left-click on the graph to add new recession periods.") self.btn_delpeak = OnOffToolButton('pencil_del', size='normal') - self.btn_delpeak.clicked.connect( + self.btn_delpeak.sig_value_changed.connect( lambda: self._btn_delpeak_isclicked()) self.btn_delpeak.setToolTip( "Left-click on a recession period to remove it.") @@ -205,6 +205,7 @@ def _btn_delpeak_isclicked(self): if self.btn_delpeak.value(): self.wlcalc.toggle_navig_and_select_tools(self.btn_delpeak) self.btn_show_mrc.setValue(True) + self.mrc_remover.set_active(self.btn_delpeak.value()) self.wlcalc.draw() @wlcalcmethod From 2572f27edb7a08b54fc603ddfae0721187bc6c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 11:09:57 -0500 Subject: [PATCH 30/43] Rename btn_addpeak -> btn_add_period --- gwhat/hydrocalc/recession/recession_tool.py | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/gwhat/hydrocalc/recession/recession_tool.py b/gwhat/hydrocalc/recession/recession_tool.py index ff80f2ce1..947456b8c 100644 --- a/gwhat/hydrocalc/recession/recession_tool.py +++ b/gwhat/hydrocalc/recession/recession_tool.py @@ -84,10 +84,10 @@ def setup(self): tip='Clear all recession periods.', triggered=self.clear_all_mrcperiods) - self.btn_addpeak = OnOffToolButton('pencil_add', size='normal') - self.btn_addpeak.sig_value_changed.connect( - lambda: self._btn_addpeak_isclicked()) - self.btn_addpeak.setToolTip( + self.btn_add_period = OnOffToolButton('pencil_add', size='normal') + self.btn_add_period.sig_value_changed.connect( + lambda: self._btn_add_period_isclicked()) + self.btn_add_period.setToolTip( "Left-click on the graph to add new recession periods.") self.btn_delpeak = OnOffToolButton('pencil_del', size='normal') @@ -109,7 +109,7 @@ def setup(self): 'Calculate the Master Recession Curve (MRC).') mrc_tb = ToolBarWidget() - for btn in [self.btn_undo, self.btn_clearPeak, self.btn_addpeak, + for btn in [self.btn_undo, self.btn_clearPeak, self.btn_add_period, self.btn_delpeak, self.btn_save_mrc]: mrc_tb.addWidget(btn) @@ -192,12 +192,12 @@ def _draw_mrc_wl(self): self._mrc_plt.set_visible(False) @wlcalcmethod - def _btn_addpeak_isclicked(self): + def _btn_add_period_isclicked(self): """Handle when the button add_peak is clicked.""" - if self.btn_addpeak.value(): - self.wlcalc.toggle_navig_and_select_tools(self.btn_addpeak) + if self.btn_add_period.value(): + self.wlcalc.toggle_navig_and_select_tools(self.btn_add_period) self.btn_show_mrc.setValue(True) - self.mrc_selector.set_active(self.btn_addpeak.value()) + self.mrc_selector.set_active(self.btn_add_period.value()) @wlcalcmethod def _btn_delpeak_isclicked(self): @@ -234,7 +234,7 @@ def _draw_mrc_periods(self): def btn_show_mrc_isclicked(self): """Handle when the button to draw of hide the mrc is clicked.""" if self.btn_show_mrc.value() is False: - self.btn_addpeak.setValue(False) + self.btn_add_period.setValue(False) self.btn_delpeak.setValue(False) self._draw_mrc() @@ -261,7 +261,7 @@ def register_tool(self, wlcalc: QWidget): action, self.btn_show_mrc) # Setup the axes widget to add and remove recession periods. - wlcalc.register_navig_and_select_tool(self.btn_addpeak) + wlcalc.register_navig_and_select_tool(self.btn_add_period) wlcalc.register_navig_and_select_tool(self.btn_delpeak) self.mrc_selector = WLCalcVSpanSelector( From df638e8b19b77c29491a110a89d8a7ac4582e0bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 11:10:50 -0500 Subject: [PATCH 31/43] Rename btn_delpeak -> btn_del_period --- gwhat/hydrocalc/recession/recession_tool.py | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/gwhat/hydrocalc/recession/recession_tool.py b/gwhat/hydrocalc/recession/recession_tool.py index 947456b8c..c9f7739cd 100644 --- a/gwhat/hydrocalc/recession/recession_tool.py +++ b/gwhat/hydrocalc/recession/recession_tool.py @@ -90,10 +90,10 @@ def setup(self): self.btn_add_period.setToolTip( "Left-click on the graph to add new recession periods.") - self.btn_delpeak = OnOffToolButton('pencil_del', size='normal') - self.btn_delpeak.sig_value_changed.connect( - lambda: self._btn_delpeak_isclicked()) - self.btn_delpeak.setToolTip( + self.btn_del_period = OnOffToolButton('pencil_del', size='normal') + self.btn_del_period.sig_value_changed.connect( + lambda: self._btn_del_period_isclicked()) + self.btn_del_period.setToolTip( "Left-click on a recession period to remove it.") self.btn_save_mrc = create_toolbutton( @@ -110,7 +110,7 @@ def setup(self): mrc_tb = ToolBarWidget() for btn in [self.btn_undo, self.btn_clearPeak, self.btn_add_period, - self.btn_delpeak, self.btn_save_mrc]: + self.btn_del_period, self.btn_save_mrc]: mrc_tb.addWidget(btn) # Setup the MRC Layout. @@ -200,12 +200,12 @@ def _btn_add_period_isclicked(self): self.mrc_selector.set_active(self.btn_add_period.value()) @wlcalcmethod - def _btn_delpeak_isclicked(self): - """Handle when the button btn_delpeak is clicked.""" - if self.btn_delpeak.value(): - self.wlcalc.toggle_navig_and_select_tools(self.btn_delpeak) + def _btn_del_period_isclicked(self): + """Handle when the button btn_del_period is clicked.""" + if self.btn_del_period.value(): + self.wlcalc.toggle_navig_and_select_tools(self.btn_del_period) self.btn_show_mrc.setValue(True) - self.mrc_remover.set_active(self.btn_delpeak.value()) + self.mrc_remover.set_active(self.btn_del_period.value()) self.wlcalc.draw() @wlcalcmethod @@ -235,7 +235,7 @@ def btn_show_mrc_isclicked(self): """Handle when the button to draw of hide the mrc is clicked.""" if self.btn_show_mrc.value() is False: self.btn_add_period.setValue(False) - self.btn_delpeak.setValue(False) + self.btn_del_period.setValue(False) self._draw_mrc() # ---- WLCalcTool API @@ -262,7 +262,7 @@ def register_tool(self, wlcalc: QWidget): # Setup the axes widget to add and remove recession periods. wlcalc.register_navig_and_select_tool(self.btn_add_period) - wlcalc.register_navig_and_select_tool(self.btn_delpeak) + wlcalc.register_navig_and_select_tool(self.btn_del_period) self.mrc_selector = WLCalcVSpanSelector( self.wlcalc.fig.axes[0], wlcalc, From 60ef56c9ab28f917b25cec8980a91fdd8efbe415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 11:16:49 -0500 Subject: [PATCH 32/43] Rename some widgets to make code clearer --- gwhat/hydrocalc/recession/recession_tool.py | 32 ++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/gwhat/hydrocalc/recession/recession_tool.py b/gwhat/hydrocalc/recession/recession_tool.py index c9f7739cd..5a37dcbca 100644 --- a/gwhat/hydrocalc/recession/recession_tool.py +++ b/gwhat/hydrocalc/recession/recession_tool.py @@ -57,15 +57,15 @@ def __init__(self, parent=None): def setup(self): # Setup MRC parameter widgets. - self.MRC_type = QComboBox() - self.MRC_type.addItems(['Linear', 'Exponential']) - self.MRC_type.setCurrentIndex(1) - - self.MRC_results = QTextEdit() - self.MRC_results.setReadOnly(True) - self.MRC_results.setMinimumHeight(25) - self.MRC_results.setMinimumWidth(100) - self.MRC_results.setSizePolicy( + self.cbox_mrc_type = QComboBox() + self.cbox_mrc_type.addItems(['Linear', 'Exponential']) + self.cbox_mrc_type.setCurrentIndex(1) + + self.txtedit_mrc_results = QTextEdit() + self.txtedit_mrc_results.setReadOnly(True) + self.txtedit_mrc_results.setMinimumHeight(25) + self.txtedit_mrc_results.setMinimumWidth(100) + self.txtedit_mrc_results.setSizePolicy( QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred)) # Setup the toolbar. @@ -77,7 +77,7 @@ def setup(self): triggered=self.undo_mrc_period) self.btn_undo.setEnabled(False) - self.btn_clearPeak = create_toolbutton( + self.btn_clear_periods = create_toolbutton( parent=self, icon='clear_changes', iconsize=get_iconsize('normal'), @@ -109,7 +109,7 @@ def setup(self): 'Calculate the Master Recession Curve (MRC).') mrc_tb = ToolBarWidget() - for btn in [self.btn_undo, self.btn_clearPeak, self.btn_add_period, + for btn in [self.btn_undo, self.btn_clear_periods, self.btn_add_period, self.btn_del_period, self.btn_save_mrc]: mrc_tb.addWidget(btn) @@ -118,9 +118,9 @@ def setup(self): row = 0 layout.addWidget(QLabel('MRC Type :'), row, 0) - layout.addWidget(self.MRC_type, row, 1) + layout.addWidget(self.cbox_mrc_type, row, 1) row += 1 - layout.addWidget(self.MRC_results, row, 0, 1, 3) + layout.addWidget(self.txtedit_mrc_results, row, 0, 1, 3) row += 1 layout.addWidget(mrc_tb, row, 0, 1, 3) row += 1 @@ -303,7 +303,7 @@ def calculate_mrc(self): self.wldset.xldates, self.wldset.waterlevels, self._mrc_period_xdata, - self.MRC_type.currentIndex()) + self.cbox_mrc_type.currentIndex()) A = coeffs.A B = coeffs.B print('MRC Parameters: A={}, B={}'.format( @@ -424,7 +424,7 @@ def remove_mrcperiod(self, xdata): def show_mrc_results(self): """Show MRC results if any.""" if self.wldset is None: - self.MRC_results.setHtml('') + self.txtedit_mrc_results.setHtml('') return mrc_data = self.wldset.get_mrc() @@ -453,4 +453,4 @@ def show_mrc_results(self): text += label.format('N/A') else: text += label.format('{:0.5f}'.format(value)) - self.MRC_results.setHtml(text) + self.txtedit_mrc_results.setHtml(text) From 091a6a05775ccd7307d2128697a9ebb023232b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 13:20:55 -0500 Subject: [PATCH 33/43] Update imports --- gwhat/HydroCalc2.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index 1bc31fc0d..a0df96a39 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -11,7 +11,6 @@ import io import os.path as osp import datetime -from typing import Any, Callable # ---- Third party imports import numpy as np @@ -21,7 +20,7 @@ from PyQt5.QtCore import pyqtSignal as QSignal from PyQt5.QtWidgets import ( QGridLayout, QTabWidget, QApplication, QWidget, QMainWindow, - QToolBar, QFrame, QMessageBox, QFileDialog) + QToolBar, QFrame, QMessageBox) import matplotlib as mpl from matplotlib.figure import Figure as MplFigure From a05058c527478c3c8523a3046b196c119a58fc2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 13:40:14 -0500 Subject: [PATCH 34/43] Fixing noob mistake of settings mutable as defaults... --- gwhat/hydrocalc/axeswidgets.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/gwhat/hydrocalc/axeswidgets.py b/gwhat/hydrocalc/axeswidgets.py index 5cc9566c8..4a4966075 100644 --- a/gwhat/hydrocalc/axeswidgets.py +++ b/gwhat/hydrocalc/axeswidgets.py @@ -30,15 +30,14 @@ class WLCalcAxesWidgetBase(AxesWidget, QObject): know what you are doing. """ - _artists = [] - _visible = {} - def __init__(self, ax: Axes, wlcalc: QWidget): AxesWidget.__init__(self, ax) QObject.__init__(self) self.useblit = self.canvas.supports_blit self.visible = True self.wlcalc = wlcalc + self._artists = [] + self._visible = {} super().set_active(False) def set_active(self, active): @@ -72,7 +71,7 @@ def restore(self): """ for artist in self._artists: artist.set_visible(self._visible[artist]) - self.ax.draw_artist(artist) + self._update() def _update(self): for artist in self._artists: From bb33a9c17f8f9495121d48377556b27cc14f3ca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 14:34:45 -0500 Subject: [PATCH 35/43] Change attr names to avoid clash with matplolib subclass --- gwhat/hydrocalc/axeswidgets.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/gwhat/hydrocalc/axeswidgets.py b/gwhat/hydrocalc/axeswidgets.py index 4a4966075..b6dbcc87b 100644 --- a/gwhat/hydrocalc/axeswidgets.py +++ b/gwhat/hydrocalc/axeswidgets.py @@ -36,8 +36,8 @@ def __init__(self, ax: Axes, wlcalc: QWidget): self.useblit = self.canvas.supports_blit self.visible = True self.wlcalc = wlcalc - self._artists = [] - self._visible = {} + self.__artists = [] + self.__visible = {} super().set_active(False) def set_active(self, active): @@ -48,8 +48,8 @@ def set_active(self, active): def register_artist(self, artist): """Register given artist.""" - self._artists.append(artist) - self._visible[artist] = False + self.__artists.append(artist) + self.__visible[artist] = False def clear(self): """ @@ -58,8 +58,8 @@ def clear(self): This method must be called by the canvas BEFORE making a copy of the canvas background. """ - for artist in self._artists: - self._visible[artist] = artist.get_visible() + for artist in self.__artists: + self.__visible[artist] = artist.get_visible() artist.set_visible(False) def restore(self): @@ -69,12 +69,12 @@ def restore(self): This method must be called by the canvas AFTER a copy has been made of the canvas background. """ - for artist in self._artists: - artist.set_visible(self._visible[artist]) + for artist in self.__artists: + artist.set_visible(self.__visible[artist]) self._update() def _update(self): - for artist in self._artists: + for artist in self.__artists: self.ax.draw_artist(artist) return False From 5e1aacc56b4398331adabde4e87af4e3a150b707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 18:27:26 -0500 Subject: [PATCH 36/43] WLCalc: get rid of sig_wldset_changed logic --- gwhat/HydroCalc2.py | 3 -- gwhat/hydrocalc/recession/recession_tool.py | 46 ++++++++++----------- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index a0df96a39..a5e4f3e5f 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -53,7 +53,6 @@ class WLCalc(QWidget, SaveFileMixin): i.e. display the data as a continuous line or individual dot, perform a MRC and ultimately estimate groundwater recharge. """ - sig_wldset_changed = QSignal() sig_date_format_changed = QSignal() def __init__(self, datamanager, parent=None): @@ -426,8 +425,6 @@ def set_wldset(self, wldset): self.setup_hydrograph() self._navig_toolbar.update() - self.sig_wldset_changed.emit() - def set_wxdset(self, wxdset): """Set the weather dataset.""" self.rechg_eval_widget.set_wxdset(wxdset) diff --git a/gwhat/hydrocalc/recession/recession_tool.py b/gwhat/hydrocalc/recession/recession_tool.py index 5a37dcbca..ac0dd21dd 100644 --- a/gwhat/hydrocalc/recession/recession_tool.py +++ b/gwhat/hydrocalc/recession/recession_tool.py @@ -37,22 +37,26 @@ class MasterRecessionCalcTool(WLCalcTool, SaveFileMixin): __tooltip__ = ("A tool to evaluate the master recession curve " "of the hydrograph.") - # Whether it is the first time showEvent is called. - _first_show_event = True - - # The WLCalc instance to which this tool is registered. - wlcalc = None - - _mrc_period_xdata = [] - _mrc_period_axvspans = [] - _mrc_period_memory = [[], ] - sig_new_mrc = QSignal() def __init__(self, parent=None): super().__init__(parent) WLCalcTool.__init__(self, parent) SaveFileMixin.__init__(self) + + self._mrc_period_xdata = [] + self._mrc_period_axvspans = [] + self._mrc_period_memory = [[], ] + + # Whether it is the first time showEvent is called. + self._first_show_event = True + + # The WLCalc instance to which this tool is registered. + self.wlcalc = None + + # The water level dataset currently registered to this tool. + self.wldset = None + self.setup() def setup(self): @@ -140,19 +144,6 @@ def setup(self): self.get_option('show_mrc', True), silent=True) # ---- WLCalc integration - @property - def wldset(self): - return None if self.wlcalc is None else self.wlcalc.wldset - - @wlcalcmethod - def _on_wldset_changed(self): - self._mrc_period_xdata = [] - self._mrc_period_memory = [[], ] - self.btn_undo.setEnabled(False) - self.setEnabled(self.wldset is not None) - self.load_mrc_from_wldset() - self._draw_mrc() - @wlcalcmethod def _on_period_selected(self, xdata): """ @@ -249,7 +240,6 @@ def register_tool(self, wlcalc: QWidget): wlcalc.tools_tabwidget.setTabToolTip(index, self.tooltip()) # wlcalc.tools_tabwidget.currentChanged.connect( # lambda: self.toggle_brfperiod_selection(False)) - wlcalc.sig_wldset_changed.connect(self._on_wldset_changed) wlcalc.sig_date_format_changed.connect(self._draw_mrc) wlcalc.sig_new_mrc = self.sig_new_mrc @@ -287,7 +277,13 @@ def close_tool(self): super().close() def set_wldset(self, wldset): - pass + self.wldset = wldset + self._mrc_period_xdata = [] + self._mrc_period_memory = [[], ] + self.btn_undo.setEnabled(False) + self.setEnabled(self.wldset is not None) + self.load_mrc_from_wldset() + self._draw_mrc() def set_wxdset(self, wxdset): pass From 0d99c11d6e0bdfa2fe938274e6b06403d50edc18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 18:35:26 -0500 Subject: [PATCH 37/43] BRFManager: define class attrs in init --- gwhat/brf_mod/kgs_gui.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/gwhat/brf_mod/kgs_gui.py b/gwhat/brf_mod/kgs_gui.py index c182adad2..277d5c3f1 100644 --- a/gwhat/brf_mod/kgs_gui.py +++ b/gwhat/brf_mod/kgs_gui.py @@ -131,13 +131,18 @@ class BRFManager(WLCalcTool): __tooltip__ = ("A tool to evaluate the barometric " "response function of wells.") - # Whether it is the first time showEvent is called. - _first_show_event = True - def __init__(self, wldset=None, parent=None): super().__init__(parent) - self._bp_and_et_lags_are_linked = False + # Whether it is the first time showEvent is called. + self._first_show_event = True + + # The WLCalc instance to which this tool is registered. self.wlcalc = None + + # The water level dataset currently registered to this tool. + self.wldset = None + + self._bp_and_et_lags_are_linked = False self._previous_toggled_navig_and_select_tool = None self.kgs_brf_installer = None From 49553d376bc30410f14b151228d636e312940149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 18:36:19 -0500 Subject: [PATCH 38/43] Use waitExposed instead of waitForWindowShown --- gwhat/meteo/tests/test_weather_viewer.py | 2 +- gwhat/tests/test_hydroprint.py | 2 +- gwhat/tests/test_mainwindow.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gwhat/meteo/tests/test_weather_viewer.py b/gwhat/meteo/tests/test_weather_viewer.py index 991f09df1..bf01f2a22 100644 --- a/gwhat/meteo/tests/test_weather_viewer.py +++ b/gwhat/meteo/tests/test_weather_viewer.py @@ -41,7 +41,7 @@ def weather_viewer(qtbot, wxdataset): weather_viewer.set_weather_dataset(wxdataset) weather_viewer.show() qtbot.addWidget(weather_viewer) - qtbot.waitForWindowShown(weather_viewer) + qtbot.waitExposed(weather_viewer) return weather_viewer diff --git a/gwhat/tests/test_hydroprint.py b/gwhat/tests/test_hydroprint.py index 7af6263be..aa7a30340 100644 --- a/gwhat/tests/test_hydroprint.py +++ b/gwhat/tests/test_hydroprint.py @@ -86,7 +86,7 @@ def pagesetup(qtbot): def test_hydroprint_page_setup(hydroprint, mocker, qtbot, projectpath): """Test the Page Setup Window is shown correctly.""" qtbot.mouseClick(hydroprint.btn_page_setup, Qt.LeftButton) - qtbot.waitForWindowShown(hydroprint.page_setup_win) + qtbot.waitExposed(hydroprint.page_setup_win) def test_autoplot_hydroprint(hydroprint): diff --git a/gwhat/tests/test_mainwindow.py b/gwhat/tests/test_mainwindow.py index a541052b6..d654a255f 100644 --- a/gwhat/tests/test_mainwindow.py +++ b/gwhat/tests/test_mainwindow.py @@ -45,7 +45,7 @@ def mainwindow(qtbot, mocker): mainwindow = MainWindow() # qtbot.addWidget(mainwindow) mainwindow.show() - qtbot.waitForWindowShown(mainwindow) + qtbot.waitExposed(mainwindow) return mainwindow From 3b5cc8dbc185d7fe6076bba813bd309bab08ba87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 4 Mar 2022 19:04:57 -0500 Subject: [PATCH 39/43] Use waitExposed instead of waitForWindowShown --- gwhat/gwrecharge/tests/test_gwrecharge_plot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gwhat/gwrecharge/tests/test_gwrecharge_plot.py b/gwhat/gwrecharge/tests/test_gwrecharge_plot.py index 9507e6c15..f03473e51 100644 --- a/gwhat/gwrecharge/tests/test_gwrecharge_plot.py +++ b/gwhat/gwrecharge/tests/test_gwrecharge_plot.py @@ -43,7 +43,7 @@ def figstackmanager(qtbot): figstackmanager = FigureStackManager() qtbot.addWidget(figstackmanager) figstackmanager.show() - qtbot.waitForWindowShown(figstackmanager) + qtbot.waitExposed(figstackmanager) return figstackmanager From 0b69aa1f92025001f3c934c33a0e4fa0a973dae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Sat, 5 Mar 2022 03:58:22 -0500 Subject: [PATCH 40/43] Change how sig_new_mrc is connectd to hydroprint --- gwhat/hydrocalc/recession/recession_tool.py | 1 - gwhat/mainwindow.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/gwhat/hydrocalc/recession/recession_tool.py b/gwhat/hydrocalc/recession/recession_tool.py index ac0dd21dd..766e88956 100644 --- a/gwhat/hydrocalc/recession/recession_tool.py +++ b/gwhat/hydrocalc/recession/recession_tool.py @@ -241,7 +241,6 @@ def register_tool(self, wlcalc: QWidget): # wlcalc.tools_tabwidget.currentChanged.connect( # lambda: self.toggle_brfperiod_selection(False)) wlcalc.sig_date_format_changed.connect(self._draw_mrc) - wlcalc.sig_new_mrc = self.sig_new_mrc # Add "Show MRC" button to WLCalc toolbar. before_widget = wlcalc.btn_show_meas_wl diff --git a/gwhat/mainwindow.py b/gwhat/mainwindow.py index 60337a86d..d741612f5 100644 --- a/gwhat/mainwindow.py +++ b/gwhat/mainwindow.py @@ -120,7 +120,7 @@ def __initUI__(self): # Setup the tab analyse hydrograph. splash.showMessage("Initializing analyse hydrograph...") self.tab_hydrocalc = HydroCalc.WLCalc(self.dmanager) - self.tab_hydrocalc.sig_new_mrc.connect( + self.tab_hydrocalc.tools['mrc'].sig_new_mrc.connect( self.tab_hydrograph.mrc_wl_changed) self.tab_hydrocalc.rechg_eval_widget.sig_new_gluedf.connect( self.tab_hydrograph.glue_wl_changed) From 1b338bf6cf6d953ef7ed3de27f62cfba013d84e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Sat, 5 Mar 2022 04:44:13 -0500 Subject: [PATCH 41/43] Explicitly close hydrocalc in tests to avoid errors in other tests --- gwhat/tests/test_hydrocalc.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/gwhat/tests/test_hydrocalc.py b/gwhat/tests/test_hydrocalc.py index 2dd03b40a..31495d59c 100644 --- a/gwhat/tests/test_hydrocalc.py +++ b/gwhat/tests/test_hydrocalc.py @@ -8,7 +8,6 @@ # ----------------------------------------------------------------------------- # ---- Standard Libraries Imports -import os import os.path as osp # ---- Third Party Libraries Imports @@ -35,15 +34,10 @@ # ---- Pytest Fixtures -@pytest.fixture(scope="module") -def projectpath(tmp_path_factory): - return tmp_path_factory.mktemp("project_test_hydrocalc") - - -@pytest.fixture(scope="module") -def project(projectpath): +@pytest.fixture +def project(tmp_path): # Create a project and add add the wldset to it. - project = ProjetReader(osp.join(projectpath, "project_test_hydrocalc.gwt")) + project = ProjetReader(osp.join(tmp_path, "project_test_hydrocalc.gwt")) # Add the weather dataset to the project. wxdset = WXDataFrame(WXFILENAME) @@ -65,15 +59,22 @@ def datamanager(project): @pytest.fixture def hydrocalc(datamanager, qtbot): hydrocalc = WLCalc(datamanager) - qtbot.addWidget(hydrocalc) hydrocalc.show() - return hydrocalc + + yield hydrocalc + + # We need to explicitely close hydrocalc, the datamanager and the project + # to avoid 'Windows fatal exception: access violation' errors in + # other tests. + hydrocalc.close() + datamanager.projet.close() + datamanager.close() # ============================================================================= # ---- Tests # ============================================================================= -def test_hydrocalc_init(hydrocalc, mocker): +def test_hydrocalc_init(hydrocalc): assert hydrocalc From d3f743fed737f67991f497a05aed60cfb222e7d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Sat, 5 Mar 2022 08:19:22 -0500 Subject: [PATCH 42/43] Try to fix tests (try#1) --- gwhat/HydroCalc2.py | 4 +- gwhat/mainwindow.py | 4 +- gwhat/tests/test_hydrocalc.py | 109 ++++++++++++++++------------------ 3 files changed, 55 insertions(+), 62 deletions(-) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index a5e4f3e5f..f373ada9d 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -102,8 +102,8 @@ def __init__(self, datamanager, parent=None): self.brf_eval_widget = BRFManager(parent=self) self.install_tool(self.brf_eval_widget) - self.mrc_eval_widget = MasterRecessionCalcTool(parent=self) - self.install_tool(self.mrc_eval_widget) + # self.mrc_eval_widget = MasterRecessionCalcTool(parent=self) + # self.install_tool(self.mrc_eval_widget) index = self.tools_tabwidget.addTab(self.rechg_eval_widget, 'Recharge') self.tools_tabwidget.setTabToolTip( diff --git a/gwhat/mainwindow.py b/gwhat/mainwindow.py index d741612f5..68462ef04 100644 --- a/gwhat/mainwindow.py +++ b/gwhat/mainwindow.py @@ -120,8 +120,8 @@ def __initUI__(self): # Setup the tab analyse hydrograph. splash.showMessage("Initializing analyse hydrograph...") self.tab_hydrocalc = HydroCalc.WLCalc(self.dmanager) - self.tab_hydrocalc.tools['mrc'].sig_new_mrc.connect( - self.tab_hydrograph.mrc_wl_changed) + # self.tab_hydrocalc.tools['mrc'].sig_new_mrc.connect( + # self.tab_hydrograph.mrc_wl_changed) self.tab_hydrocalc.rechg_eval_widget.sig_new_gluedf.connect( self.tab_hydrograph.glue_wl_changed) diff --git a/gwhat/tests/test_hydrocalc.py b/gwhat/tests/test_hydrocalc.py index 31495d59c..ad6595583 100644 --- a/gwhat/tests/test_hydrocalc.py +++ b/gwhat/tests/test_hydrocalc.py @@ -60,15 +60,8 @@ def datamanager(project): def hydrocalc(datamanager, qtbot): hydrocalc = WLCalc(datamanager) hydrocalc.show() - - yield hydrocalc - - # We need to explicitely close hydrocalc, the datamanager and the project - # to avoid 'Windows fatal exception: access violation' errors in - # other tests. - hydrocalc.close() - datamanager.projet.close() - datamanager.close() + qtbot.addWidget(hydrocalc) + return hydrocalc # ============================================================================= @@ -91,55 +84,55 @@ def test_copy_to_clipboard(hydrocalc, qtbot): assert not QApplication.clipboard().image().isNull() -def test_calc_mrc(hydrocalc, tmp_path, qtbot, mocker): - """ - Test that the tool to calculate the MRC is working as expected. - """ - assert hydrocalc.dformat == 1 # Matplotlib date format - hydrocalc.switch_date_format() - assert hydrocalc.dformat == 0 # Excel date format - - # Select recession periods on the hydrograph. - coordinates = [ - (41384.260416666664, 41414.114583333336), - (41310.385416666664, 41340.604166666664), - (41294.708333333336, 41302.916666666664), - (41274.5625, 41284.635416666664), - (41457.395833333336, 41486.875), - (41440.604166666664, 41447.697916666664), - (41543.958333333336, 41552.541666666664)] - for coord in coordinates: - hydrocalc.tools['mrc'].add_mrcperiod(coord) - - # Calcul the MRC. - mrc_data = hydrocalc.wldset.get_mrc() - assert np.isnan(mrc_data['params']).all() - assert len(mrc_data['peak_indx']) == 0 - assert len(mrc_data['recess']) == 0 - assert len(mrc_data['time']) == 0 - - hydrocalc.tools['mrc'].calculate_mrc() - - mrc_data = hydrocalc.wldset.get_mrc() - assert abs(mrc_data['params'][0] - 0.07004324034418882) < 10**-5 - assert abs(mrc_data['params'][1] - 0.25679183844863535) < 10**-5 - assert len(mrc_data['peak_indx']) == 7 - assert len(mrc_data['recess']) == 343 - assert len(mrc_data['time']) == 343 - assert np.sum(~np.isnan(mrc_data['recess'])) == 123 - - # Save MRC results to file. - outfile = osp.join(tmp_path, 'test_mrc_export') - ffilter = "Text CSV (*.csv)" - qfdialog_patcher = mocker.patch.object( - QFileDialog, - 'getSaveFileName', - return_value=(outfile, ffilter)) - - assert not osp.exists(outfile + '.csv') - hydrocalc.tools['mrc'].save_mrc_tofile() - assert osp.exists(outfile + '.csv') - assert qfdialog_patcher.call_count == 1 +# def test_calc_mrc(hydrocalc, tmp_path, qtbot, mocker): +# """ +# Test that the tool to calculate the MRC is working as expected. +# """ +# assert hydrocalc.dformat == 1 # Matplotlib date format +# hydrocalc.switch_date_format() +# assert hydrocalc.dformat == 0 # Excel date format + +# # Select recession periods on the hydrograph. +# coordinates = [ +# (41384.260416666664, 41414.114583333336), +# (41310.385416666664, 41340.604166666664), +# (41294.708333333336, 41302.916666666664), +# (41274.5625, 41284.635416666664), +# (41457.395833333336, 41486.875), +# (41440.604166666664, 41447.697916666664), +# (41543.958333333336, 41552.541666666664)] +# for coord in coordinates: +# hydrocalc.tools['mrc'].add_mrcperiod(coord) + +# # Calcul the MRC. +# mrc_data = hydrocalc.wldset.get_mrc() +# assert np.isnan(mrc_data['params']).all() +# assert len(mrc_data['peak_indx']) == 0 +# assert len(mrc_data['recess']) == 0 +# assert len(mrc_data['time']) == 0 + +# hydrocalc.tools['mrc'].calculate_mrc() + +# mrc_data = hydrocalc.wldset.get_mrc() +# assert abs(mrc_data['params'][0] - 0.07004324034418882) < 10**-5 +# assert abs(mrc_data['params'][1] - 0.25679183844863535) < 10**-5 +# assert len(mrc_data['peak_indx']) == 7 +# assert len(mrc_data['recess']) == 343 +# assert len(mrc_data['time']) == 343 +# assert np.sum(~np.isnan(mrc_data['recess'])) == 123 + +# # Save MRC results to file. +# outfile = osp.join(tmp_path, 'test_mrc_export') +# ffilter = "Text CSV (*.csv)" +# qfdialog_patcher = mocker.patch.object( +# QFileDialog, +# 'getSaveFileName', +# return_value=(outfile, ffilter)) + +# assert not osp.exists(outfile + '.csv') +# hydrocalc.tools['mrc'].save_mrc_tofile() +# assert osp.exists(outfile + '.csv') +# assert qfdialog_patcher.call_count == 1 if __name__ == "__main__": From ee42fb8c3019c3906f02e7e423f027bd79396688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Sat, 5 Mar 2022 08:58:38 -0500 Subject: [PATCH 43/43] Fix WLCalc __init__ --- gwhat/HydroCalc2.py | 4 +- gwhat/hydrocalc/recession/recession_tool.py | 1 - gwhat/mainwindow.py | 4 +- gwhat/tests/test_hydrocalc.py | 98 ++++++++++----------- 4 files changed, 53 insertions(+), 54 deletions(-) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index f373ada9d..a5e4f3e5f 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -102,8 +102,8 @@ def __init__(self, datamanager, parent=None): self.brf_eval_widget = BRFManager(parent=self) self.install_tool(self.brf_eval_widget) - # self.mrc_eval_widget = MasterRecessionCalcTool(parent=self) - # self.install_tool(self.mrc_eval_widget) + self.mrc_eval_widget = MasterRecessionCalcTool(parent=self) + self.install_tool(self.mrc_eval_widget) index = self.tools_tabwidget.addTab(self.rechg_eval_widget, 'Recharge') self.tools_tabwidget.setTabToolTip( diff --git a/gwhat/hydrocalc/recession/recession_tool.py b/gwhat/hydrocalc/recession/recession_tool.py index 766e88956..d02aa9069 100644 --- a/gwhat/hydrocalc/recession/recession_tool.py +++ b/gwhat/hydrocalc/recession/recession_tool.py @@ -40,7 +40,6 @@ class MasterRecessionCalcTool(WLCalcTool, SaveFileMixin): sig_new_mrc = QSignal() def __init__(self, parent=None): - super().__init__(parent) WLCalcTool.__init__(self, parent) SaveFileMixin.__init__(self) diff --git a/gwhat/mainwindow.py b/gwhat/mainwindow.py index 68462ef04..d741612f5 100644 --- a/gwhat/mainwindow.py +++ b/gwhat/mainwindow.py @@ -120,8 +120,8 @@ def __initUI__(self): # Setup the tab analyse hydrograph. splash.showMessage("Initializing analyse hydrograph...") self.tab_hydrocalc = HydroCalc.WLCalc(self.dmanager) - # self.tab_hydrocalc.tools['mrc'].sig_new_mrc.connect( - # self.tab_hydrograph.mrc_wl_changed) + self.tab_hydrocalc.tools['mrc'].sig_new_mrc.connect( + self.tab_hydrograph.mrc_wl_changed) self.tab_hydrocalc.rechg_eval_widget.sig_new_gluedf.connect( self.tab_hydrograph.glue_wl_changed) diff --git a/gwhat/tests/test_hydrocalc.py b/gwhat/tests/test_hydrocalc.py index ad6595583..da65a81bf 100644 --- a/gwhat/tests/test_hydrocalc.py +++ b/gwhat/tests/test_hydrocalc.py @@ -84,55 +84,55 @@ def test_copy_to_clipboard(hydrocalc, qtbot): assert not QApplication.clipboard().image().isNull() -# def test_calc_mrc(hydrocalc, tmp_path, qtbot, mocker): -# """ -# Test that the tool to calculate the MRC is working as expected. -# """ -# assert hydrocalc.dformat == 1 # Matplotlib date format -# hydrocalc.switch_date_format() -# assert hydrocalc.dformat == 0 # Excel date format - -# # Select recession periods on the hydrograph. -# coordinates = [ -# (41384.260416666664, 41414.114583333336), -# (41310.385416666664, 41340.604166666664), -# (41294.708333333336, 41302.916666666664), -# (41274.5625, 41284.635416666664), -# (41457.395833333336, 41486.875), -# (41440.604166666664, 41447.697916666664), -# (41543.958333333336, 41552.541666666664)] -# for coord in coordinates: -# hydrocalc.tools['mrc'].add_mrcperiod(coord) - -# # Calcul the MRC. -# mrc_data = hydrocalc.wldset.get_mrc() -# assert np.isnan(mrc_data['params']).all() -# assert len(mrc_data['peak_indx']) == 0 -# assert len(mrc_data['recess']) == 0 -# assert len(mrc_data['time']) == 0 - -# hydrocalc.tools['mrc'].calculate_mrc() - -# mrc_data = hydrocalc.wldset.get_mrc() -# assert abs(mrc_data['params'][0] - 0.07004324034418882) < 10**-5 -# assert abs(mrc_data['params'][1] - 0.25679183844863535) < 10**-5 -# assert len(mrc_data['peak_indx']) == 7 -# assert len(mrc_data['recess']) == 343 -# assert len(mrc_data['time']) == 343 -# assert np.sum(~np.isnan(mrc_data['recess'])) == 123 - -# # Save MRC results to file. -# outfile = osp.join(tmp_path, 'test_mrc_export') -# ffilter = "Text CSV (*.csv)" -# qfdialog_patcher = mocker.patch.object( -# QFileDialog, -# 'getSaveFileName', -# return_value=(outfile, ffilter)) - -# assert not osp.exists(outfile + '.csv') -# hydrocalc.tools['mrc'].save_mrc_tofile() -# assert osp.exists(outfile + '.csv') -# assert qfdialog_patcher.call_count == 1 +def test_calc_mrc(hydrocalc, tmp_path, qtbot, mocker): + """ + Test that the tool to calculate the MRC is working as expected. + """ + assert hydrocalc.dformat == 1 # Matplotlib date format + hydrocalc.switch_date_format() + assert hydrocalc.dformat == 0 # Excel date format + + # Select recession periods on the hydrograph. + coordinates = [ + (41384.260416666664, 41414.114583333336), + (41310.385416666664, 41340.604166666664), + (41294.708333333336, 41302.916666666664), + (41274.5625, 41284.635416666664), + (41457.395833333336, 41486.875), + (41440.604166666664, 41447.697916666664), + (41543.958333333336, 41552.541666666664)] + for coord in coordinates: + hydrocalc.tools['mrc'].add_mrcperiod(coord) + + # Calcul the MRC. + mrc_data = hydrocalc.wldset.get_mrc() + assert np.isnan(mrc_data['params']).all() + assert len(mrc_data['peak_indx']) == 0 + assert len(mrc_data['recess']) == 0 + assert len(mrc_data['time']) == 0 + + hydrocalc.tools['mrc'].calculate_mrc() + + mrc_data = hydrocalc.wldset.get_mrc() + assert abs(mrc_data['params'][0] - 0.07004324034418882) < 10**-5 + assert abs(mrc_data['params'][1] - 0.25679183844863535) < 10**-5 + assert len(mrc_data['peak_indx']) == 7 + assert len(mrc_data['recess']) == 343 + assert len(mrc_data['time']) == 343 + assert np.sum(~np.isnan(mrc_data['recess'])) == 123 + + # Save MRC results to file. + outfile = osp.join(tmp_path, 'test_mrc_export') + ffilter = "Text CSV (*.csv)" + qfdialog_patcher = mocker.patch.object( + QFileDialog, + 'getSaveFileName', + return_value=(outfile, ffilter)) + + assert not osp.exists(outfile + '.csv') + hydrocalc.tools['mrc'].save_mrc_tofile() + assert osp.exists(outfile + '.csv') + assert qfdialog_patcher.call_count == 1 if __name__ == "__main__":