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,
study=None, config=None,
solver=None, parent=None, mage_rep=None):
solver=None, parent=None, mage_rep=None, mode="adists"):
trad = CheckListTranslate()
self._autorun = autorun
self._solver = solver
self._mage_rep = mage_rep
self._mode = mode
name = trad[self._pamhyr_name] + " - " + study.name
@ -73,6 +74,8 @@ class CheckListWindowAdisTS(PamhyrWindow):
# Add solver to hash computation data
self._hash_data.append(self._solver)
self._hash_data.append(self._mage_rep)
self._hash_data.append(self._mode)
self._checker_list = (
self._study.checkers() +
@ -169,7 +172,7 @@ class CheckListWindowAdisTS(PamhyrWindow):
if not errors:
self.find(QPushButton, "pushButton_ok").setEnabled(True)
if self._autorun:
self._parent.solver_log_adists(self._solver, self._mage_rep)
self.open_solver_log()
self.end()
self.update_statusbar()
@ -196,5 +199,11 @@ class CheckListWindowAdisTS(PamhyrWindow):
self.end()
def accept(self):
self._parent.solver_log_adists(self._solver, self._mage_rep)
self.open_solver_log()
# 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.WindowAdisTS import CheckListWindowAdisTS
from View.Results.WindowAdisTS import ResultsWindowAdisTS
from View.Results.WindowAdisTT import ResultsWindowAdisTT
from View.Results.ReadingResultsDialog import ReadingResultsDialog
from View.Debug.Window import ReplWindow, TimerWindow
@ -297,9 +298,9 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
"action_menu_d90": self.open_d90,
"action_menu_pollutants": self.open_pollutants,
"action_menu_run_adists":
lambda: self.select_run_solver_adists(mode="adists"),
self.select_run_solver_adists,
"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_config": self.open_configure,
"action_menu_new": self.open_new_study,
@ -1570,7 +1571,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
if run.exec():
self.run_solver(run.solver)
def select_run_solver_adists(self, mode="adists"):
def select_run_solver_adists(self):
if self._study is None:
return
@ -1586,9 +1587,27 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
parent=self
)
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:
return
@ -1598,7 +1617,8 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
self._study,
self.conf,
solver,
mage_rep
mage_rep,
mode,
]
):
return
@ -1609,6 +1629,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
solver=solver,
parent=self,
mage_rep=mage_rep,
mode=mode,
)
check.show()
@ -1644,6 +1665,16 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
)
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):
sol = SolverLogWindow(
study=self._study,
@ -1763,6 +1794,16 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
)
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():
self._tmp_results = results
@ -1804,7 +1845,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
# Windows already opened
if self.sub_window_exists(
ResultsWindowAdisTS,
window_class,
data=[
self._study,
None, # No config
@ -1815,7 +1856,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
):
return
res = ResultsWindowAdisTS(
res = window_class(
study=self._study,
solver=solver,
results=results,

View File

@ -63,6 +63,9 @@ class PlotAdis(PamhyrPlot):
self.label["C"] = _translate("Results", "Concentration")
self.label_max["C"] = _translate("Results", "Max 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
self.label["M"] = _translate("Results", "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.val_id = {}
self.val_id["C"] = 0
self.val_id["T"] = 0
self.val_id["G"] = 1
self.val_id["M"] = 2
self.val_id["D"] = 3
@ -205,9 +209,12 @@ class PlotAdis_dx(PlotAdis):
self._current_timestamp, "pols"
)[pol_id][self.val_id[self._key]],
reach.profiles))
label = self.label[self._key]
if name != "TEM":
label += (" "+name)
self._lines.append(self.canvas.axes.plot(
x, y,
label=self.label[self._key]+" "+name,
label=label,
color=self.colors[pol_id % len(self.colors)],
**self.plot_default_kargs
)[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,6 +121,9 @@ class ResultsTranslate(MainTranslate):
}
tmp = self._sub_dict["table_headers_raw_data"]
for pol in self.pollutants:
if pol == "TEM":
tmp[pol + " Temperature"] = "Temperature (°C)"
else:
tmp[pol + " Concentration"] = pol + "\n Concentration"
tmp[pol + " Mass"] = pol + "\n Mass"
else:
@ -146,6 +149,7 @@ class ResultsTranslate(MainTranslate):
self._sub_dict["values_y_pol"] = {
"unit_C": self._dict["unit_concentration"],
"unit_T": self._dict["unit_temperature"],
"unit_M": self._dict["unit_mass"],
"unit_thickness": self._dict["unit_thickness"],
}

View File

@ -83,7 +83,7 @@ class SelectSolverWindowAdisTS(PamhyrDialog):
# solvers = self._config.solvers
# solvers mage
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
))
solvers_name = list(
@ -94,7 +94,8 @@ class SelectSolverWindowAdisTS(PamhyrDialog):
)
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
))
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 mage
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
))
solvers_name = list(
@ -94,7 +94,8 @@ class SelectSolverWindowAdisTT(PamhyrDialog):
)
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
))
solvers_mage_names = list(map(lambda x: x._name, solvers_mage))

View File

@ -26,6 +26,7 @@ class MplCanvas(FigureCanvasQTAgg):
layout='constrained',
)
super(MplCanvas, self).__init__(fig)
self.setMinimumSize(1, 1)
self.axes = fig.add_subplot(111)
self.axes.format_coord = lambda x, y: (
@ -38,6 +39,13 @@ class MplCanvas(FigureCanvasQTAgg):
self.axes.spines[['top', 'right']].set_color('none')
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):
al = 8.
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>