diff --git a/src/View/CheckList/WindowAdisTS.py b/src/View/CheckList/WindowAdisTS.py
index b0780801..bc95fb5b 100644
--- a/src/View/CheckList/WindowAdisTS.py
+++ b/src/View/CheckList/WindowAdisTS.py
@@ -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)
diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py
index 2243e8e8..b855fef7 100644
--- a/src/View/MainWindow.py
+++ b/src/View/MainWindow.py
@@ -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,
diff --git a/src/View/Results/PlotSedAdis.py b/src/View/Results/PlotSedAdis.py
index c22c3ad5..4f6f4272 100644
--- a/src/View/Results/PlotSedAdis.py
+++ b/src/View/Results/PlotSedAdis.py
@@ -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])
diff --git a/src/View/Results/TableAdisTT.py b/src/View/Results/TableAdisTT.py
new file mode 100644
index 00000000..a1956ad8
--- /dev/null
+++ b/src/View/Results/TableAdisTT.py
@@ -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 .
+
+# -*- 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()
diff --git a/src/View/Results/WindowAdisTT.py b/src/View/Results/WindowAdisTT.py
new file mode 100644
index 00000000..bdb73787
--- /dev/null
+++ b/src/View/Results/WindowAdisTT.py
@@ -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 .
+
+# -*- 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
diff --git a/src/View/Results/translate.py b/src/View/Results/translate.py
index 6e6b855e..be240b53 100644
--- a/src/View/Results/translate.py
+++ b/src/View/Results/translate.py
@@ -121,8 +121,11 @@ class ResultsTranslate(MainTranslate):
}
tmp = self._sub_dict["table_headers_raw_data"]
for pol in self.pollutants:
- tmp[pol + " Concentration"] = pol + "\n Concentration"
- tmp[pol + " Mass"] = pol + "\n Mass"
+ if pol == "TEM":
+ tmp[pol + " Temperature"] = "Temperature (°C)"
+ else:
+ tmp[pol + " Concentration"] = pol + "\n Concentration"
+ tmp[pol + " Mass"] = pol + "\n Mass"
else:
self._sub_dict["table_headers_raw_data"] = {
"name": _translate("Results", "Profile"),
@@ -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"],
}
diff --git a/src/View/RunSolver/WindowAdisTS.py b/src/View/RunSolver/WindowAdisTS.py
index a768e4d6..b2cafd73 100644
--- a/src/View/RunSolver/WindowAdisTS.py
+++ b/src/View/RunSolver/WindowAdisTS.py
@@ -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))
diff --git a/src/View/RunSolver/WindowAdisTT.py b/src/View/RunSolver/WindowAdisTT.py
index 4fc15c82..b2038de1 100644
--- a/src/View/RunSolver/WindowAdisTT.py
+++ b/src/View/RunSolver/WindowAdisTT.py
@@ -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))
diff --git a/src/View/Tools/Plot/PamhyrCanvas.py b/src/View/Tools/Plot/PamhyrCanvas.py
index cebb1d5c..a27505db 100644
--- a/src/View/Tools/Plot/PamhyrCanvas.py
+++ b/src/View/Tools/Plot/PamhyrCanvas.py
@@ -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(
diff --git a/src/View/ui/ResultsAdisTT.ui b/src/View/ui/ResultsAdisTT.ui
new file mode 100644
index 00000000..69dd6188
--- /dev/null
+++ b/src/View/ui/ResultsAdisTT.ui
@@ -0,0 +1,265 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 1410
+ 821
+
+
+
+ MainWindow
+
+
+
+
+
+
+
+ 874
+ 0
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ Qt::Vertical
+
+
+
+
+
-
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+
+
+
+ ressources/media-skip-backward.pngressources/media-skip-backward.png
+
+
+
+ -
+
+
+
+
+
+
+ ressources/media-seek-backward.pngressources/media-seek-backward.png
+
+
+
+ -
+
+
+
+
+
+
+ ressources/player_play.png
+ ressources/player_pause.pngressources/player_play.png
+
+
+ true
+
+
+ true
+
+
+
+ -
+
+
+
+
+
+
+ ressources/media-seek-forward.pngressources/media-seek-forward.png
+
+
+
+ -
+
+
+
+
+
+
+ ressources/media-skip-forward.pngressources/media-skip-forward.png
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+
+
+ -
+
+
+ true
+
+
+ 1
+
+
+
+ Raw data
+
+
+
-
+
+
-
+
+
+ -
+
+
+
+
+
+
+
+
+ true
+
+
+ Temperature
+
+
+ -
+
+
-
+
+
+ 0
+
+
+
+ Temperature dx
+
+
+
-
+
+
+
+
+
+
+ Temperature dt
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ toolBar
+
+
+ TopToolBarArea
+
+
+ false
+
+
+
+
+
+
+
+
+ ressources/add.pngressources/add.png
+
+
+ Add
+
+
+ Add custom visualization
+
+
+
+
+
+ ressources/reload.pngressources/reload.png
+
+
+ Reload
+
+
+
+
+
+ ressources/export.pngressources/export.png
+
+
+ Export
+
+
+ Export raw data
+
+
+ Ctrl+E
+
+
+
+
+
+
+
+
+
+