AdisTT: new results window, adapted from adists results

dev_dylan
Dylan Jeannin 2026-07-21 14:00:59 +02:00
parent 57f82415bd
commit 9b9202c739
10 changed files with 1238 additions and 18 deletions

View File

@ -53,12 +53,13 @@ class CheckListWindowAdisTS(PamhyrWindow):
def __init__(self, autorun: bool = True, def __init__(self, autorun: bool = True,
study=None, config=None, study=None, config=None,
solver=None, parent=None, mage_rep=None): solver=None, parent=None, mage_rep=None, mode="adists"):
trad = CheckListTranslate() trad = CheckListTranslate()
self._autorun = autorun self._autorun = autorun
self._solver = solver self._solver = solver
self._mage_rep = mage_rep self._mage_rep = mage_rep
self._mode = mode
name = trad[self._pamhyr_name] + " - " + study.name name = trad[self._pamhyr_name] + " - " + study.name
@ -73,6 +74,8 @@ class CheckListWindowAdisTS(PamhyrWindow):
# Add solver to hash computation data # Add solver to hash computation data
self._hash_data.append(self._solver) self._hash_data.append(self._solver)
self._hash_data.append(self._mage_rep)
self._hash_data.append(self._mode)
self._checker_list = ( self._checker_list = (
self._study.checkers() + self._study.checkers() +
@ -169,7 +172,7 @@ class CheckListWindowAdisTS(PamhyrWindow):
if not errors: if not errors:
self.find(QPushButton, "pushButton_ok").setEnabled(True) self.find(QPushButton, "pushButton_ok").setEnabled(True)
if self._autorun: if self._autorun:
self._parent.solver_log_adists(self._solver, self._mage_rep) self.open_solver_log()
self.end() self.end()
self.update_statusbar() self.update_statusbar()
@ -196,5 +199,11 @@ class CheckListWindowAdisTS(PamhyrWindow):
self.end() self.end()
def accept(self): def accept(self):
self._parent.solver_log_adists(self._solver, self._mage_rep) self.open_solver_log()
# self.end() # self.end()
def open_solver_log(self):
if self._mode == "adistt":
self._parent.solver_log_adistt(self._solver, self._mage_rep)
else:
self._parent.solver_log_adists(self._solver, self._mage_rep)

View File

@ -103,6 +103,7 @@ from View.RunSolver.WindowAdisTS import (
from View.CheckList.Window import CheckListWindow from View.CheckList.Window import CheckListWindow
from View.CheckList.WindowAdisTS import CheckListWindowAdisTS from View.CheckList.WindowAdisTS import CheckListWindowAdisTS
from View.Results.WindowAdisTS import ResultsWindowAdisTS from View.Results.WindowAdisTS import ResultsWindowAdisTS
from View.Results.WindowAdisTT import ResultsWindowAdisTT
from View.Results.ReadingResultsDialog import ReadingResultsDialog from View.Results.ReadingResultsDialog import ReadingResultsDialog
from View.Debug.Window import ReplWindow, TimerWindow from View.Debug.Window import ReplWindow, TimerWindow
@ -297,9 +298,9 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
"action_menu_d90": self.open_d90, "action_menu_d90": self.open_d90,
"action_menu_pollutants": self.open_pollutants, "action_menu_pollutants": self.open_pollutants,
"action_menu_run_adists": "action_menu_run_adists":
lambda: self.select_run_solver_adists(mode="adists"), self.select_run_solver_adists,
"action_menu_run_adistt": "action_menu_run_adistt":
lambda: self.select_run_solver_adists(mode="adistt"), self.select_run_solver_adistt,
"action_menu_output_rk": self.open_output_rk_adists, "action_menu_output_rk": self.open_output_rk_adists,
"action_menu_config": self.open_configure, "action_menu_config": self.open_configure,
"action_menu_new": self.open_new_study, "action_menu_new": self.open_new_study,
@ -1570,7 +1571,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
if run.exec(): if run.exec():
self.run_solver(run.solver) self.run_solver(run.solver)
def select_run_solver_adists(self, mode="adists"): def select_run_solver_adists(self):
if self._study is None: if self._study is None:
return return
@ -1586,9 +1587,27 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
parent=self parent=self
) )
if run.exec(): if run.exec():
self.run_solver_adists(run.solver, run.mage_rep, mode=mode) self.run_solver_adists(run.solver, run.mage_rep)
def run_solver_adists(self, solver, mage_rep, mode="adists"): def select_run_solver_adistt(self):
if self._study is None:
return
run = SelectSolverWindowAdisTT(
study=self._study,
config=self.conf,
parent=self
)
if run.exec():
self.run_solver_adistt(run.solver, run.mage_rep)
def run_solver_adists(self, solver, mage_rep):
self._run_solver_adis(solver, mage_rep, mode="adists")
def run_solver_adistt(self, solver, mage_rep):
self._run_solver_adis(solver, mage_rep, mode="adistt")
def _run_solver_adis(self, solver, mage_rep, mode):
if self._study is None: if self._study is None:
return return
@ -1598,7 +1617,8 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
self._study, self._study,
self.conf, self.conf,
solver, solver,
mage_rep mage_rep,
mode,
] ]
): ):
return return
@ -1609,6 +1629,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
solver=solver, solver=solver,
parent=self, parent=self,
mage_rep=mage_rep, mage_rep=mage_rep,
mode=mode,
) )
check.show() check.show()
@ -1644,6 +1665,16 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
) )
sol.show() sol.show()
def solver_log_adistt(self, solver, mage_rep):
sol = SolverLogWindowAdisTT(
study=self._study,
config=self.conf,
solver=solver,
parent=self,
mage_rep=mage_rep,
)
sol.show()
def solver_log(self, solver): def solver_log(self, solver):
sol = SolverLogWindow( sol = SolverLogWindow(
study=self._study, study=self._study,
@ -1763,6 +1794,16 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
) )
def open_solver_results_adists(self, solver, results=None): def open_solver_results_adists(self, solver, results=None):
self._open_solver_results_adis(
solver, ResultsWindowAdisTS, results=results
)
def open_solver_results_adistt(self, solver, results=None):
self._open_solver_results_adis(
solver, ResultsWindowAdisTT, results=results
)
def _open_solver_results_adis(self, solver, window_class, results=None):
def reading_fn(): def reading_fn():
self._tmp_results = results self._tmp_results = results
@ -1804,7 +1845,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
# Windows already opened # Windows already opened
if self.sub_window_exists( if self.sub_window_exists(
ResultsWindowAdisTS, window_class,
data=[ data=[
self._study, self._study,
None, # No config None, # No config
@ -1815,7 +1856,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
): ):
return return
res = ResultsWindowAdisTS( res = window_class(
study=self._study, study=self._study,
solver=solver, solver=solver,
results=results, results=results,

View File

@ -63,6 +63,9 @@ class PlotAdis(PamhyrPlot):
self.label["C"] = _translate("Results", "Concentration") self.label["C"] = _translate("Results", "Concentration")
self.label_max["C"] = _translate("Results", "Max Concentration") self.label_max["C"] = _translate("Results", "Max Concentration")
self.label_min["C"] = _translate("Results", "Min Concentration") self.label_min["C"] = _translate("Results", "Min Concentration")
self.label["T"] = _translate("Results", "Temperature")
self.label_max["T"] = _translate("Results", "Max Temperature")
self.label_min["T"] = _translate("Results", "Min Temperature")
if self._type_pol[pol_id[0]] == -1: # Total if self._type_pol[pol_id[0]] == -1: # Total
self.label["M"] = _translate("Results", "Thickness") self.label["M"] = _translate("Results", "Thickness")
self.label_max["M"] = _translate("Results", "Max Thickness") self.label_max["M"] = _translate("Results", "Max Thickness")
@ -75,6 +78,7 @@ class PlotAdis(PamhyrPlot):
self.label_y = self._available_values_y["unit_"+self._key] self.label_y = self._available_values_y["unit_"+self._key]
self.val_id = {} self.val_id = {}
self.val_id["C"] = 0 self.val_id["C"] = 0
self.val_id["T"] = 0
self.val_id["G"] = 1 self.val_id["G"] = 1
self.val_id["M"] = 2 self.val_id["M"] = 2
self.val_id["D"] = 3 self.val_id["D"] = 3
@ -205,9 +209,12 @@ class PlotAdis_dx(PlotAdis):
self._current_timestamp, "pols" self._current_timestamp, "pols"
)[pol_id][self.val_id[self._key]], )[pol_id][self.val_id[self._key]],
reach.profiles)) reach.profiles))
label = self.label[self._key]
if name != "TEM":
label += (" "+name)
self._lines.append(self.canvas.axes.plot( self._lines.append(self.canvas.axes.plot(
x, y, x, y,
label=self.label[self._key]+" "+name, label=label,
color=self.colors[pol_id % len(self.colors)], color=self.colors[pol_id % len(self.colors)],
**self.plot_default_kargs **self.plot_default_kargs
)[0]) )[0])

View File

@ -0,0 +1,128 @@
# TableAdisTT.py -- Pamhyr
# Copyright (C) 2023-2026 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
import traceback
from tools import timer, trace
from PyQt5.QtGui import (
QKeySequence, QColor
)
from PyQt5.QtCore import (
Qt, QAbstractTableModel, QModelIndex,
QVariant, pyqtSlot, QCoreApplication,
)
from PyQt5.QtWidgets import (
QMessageBox, QUndoCommand, QUndoStack,
QStyledItemDelegate, QLineEdit, QAbstractItemView,
QComboBox,
)
from View.Tools.PamhyrTable import PamhyrTableModel
from View.Results.translate import *
logger = logging.getLogger()
_translate = QCoreApplication.translate
class TableModel(PamhyrTableModel):
def _setup_lst(self):
_river = self._data.river
self._timestamp = min(list(self._data.get("timestamps")))
if self._opt_data == "reach":
self._lst = _river.reachs
elif self._opt_data == "profile":
self._lst = _river.reach(0).profiles
elif self._opt_data == "raw_data":
self._lst = _river.reach(0).profiles
def __init__(self, type_pol, **kwargs):
self._type_pol = type_pol
super(TableModel, self).__init__(**kwargs)
def data(self, index, role=Qt.DisplayRole):
if role != Qt.ItemDataRole.DisplayRole:
return QVariant()
row = index.row()
column = index.column()
if self._opt_data == "reach":
if self._headers[column] == "name":
v = self._lst[row].name
return str(v)
elif self._opt_data == "profile":
if self._headers[column] == "name":
v = self._lst[row].name
return str(v)
elif self._headers[column] == "rk":
v = self._lst[row].rk
return f"{v:.4f}"
elif self._opt_data == "raw_data":
p = self._lst[row]
if self._headers[column] == "name":
if p.name == "":
return f"{p.rk:.4f}"
return f"{p.name}"
tmp_list = self._data.pollutants_list.copy()
tmp_list.remove("total_sediment")
tmp_list2 = self._data.pollutants_list.copy()
for pol in tmp_list:
pol_index = tmp_list2.index(pol)
if self._headers[column] == pol + " Concentration":
v = self._lst[row].get_ts_key(
self._timestamp, "pols")[pol_index][0]
return f"{v:.4f}"
if self._headers[column] == pol + " Temperature":
v = self._lst[row].get_ts_key(
self._timestamp, "pols")[pol_index][0]
return f"{v:.4f}"
if self._headers[column] == pol + " Mass":
if self._type_pol[pol_index] == 7:
m1 = self._lst[row].get_ts_key(
self._timestamp, "pols")[pol_index][1]
m2 = self._lst[row].get_ts_key(
self._timestamp, "pols")[pol_index][2]
m3 = self._lst[row].get_ts_key(
self._timestamp, "pols")[pol_index][3]
v = m1 + m2 + m3
return f"{v:.4f}"
else:
return ""
return QVariant()
def update(self, reach):
_river = self._data.river
if self._opt_data == "reach":
self._lst = _river.reachs
elif self._opt_data == "profile" or self._opt_data == "raw_data":
self._lst = _river.reach(reach).profiles
self.layoutChanged.emit()
@property
def timestamp(self):
return self._timestamp
def set_timestamp(self, timestamp):
self._timestamp = timestamp
self.layoutChanged.emit()

View File

@ -0,0 +1,756 @@
# WindowAdisTT.py -- Pamhyr
# Copyright (C) 2023-2026 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import os
import csv
import logging
from datetime import datetime
from tools import trace, timer, logger_exception
from View.Tools.PamhyrWindow import PamhyrWindow
from PyQt5.QtGui import (
QKeySequence, QIcon, QPixmap,
)
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel,
QCoreApplication, QModelIndex, pyqtSlot,
QItemSelectionModel, QTimer,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QPushButton, QLineEdit,
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
QSlider, QLabel, QWidget, QGridLayout,
)
from View.Tools.Plot.PamhyrCanvas import MplCanvas
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.Results.PlotSedAdis import PlotAdis_dx, PlotAdis_dt
from View.Results.CustomPlot.Plot import CustomPlot
from View.Results.CustomExport.CustomExportAdis import (
CustomExportAdisDialog,
)
from View.Results.TableAdisTT import TableModel
from View.Results.translate import ResultsTranslate
_translate = QCoreApplication.translate
logger = logging.getLogger()
class ResultsWindowAdisTT(PamhyrWindow):
_pamhyr_ui = "ResultsAdisTT"
_pamhyr_name = "Results"
def _path_file(self, filename):
return os.path.abspath(
os.path.join(
os.path.dirname(__file__),
"..", "ui", "ressources", filename
)
)
def __init__(self, study=None, config=None,
solver=None, results=None,
parent=None):
self._solver = solver
self._results = results
self.pollutant_label = ["None"]
pollutants_headers = self._results.pollutants_list.copy()
trad = ResultsTranslate(pollutants_headers)
name = (
trad[self._pamhyr_name] + " - "
+ study.name + " - "
+ self._solver.name
)
super(ResultsWindowAdisTT, self).__init__(
title=name,
study=study,
config=config,
trad=trad,
parent=parent
)
self._hash_data.append(self._solver)
self._hash_data.append(self._results._repertory)
self._hash_data.append(self._results._name)
self._additional_plot = {}
self._current_pol_id = [1]
self._reach_id = 0
self._profile_id = 0
try:
self._timestamps = sorted(list(self._results.get("timestamps")))
self.set_type_pol()
self.setup_slider()
self.setup_table()
self.setup_plots()
self.setup_statusbar()
self.setup_connections()
self.update_table_selection_reach(self._reach_id)
self.update_table_selection_profile(self._profile_id)
except Exception as e:
logger_exception(e)
return
def set_type_pol(self):
self._type_pol = []
self._pol_id_dict = {}
tmp_list = self._results.river.reach(0).profiles
for pol_index in range(self._results.nb_pollutants):
pol_name = self._results.pollutants_list[pol_index]
if pol_name == "total_sediment":
self._type_pol.append(-1)
else:
self._type_pol.append(len(
tmp_list[0].get_ts_key(
self._timestamps[0], "pols")[pol_index])
)
self._pol_id_dict[pol_name] = pol_index
def setup_table(self):
self._table = {}
for t in ["reach", "profile", "raw_data"]:
table = self.find(QTableView, f"tableView_{t}")
self._table[t] = TableModel(
self._type_pol,
table_view=table,
table_headers=self._trad.get_dict(f"table_headers_{t}"),
data=self._results,
undo=self._undo_stack,
opt_data=t,
)
self._table[t]._timestamp = self._timestamps[
self._slider_time.value()]
def setup_slider(self):
self._slider_time = self.find(QSlider, f"horizontalSlider_time")
self._slider_time.setMaximum(len(self._timestamps) - 1)
self._slider_time.setValue(len(self._timestamps) - 1)
self._icon_start = QIcon()
self._icon_start.addPixmap(
QPixmap(self._path_file("media-playback-start.png"))
)
self._icon_pause = QIcon()
self._icon_pause.addPixmap(
QPixmap(self._path_file("media-playback-pause.png"))
)
self._button_play = self.find(QPushButton, f"playButton")
self._button_play.setIcon(self._icon_start)
self._button_back = self.find(QPushButton, f"backButton")
self._button_next = self.find(QPushButton, f"nextButton")
self._button_first = self.find(QPushButton, f"firstButton")
self._button_last = self.find(QPushButton, f"lastButton")
self._timer = QTimer(self)
def setup_plots(self):
self.canvas_cdt = MplCanvas(width=5, height=4, dpi=100)
self.canvas_cdt.setObjectName("canvas_cdt")
self.toolbar_cdt = PamhyrPlotToolbar(
self.canvas_cdt, self, items=[
"home", "move", "zoom", "save",
"iso", "back/forward"
]
)
self.plot_layout_cdt = self.find(
QVBoxLayout, "verticalLayout_concentration_dt")
self.plot_layout_cdt.addWidget(self.toolbar_cdt)
self.plot_layout_cdt.addWidget(self.canvas_cdt)
self.plot_cdt = PlotAdis_dt(
canvas=self.canvas_cdt,
results=self._results,
reach_id=self._reach_id,
profile_id=self._profile_id,
pol_id=self._current_pol_id,
key="T",
type_pol=self._type_pol,
trad=self._trad,
toolbar=self.toolbar_cdt
)
self.plot_cdt.draw()
self.canvas_cdx = MplCanvas(width=5, height=4, dpi=100)
self.canvas_cdx.setObjectName("canvas_cdx")
self.toolbar_cdx = PamhyrPlotToolbar(
self.canvas_cdx, self, items=[
"home", "move", "zoom", "save",
"iso", "back/forward"
]
)
self.plot_layout_cdx = self.find(
QVBoxLayout, "verticalLayout_concentration_dx")
self.plot_layout_cdx.addWidget(self.toolbar_cdx)
self.plot_layout_cdx.addWidget(self.canvas_cdx)
self.plot_cdx = PlotAdis_dx(
canvas=self.canvas_cdx,
results=self._results,
reach_id=self._reach_id,
profile_id=self._profile_id,
pol_id=self._current_pol_id,
key="T",
type_pol=self._type_pol,
trad=self._trad,
toolbar=self.toolbar_cdx
)
self.plot_cdx.draw()
# The AdisTT window only displays temperature plots. The code below
# belongs to the sediment/pollutant result window and its layouts are
# intentionally absent from ResultsAdisTT.ui.
return
self.canvas_mdx = MplCanvas(width=5, height=4, dpi=100)
self.canvas_mdx.setObjectName("canvas_mdx")
self.toolbar_mdx = PamhyrPlotToolbar(
self.canvas_mdx, self, items=[
"home", "move", "zoom", "save",
"iso", "back/forward"
]
)
self.plot_layout_mdx = self.find(
QVBoxLayout, "verticalLayout_mass_dx2")
self.plot_layout_mdx.addWidget(self.toolbar_mdx)
self.plot_layout_mdx.addWidget(self.canvas_mdx)
self.plot_mdx = PlotAdis_dx(
canvas=self.canvas_mdx,
results=self._results,
reach_id=self._reach_id,
profile_id=self._profile_id,
pol_id=self._current_pol_id,
key="M",
type_pol=self._type_pol,
trad=self._trad,
toolbar=self.toolbar_mdx
)
self.plot_mdx.draw()
self.canvas_mdt = MplCanvas(width=5, height=4, dpi=100)
self.canvas_mdt.setObjectName("canvas_mdt")
self.toolbar_mdt = PamhyrPlotToolbar(
self.canvas_mdt, self, items=[
"home", "move", "zoom", "save",
"iso", "back/forward"
]
)
self.plot_layout_mdt = self.find(
QVBoxLayout, "verticalLayout_mass_dt2")
self.plot_layout_mdt.addWidget(self.toolbar_mdt)
self.plot_layout_mdt.addWidget(self.canvas_mdt)
self.plot_mdt = PlotAdis_dt(
canvas=self.canvas_mdt,
results=self._results,
reach_id=self._reach_id,
profile_id=self._profile_id,
pol_id=self._current_pol_id,
key="M",
type_pol=self._type_pol,
trad=self._trad,
toolbar=self.toolbar_mdt
)
self.plot_mdt.draw()
# Thickness
self.canvas_tdx = MplCanvas(width=5, height=4, dpi=100)
self.canvas_tdx.setObjectName("canvas_tdx")
self.toolbar_tdx = PamhyrPlotToolbar(
self.canvas_tdx, self, items=[
"home", "move", "zoom", "save",
"iso", "back/forward"
]
)
self.plot_layout_tdx = self.find(
QVBoxLayout, "verticalLayout_tdx")
self.plot_layout_tdx.addWidget(self.toolbar_tdx)
self.plot_layout_tdx.addWidget(self.canvas_tdx)
self.plot_tdx = PlotAdis_dx(
canvas=self.canvas_tdx,
results=self._results,
reach_id=self._reach_id,
profile_id=self._profile_id,
pol_id=[0],
key="M",
type_pol=self._type_pol,
trad=self._trad,
toolbar=self.toolbar_tdx
)
self.plot_tdx.draw()
self.canvas_tdt = MplCanvas(width=5, height=4, dpi=100)
self.canvas_tdt.setObjectName("canvas_tdt")
self.toolbar_tdt = PamhyrPlotToolbar(
self.canvas_tdt, self, items=[
"home", "move", "zoom", "save",
"iso", "back/forward"
]
)
self.plot_layout_tdt = self.find(
QVBoxLayout, "verticalLayout_tdt")
self.plot_layout_tdt.addWidget(self.toolbar_tdt)
self.plot_layout_tdt.addWidget(self.canvas_tdt)
self.plot_tdt = PlotAdis_dt(
canvas=self.canvas_tdt,
results=self._results,
reach_id=self._reach_id,
profile_id=self._profile_id,
pol_id=[0],
key="M",
type_pol=self._type_pol,
trad=self._trad,
toolbar=self.toolbar_tdt
)
self.plot_tdt.draw()
def closeEvent(self, event):
try:
self._timer.stop()
except Exception as e:
logger_exception(e)
super(ResultsWindowAdisTT, self).closeEvent(event)
def _compute_status_label(self):
return (self.text_bief() + " | " +
self.text_profile() + " | " +
self.text_time())
def text_bief(self):
# Reach
table = self.find(QTableView, f"tableView_reach")
indexes = table.selectedIndexes()
if len(indexes) == 0:
reach = self._study.river.enable_edges()[0]
else:
reach = self._study.river.enable_edges()[indexes[0].row()]
return f"{self._trad['reach']}: {reach.name}"
def text_profile(self):
# Reach
table = self.find(QTableView, f"tableView_reach")
indexes = table.selectedIndexes()
if len(indexes) == 0:
reach = self._study.river.enable_edges()[0]
else:
reach = self._study.river.enable_edges()[indexes[0].row()]
# Profile
table = self.find(QTableView, f"tableView_profile")
indexes = table.selectedIndexes()
if len(indexes) == 0:
profile = reach.reach.profile(0)
else:
profile = reach.reach.profile(indexes[0].row())
pname = profile.name if profile.name != "" else profile.rk
return f"{self._trad['cross_section']}: {pname}"
def text_time(self):
# Timestamp
ts = self._timestamps[self._slider_time.value()]
t0 = datetime.fromtimestamp(0)
fts = str(
datetime.fromtimestamp(ts) - t0
)
fts = str(
datetime.fromtimestamp(ts) - t0
)
fts.replace("days", _translate("Results", "days"))\
.replace("day", _translate("Results", "day"))
return f"{self._trad['time']} : {fts} ({ts} sec)"
def text_pollutant(self):
# Pollutant
table = self.find(QTableView, f"tableView_pollutants")
indexes = table.selectedIndexes()
if len(indexes) != 0:
self.pollutant_label = [
self._results.pollutants_list[i.row()+1] for i in indexes
]
return (f"Pollutant: {', '.join(self.pollutant_label)}")
def setup_statusbar(self):
txt = self._compute_status_label()
self._status_label = QLabel(txt)
self.statusbar.addPermanentWidget(self._status_label)
def update_statusbar(self):
txt = self._compute_status_label()
self._status_label.setText(txt)
def setup_connections(self):
# Action
actions = {
"action_reload": self._reload,
# TODO "action_add": self._add_custom_plot,
"action_export": self.export,
}
self.find(QAction, "action_add").setEnabled(False)
for action in actions:
self.find(QAction, action).triggered.connect(
actions[action]
)
# Table and Plot
fun = {
"reach": self._set_current_reach,
"profile": self._set_current_profile,
"raw_data": self._set_current_profile_raw_data,
}
for t in ["reach", "profile"]:
table = self.find(QTableView, f"tableView_{t}")
table.selectionModel()\
.selectionChanged\
.connect(fun[t])
self._table[t].dataChanged.connect(fun[t])
self._slider_time.valueChanged.connect(self._set_current_timestamp)
self._button_play.setChecked(False)
self._button_play.clicked.connect(self._pause)
self._button_back.clicked.connect(self._back)
self._button_next.clicked.connect(self._next)
self._button_first.clicked.connect(self._first)
self._button_last.clicked.connect(self._last)
self._timer.timeout.connect(self._update_slider)
def update_table_selection_reach(self, ind):
table = self.find(QTableView, f"tableView_reach")
selectionModel = table.selectionModel()
index = table.model().index(ind, 0)
selectionModel.select(
index,
QItemSelectionModel.Rows |
QItemSelectionModel.ClearAndSelect |
QItemSelectionModel.Select
)
table.scrollTo(index)
self._table["profile"].update(ind)
self._table["raw_data"].update(ind)
def update_table_selection_profile(self, ind):
for t in ["profile"]:
table = self.find(QTableView, f"tableView_{t}")
selectionModel = table.selectionModel()
index = table.model().index(ind, 0)
selectionModel.select(
index,
QItemSelectionModel.Rows |
QItemSelectionModel.ClearAndSelect |
QItemSelectionModel.Select
)
table.scrollTo(index)
def update_table_selection_pol(self, ind):
for t in ["pollutants"]:
table = self.find(QTableView, f"tableView_{t}")
selectionModel = table.selectionModel()
index = table.model().index(ind[0] - 1, 0)
selectionModel.select(
index,
QItemSelectionModel.Rows |
QItemSelectionModel.ClearAndSelect |
QItemSelectionModel.Select
)
table.scrollTo(index)
def update(self, reach_id=None, profile_id=None,
pol_id=None, timestamp=None):
if reach_id is not None:
self._reach_id = reach_id
self.plot_cdt.set_reach(reach_id)
self.plot_cdx.set_reach(reach_id)
self.update_table_selection_reach(reach_id)
self.update_table_selection_profile(0)
if profile_id is not None:
self._profile_id = profile_id
self.plot_cdt.set_profile(profile_id)
self.plot_cdx.set_profile(profile_id)
self.update_table_selection_profile(profile_id)
if pol_id is not None:
self._current_pol_id = [p+1 for p in pol_id] # rm total_sediment
self.plot_cdt.set_pollutant(self._current_pol_id)
self.plot_cdx.set_pollutant(self._current_pol_id)
if timestamp is not None:
self.plot_cdt.set_timestamp(timestamp)
self.plot_cdx.set_timestamp(timestamp)
self._table["raw_data"].set_timestamp(timestamp)
self.update_statusbar()
def _get_current_timestamp(self):
return self._timestamps[
self._slider_time.value()
]
def _set_current_reach(self):
table = self.find(QTableView, f"tableView_reach")
indexes = table.selectedIndexes()
if len(indexes) == 0:
return
self.update(reach_id=indexes[0].row())
def _set_current_profile(self):
table = self.find(QTableView, f"tableView_profile")
indexes = table.selectedIndexes()
if len(indexes) == 0:
return
self.update(profile_id=indexes[0].row())
def _set_current_pol(self):
table = self.find(QTableView, f"tableView_pollutants")
indexes = table.selectedIndexes()
rows = [i.row() for i in indexes]
if len(indexes) == 0:
return
self.update(pol_id=rows)
def _set_current_profile_raw_data(self):
return
def _set_current_timestamp(self):
timestamp = self._timestamps[self._slider_time.value()]
self.update(timestamp=timestamp)
def _reload_plots(self):
self.plot_cdt.results = self._results
self.plot_cdx.results = self._results
self.plot_cdt.draw()
self.plot_cdx.draw()
def _reload_slider(self):
self._slider_time = self.find(QSlider, f"horizontalSlider_time")
self._slider_time.setMaximum(len(self._timestamps) - 1)
self._slider_time.setValue(len(self._timestamps) - 1)
def _reload(self):
logger.debug("Reload results...")
self._results = self._results.reload()
self._timestamps = sorted(list(self._results.get("timestamps")))
self._reload_plots()
self._reload_slider()
def _copy(self):
# focus on raw data table
if self.find(QTabWidget, f"tabWidget").currentIndex() == 0:
table = self.find(QTableView, f"tableView_raw_data")
model = self._table["raw_data"]
coltext = []
for index in sorted(table.selectionModel().selectedRows()):
row = index.row()
rowtext = []
for column in range(model.columnCount()):
i = model.createIndex(row, column)
try:
rowtext.append(str(model.data(i)))
except AttributeError:
rowtext.append("")
coltext.append(rowtext)
self.copyTableIntoClipboard(coltext)
else:
logger.info("TODO: copy")
def _paste(self):
logger.info("TODO: paste")
def _undo(self):
self._table.undo()
def _redo(self):
self._table.redo()
# play / pause buttons
def _update_slider(self):
if self._slider_time.value() == self._slider_time.maximum():
self._slider_time.setValue(self._slider_time.minimum())
else:
self._slider_time.setValue(self._slider_time.value()+1)
def _next(self):
self._slider_time.setValue(self._slider_time.value()+1)
def _back(self):
self._slider_time.setValue(self._slider_time.value()-1)
def _first(self):
self._slider_time.setValue(self._slider_time.minimum())
def _last(self):
self._slider_time.setValue(self._slider_time.maximum())
def _pause(self):
if self._button_play.isChecked():
self._button_next.setEnabled(False)
self._button_back.setEnabled(False)
self._button_first.setEnabled(False)
self._button_last.setEnabled(False)
self._timer.start(100)
self._button_play.setIcon(self._icon_pause)
else:
self._timer.stop()
self._button_next.setEnabled(True)
self._button_back.setEnabled(True)
self._button_first.setEnabled(True)
self._button_last.setEnabled(True)
self._button_play.setIcon(self._icon_start)
def export(self):
pols = self._results.pollutants_list.copy()
dlg = CustomExportAdisDialog(pollutants=pols,
parent=self)
if dlg.exec():
x, y, pol = dlg.value
else:
return
self.file_dialog(
select_file="AnyFile",
callback=lambda f: self.export_to(f[0], x, y, pol),
default_suffix=".csv",
file_filter=["CSV (*.csv)"],
)
def export_to(self, filename, x, y, pol):
timestamps = sorted(self._results.get("timestamps"))
reach = self._results.river.reachs[self._reach_id]
first_line = [f"Study: {self._results.study.name}",
f"Reach: {reach.name}"]
if x == "rk":
timestamp = self._get_current_timestamp()
first_line.append(f"Time: {timestamp}s")
val_dict = self._export_rk(timestamp, y, pol)
elif x == "time":
profile = reach.profile(self._profile_id)
pname = profile.name if profile.name != "" else profile.rk
first_line.append(f"Profile: {pname}")
val_dict = self._export_time(self._profile_id, y, pol)
with open(filename, 'w', newline='') as csvfile:
writer = csv.writer(csvfile, delimiter=',',
quotechar='|', quoting=csv.QUOTE_MINIMAL)
dict_x = self._trad.get_dict("values_x")
header = []
writer.writerow(first_line)
for text in val_dict.keys():
header.append(text)
writer.writerow(header)
for row in range(len(val_dict[dict_x[x]])):
line = []
for var in val_dict.keys():
line.append(val_dict[var][row])
writer.writerow(line)
def _export_rk(self, timestamp, y, pol):
reach = self._results.river.reachs[self._reach_id]
dict_x = self._trad.get_dict("values_x")
dict_y = self._trad.get_dict("values_y_pol")
my_dict = {}
my_dict[dict_x["rk"]] = reach.geometry.get_rk()
val_id = {}
val_id["unit_C"] = 0
val_id["unit_M"] = 2
val_id["unit_thickness"] = 2
for unit in y:
if unit == "unit_thickness":
pol_id = 0
else:
pol_id = self._pol_id_dict[pol]
if unit == "unit_M" and self._type_pol[pol_id] == 1:
my_dict[dict_y[unit]] = [0.0]*len(reach.profiles)
else:
my_dict[dict_y[unit]] = list(map(lambda p:
p.get_ts_key(
timestamp, "pols"
)[pol_id][val_id[unit]],
reach.profiles))
return my_dict
def _export_time(self, profile, y, pol):
reach = self._results.river.reachs[self._reach_id]
profile = reach.profile(profile)
ts = list(self._results.get("timestamps"))
ts.sort()
dict_x = self._trad.get_dict("values_x")
dict_y = self._trad.get_dict("values_y_pol")
my_dict = {}
my_dict[dict_x["time"]] = ts
val_id = {}
val_id["unit_C"] = 0
val_id["unit_M"] = 2
val_id["unit_thickness"] = 2
for unit in y:
if unit == "unit_thickness":
pol_id = 0
else:
pol_id = self._pol_id_dict[pol]
if unit == "unit_M" and self._type_pol[pol_id] == 1:
my_dict[dict_y[unit]] = [0.0]*len(ts)
else:
my_dict[dict_y[unit]] = list(map(lambda data_el:
data_el[pol_id][val_id[unit]],
profile.get_key("pols")
))
return my_dict

View File

@ -121,8 +121,11 @@ class ResultsTranslate(MainTranslate):
} }
tmp = self._sub_dict["table_headers_raw_data"] tmp = self._sub_dict["table_headers_raw_data"]
for pol in self.pollutants: for pol in self.pollutants:
tmp[pol + " Concentration"] = pol + "\n Concentration" if pol == "TEM":
tmp[pol + " Mass"] = pol + "\n Mass" tmp[pol + " Temperature"] = "Temperature (°C)"
else:
tmp[pol + " Concentration"] = pol + "\n Concentration"
tmp[pol + " Mass"] = pol + "\n Mass"
else: else:
self._sub_dict["table_headers_raw_data"] = { self._sub_dict["table_headers_raw_data"] = {
"name": _translate("Results", "Profile"), "name": _translate("Results", "Profile"),
@ -146,6 +149,7 @@ class ResultsTranslate(MainTranslate):
self._sub_dict["values_y_pol"] = { self._sub_dict["values_y_pol"] = {
"unit_C": self._dict["unit_concentration"], "unit_C": self._dict["unit_concentration"],
"unit_T": self._dict["unit_temperature"],
"unit_M": self._dict["unit_mass"], "unit_M": self._dict["unit_mass"],
"unit_thickness": self._dict["unit_thickness"], "unit_thickness": self._dict["unit_thickness"],
} }

View File

@ -83,7 +83,7 @@ class SelectSolverWindowAdisTS(PamhyrDialog):
# solvers = self._config.solvers # solvers = self._config.solvers
# solvers mage # solvers mage
solvers = list(filter( solvers = list(filter(
lambda x: "mage" not in x._type and "rubar" not in x._type, lambda x: "adists" in x._type.lower(),
self._config.solvers self._config.solvers
)) ))
solvers_name = list( solvers_name = list(
@ -94,7 +94,8 @@ class SelectSolverWindowAdisTS(PamhyrDialog):
) )
solvers_mage = list(filter( solvers_mage = list(filter(
lambda x: "mage" or "rubar" in x._type.lower(), lambda x: ("mage" in x._type.lower() or
"rubar" in x._type.lower()),
self._config.solvers self._config.solvers
)) ))
solvers_mage_names = list(map(lambda x: x._name, solvers_mage)) solvers_mage_names = list(map(lambda x: x._name, solvers_mage))

View File

@ -83,7 +83,7 @@ class SelectSolverWindowAdisTT(PamhyrDialog):
# solvers = self._config.solvers # solvers = self._config.solvers
# solvers mage # solvers mage
solvers = list(filter( solvers = list(filter(
lambda x: "mage" not in x._type and "rubar" not in x._type, lambda x: "adistt" in x._type.lower(),
self._config.solvers self._config.solvers
)) ))
solvers_name = list( solvers_name = list(
@ -94,7 +94,8 @@ class SelectSolverWindowAdisTT(PamhyrDialog):
) )
solvers_mage = list(filter( solvers_mage = list(filter(
lambda x: "mage" or "rubar" in x._type.lower(), lambda x: ("mage" in x._type.lower() or
"rubar" in x._type.lower()),
self._config.solvers self._config.solvers
)) ))
solvers_mage_names = list(map(lambda x: x._name, solvers_mage)) solvers_mage_names = list(map(lambda x: x._name, solvers_mage))

View File

@ -26,6 +26,7 @@ class MplCanvas(FigureCanvasQTAgg):
layout='constrained', layout='constrained',
) )
super(MplCanvas, self).__init__(fig) super(MplCanvas, self).__init__(fig)
self.setMinimumSize(1, 1)
self.axes = fig.add_subplot(111) self.axes = fig.add_subplot(111)
self.axes.format_coord = lambda x, y: ( self.axes.format_coord = lambda x, y: (
@ -38,6 +39,13 @@ class MplCanvas(FigureCanvasQTAgg):
self.axes.spines[['top', 'right']].set_color('none') self.axes.spines[['top', 'right']].set_color('none')
self.add_arrows() self.add_arrows()
def resizeEvent(self, event):
"""Ignore transient invalid sizes produced by hidden Qt tabs."""
if self.width() <= 0 or self.height() <= 0:
return
super(MplCanvas, self).resizeEvent(event)
def add_arrows(self): def add_arrows(self):
al = 8. al = 8.
arrowprops = dict( arrowprops = dict(

View File

@ -0,0 +1,265 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1410</width>
<height>821</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<property name="locale">
<locale language="English" country="Europe"/>
</property>
<widget class="QWidget" name="centralwidget">
<property name="minimumSize">
<size>
<width>874</width>
<height>0</height>
</size>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="0">
<widget class="QSplitter" name="splitter_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QSplitter" name="splitter_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QTableView" name="tableView_reach"/>
<widget class="QWidget" name="layoutWidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTableView" name="tableView_profile"/>
</item>
</layout>
</widget>
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QGridLayout" name="gridLayout_2">
<item row="4" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="firstButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>ressources/media-skip-backward.png</normaloff>ressources/media-skip-backward.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="backButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>ressources/media-seek-backward.png</normaloff>ressources/media-seek-backward.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="playButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>ressources/player_play.png</normaloff>
<normalon>ressources/player_pause.png</normalon>ressources/player_play.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="nextButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>ressources/media-seek-forward.png</normaloff>ressources/media-seek-forward.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="lastButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>ressources/media-skip-forward.png</normaloff>ressources/media-skip-forward.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="horizontalSlider_time">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QTabWidget" name="tabWidget">
<property name="enabled">
<bool>true</bool>
</property>
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="tab_4">
<attribute name="title">
<string>Raw data</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4"/>
</item>
<item>
<widget class="QTableView" name="tableView_raw_data"/>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="Temperature">
<property name="enabled">
<bool>true</bool>
</property>
<attribute name="title">
<string>Temperature</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QTabWidget" name="tabWidget_c">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_cdx">
<attribute name="title">
<string>Temperature dx</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_17">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_concentration_dx"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_cdt">
<attribute name="title">
<string>Temperature dt</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_18">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_concentration_dt"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1410</width>
<height>20</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBar">
<property name="enabled">
<bool>true</bool>
</property>
<property name="windowTitle">
<string>toolBar</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="action_add"/>
<addaction name="action_export"/>
<addaction name="action_reload"/>
</widget>
<action name="action_add">
<property name="icon">
<iconset>
<normaloff>ressources/add.png</normaloff>ressources/add.png</iconset>
</property>
<property name="text">
<string>Add</string>
</property>
<property name="toolTip">
<string>Add custom visualization</string>
</property>
</action>
<action name="action_reload">
<property name="icon">
<iconset>
<normaloff>ressources/reload.png</normaloff>ressources/reload.png</iconset>
</property>
<property name="text">
<string>Reload</string>
</property>
</action>
<action name="action_export">
<property name="icon">
<iconset>
<normaloff>ressources/export.png</normaloff>ressources/export.png</iconset>
</property>
<property name="text">
<string>Export</string>
</property>
<property name="toolTip">
<string>Export raw data</string>
</property>
<property name="shortcut">
<string>Ctrl+E</string>
</property>
</action>
</widget>
<resources/>
<connections/>
<buttongroups>
<buttongroup name="buttonGroup"/>
<buttongroup name="buttonGroup_2"/>
</buttongroups>
</ui>