Skip to content
Snippets Groups Projects
icbktransitionuim.py 11.4 KiB
Newer Older
import datetime
import warnings
import pyqtgraph as pg
import scipy.optimize.optimize
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QColor
from PyQt5.QtCore import *
import pandas as pd
import pytz
from pandas.api.types import is_numeric_dtype
from pyqtgraph.GraphicsScene.mouseEvents import MouseClickEvent

import utils
from config import Config
from dataprovider.exploprovider import ExploProvider
from uim.explouim import ExploUim
from gui.ui_mainwindow import Ui_MainWindow
from gui.icbktransition_dialog import IcbktransitionDialog
from cfatools.logreader.dataset import DatasetReader


class IcbktransitionUim:

    # "Variables" tableWidget columns identifiers.
    ID_COL = 0
    NAME_COL = 1
    START_COL = 2
    END_COL = 3
    HEIGHT_COL = 4

    PROBES_COLORS = {"C1": QColor(228, 26, 28),  # Red
                     "C2": QColor(55, 126, 184),  # Blue
                     "C3": QColor(77, 175, 74),  # Green
                     "C4": QColor(152, 78, 163),  # Violet
                     "C5": QColor(255, 127, 0)} # Orange
                      # QColor(255, 255, 51),  # Yellow
                      # QColor(166, 86, 40),  # Brown
                      # QColor(247, 129, 191)}  # Pink

    PROBES = ["C1", "C2", "C3", "C4", "C5"]

    def __init__(self, explo_prvd: ExploProvider, explo_uim: ExploUim, main_ui: Ui_MainWindow, config: Config, icbktransition_dialog: IcbktransitionDialog):
        self.main_ui = main_ui
        self.explo_prvd = explo_prvd
        self.explo_uim = explo_uim
        self.config = config
        self.icbktransition_dialog = icbktransition_dialog
        self.icbktransition_ui = self.icbktransition_dialog.ui

        self.dataset_readers = dict()
        self.current_dataset = None

        self.__init_plot__()
        self.main_ui.explo_pushbutton_icbktransition.clicked.connect(self.__show_icbktransition_dialog__)
        self.icbktransition_ui.timeedit_reftime.dateTimeChanged.connect(self.__update_plot__)

    def __show_icbktransition_dialog__(self):
        self.current_dataset = self.explo_uim.current_dataset
        self.conduct_timeseries = self.current_dataset.get_timeseries("CONDUCTI_periodic").copy()
        self.__init_reftime__()
        self.__update_plot__()
        self.icbktransition_dialog.show()

    ####################################################################################################################
    # "Transition" table

    def __initialize_transition_table__(self):
        # Set column widths
        self.icbktransition_ui.tablewidget_icbk_list.setColumnWidth(self.ID_COL, 150)
        self.icbktransition_ui.tablewidget_icbk_list.setColumnWidth(self.NAME_COL, 130)
        self.icbktransition_ui.tablewidget_icbk_list.setColumnWidth(self.START_COL, 55)
        self.icbktransition_ui.tablewidget_icbk_list.setColumnWidth(self.END_COL, 100)
        self.icbktransition_ui.tablewidget_icbk_list.setColumnWidth(self.HEIGHT_COL, 120)

    def __add_new_row_in_variable_table__(self):
        """Add a new row in the calibration table. Initialize the cell's content and properties."""
        table = self.main_ui.explo_tablewidget_variables
        row_id = table.rowCount()
        table.insertRow(row_id)

        # Instruments
        instrument_item = QComboBox()
        table.setCellWidget(row_id, self.INSTRUMENT_COL, instrument_item)

        # Variables
        variable_item = QComboBox()
        table.setCellWidget(row_id, self.VARIABLE_COL, variable_item)
        table.cellWidget(row_id, self.VARIABLE_COL).currentTextChanged.connect(
            lambda text, row_id=row_id: self.__apply_variable_change__(row_id=row_id, variable_name=text))

        # Connect Instrument change to variables display
        table.cellWidget(row_id, self.INSTRUMENT_COL).currentTextChanged.connect(
            lambda text, row_id=row_id: self.__update_variables_combobox__(combobox_text=text,
                                                                           variables_combobox=variable_item))

        # Color
        color_item = QTableWidgetItem()
        color_item.setBackground(self.DEFAULT_COLORS[row_id % len(self.DEFAULT_COLORS)])
        table.setItem(row_id, self.COLOR_COL, color_item)

        # Offset
        offset_item = QDoubleSpinBox()
        offset_item.setMinimum(-10000.0)
        offset_item.setMaximum(10000.0)
        offset_item.setDecimals(3)
        table.setCellWidget(row_id, self.OFFSET_COL, offset_item)
        table.cellWidget(row_id, self.OFFSET_COL).valueChanged.connect(
            lambda value, row_id=row_id: self.__apply_variable_change__(row_id=row_id))

        # Multiplicative factor
        mult_item = QDoubleSpinBox()
        mult_item.setValue(1.0)
        mult_item.setMinimum(-10000.0)
        mult_item.setMaximum(10000.0)
        mult_item.setDecimals(5)
        table.setCellWidget(row_id, self.MULT_COL, mult_item)
        table.cellWidget(row_id, self.MULT_COL).valueChanged.connect(
            lambda value, row_id=row_id: self.__apply_variable_change__(row_id=row_id))

        # Time shift
        timeshift_item = QDoubleSpinBox()
        timeshift_item.setMinimum(-10000.0)
        timeshift_item.setMaximum(10000.0)
        table.setCellWidget(row_id, self.TIMESHIFT_COL, timeshift_item)
        table.cellWidget(row_id, self.TIMESHIFT_COL).valueChanged.connect(
            lambda value, row_id=row_id: self.__apply_variable_change__(row_id=row_id))

        # Visible
        visible_item = QCheckBox()
        visible_item.setChecked(True)
        table.setCellWidget(row_id, self.VISIBLE_COL, visible_item)
        table.cellWidget(row_id, self.VISIBLE_COL).stateChanged.connect(
            lambda state, row_id=row_id: self.__apply_variable_change__(row_id=row_id))

        # X original
        xorig_item = QTableWidgetItem()
        xorig_item.setFlags(Qt.ItemIsEnabled)  # Read only
        table.setItem(row_id, self.XORIG_COL, xorig_item)

        # Y original
        yorig_item = QTableWidgetItem()
        yorig_item.setFlags(Qt.ItemIsEnabled)  # Read only
        table.setItem(row_id, self.YORIG_COL, yorig_item)

        self.__update_instruments_combobox__(self.main_ui.explo_tablewidget_variables.cellWidget(row_id, self.INSTRUMENT_COL))

        if hasattr(self, "plot_item"):
            self.plot_item.getViewBox().enableAutoRange(enable=True)

    def __update_instruments_combobox__(self, combobox: QComboBox):
        combobox.clear()

        for instrument_name in self.current_dataset.get_instruments_names():
            if instrument_name == "manual-event":
                continue
            combobox.addItem(instrument_name)

    def __update_variables_combobox__(self, combobox_text: str, variables_combobox: QComboBox):
        if combobox_text == "":
            return
        variables_combobox.clear()
        instrument_name = combobox_text

        variable_names = self.explo_prvd.get_instrument_variables(self.current_dataset, instrument_name)

        for variable_name in variable_names:
            variables_combobox.addItem(variable_name)

    def __change_color__(self, row: int, column: int):
        if column != self.COLOR_COL:
            return

        color = QColorDialog.getColor()
        self.main_ui.explo_tablewidget_variables.item(row, self.COLOR_COL).setBackground(color)
        self.__apply_variable_change__(row)

    def __get_row_dataframe__(self, row_id: int) -> pd.DataFrame:
        table = self.main_ui.explo_tablewidget_variables

        # Get instrument log
        instrument_name = table.cellWidget(row_id, self.INSTRUMENT_COL).currentText()
        variable_name = table.cellWidget(row_id, self.VARIABLE_COL).currentText()

        timeseries = self.explo_prvd.get_timeseries(self.current_dataset, instrument_name, variable_name)

        return timeseries


    ####################################################################
    # Plot


    def __mouse_clicked__(self, event: MouseClickEvent) -> None:
        """Function triggered when the user clicks on the plot. Display a vertical line under the mouse click and call
        function ``__update_xy_original__``

        Parameters
        ----------
        event: pyqtgraph.MouseClickEvent
        """
        pos = event.scenePos()
        mouse_point = self.step_curves[0].getViewBox().mapSceneToView(pos)
        instant = datetime.datetime.fromtimestamp(mouse_point.x(), tz=datetime.timezone.utc)
        self.measure_vline.setPos(mouse_point.x())
        self.__update_xy_original__(instant)

    def __mouse_moved__(self, pos: QPointF) -> None:
        """Function triggered when the user's mouse cursor hovers over the plot. Display a vertical line where the
        cursor is, and update time shift with click-fixed line, if any.

        Parameters
        ----------
        pos: PyQt5.QtCore.QPointF
        """
        mouse_point = self.step_curves[0].getViewBox().mapSceneToView(pos)
        self.cursor_vline.setPos(mouse_point.x())

        if self.measure_vline.getPos() != [0, 0]:
            timeshift_s = abs(self.measure_vline.getPos()[0] - self.cursor_vline.getPos()[0])
            self.main_ui.explo_label_timeshift.setText("Time shift: " + "{:.2f}".format(timeshift_s) + "s")

    def __init_plot__(self):
        self.plot_item = pg.PlotItem(axisItems={'bottom': utils.TimeAxisItem(orientation='bottom')})
        self.icbktransition_dialog.ui.graphicsview.setCentralItem(self.plot_item)

        # Conduct curves
        self.conduct_curves = dict()
        for probe in self.PROBES:
            self.conduct_curves[probe] = pg.PlotCurveItem()
            self.plot_item.addItem(self.conduct_curves[probe])

        # Vertical lines for the original iceblocks transition
        self.originaltransition_vlines = []
        self.originaltransition_texts = []

        # Vertical lines for the original iceblocks transition
        self.newtransition_vlines = []
        self.newtransition_texts = []

        # Vertical line following the cursor
        self.cursor_vline = pg.InfiniteLine(angle=90, movable=False, pen=pg.mkPen(style=Qt.DotLine))
        self.plot_item.addItem(self.cursor_vline, ignoreBounds=True)

    def __update_plot__(self):
        # Set data to step curve
        x_values = utils.pd_time_to_epoch_ms(self.conduct_timeseries.index)
        x_values.append(x_values[-1] + 1)
        for probe in self.PROBES:
            y_values = list(self.__shift_to_reftime__(self.conduct_timeseries[probe]))
            self.conduct_curves[probe].setData(x=x_values,
                                               y=y_values,
                                               pen=self.PROBES_COLORS[probe],
                                               stepMode=True)

    def __shift_to_reftime__(self, series: pd.Series) -> pd.Series:
        timezone = pytz.timezone("UTC")
        reftime = self.icbktransition_ui.timeedit_reftime.dateTime().toPyDateTime()
        reftime = timezone.localize(reftime)
        value_at_reftime = series.iloc[series.index.get_loc(reftime,
                                                            method='nearest')]
        series = series - value_at_reftime
        return series

    def __init_reftime__(self):
        min_reftime = min(self.conduct_timeseries.index)
        self.icbktransition_ui.timeedit_reftime.blockSignals(True)
        self.icbktransition_ui.timeedit_reftime.setMinimumDateTime(min_reftime)
        self.icbktransition_ui.timeedit_reftime.setMaximumDateTime(max(self.conduct_timeseries.index))
        self.icbktransition_ui.timeedit_reftime.setDateTime(min_reftime + datetime.timedelta(minutes=20))
        self.icbktransition_ui.timeedit_reftime.blockSignals(False)