Skip to content
Snippets Groups Projects
picarroinjectionuim.py 13.8 KiB
Newer Older
import datetime
import os
import pyqtgraph as pg
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import pandas as pd
from uim.picarrocalibuim import PicarroCalibUim
    """Class managing the 'Injections identification' sub-tab of the 'Picarro calibration' tab."""

    def __init__(self, picarrocalib_uim: PicarroCalibUim):
        super(PicarroInjectionUim, self).__init__()
        self.main_ui = picarrocalib_uim.main_ui
        self.picarro_prvd = picarrocalib_uim.picarro_prvd

        self.__init_tablewidget_dimensions__()

        # Injection's properties
        self.injection_prop_dict = dict()
        self.mean_sd_dict = dict()

        # Connect picarro_prvd signals
        self.picarro_prvd.sig_picarro_df_updated.connect(self.create_plot)
        self.picarro_prvd.sig_picarro_df_updated.connect(self.update_stdname_combobox)
        self.picarro_prvd.sig_stdtrue_df_updated.connect(self.update_stdname_combobox)
        self.picarro_prvd.sig_calibs_df_updated.connect(
            self.__update_calib_lines_data__
        )
        self.main_ui.picarro_pushbutton_addtofile.clicked.connect(
            self.__append_to_calibration_file
        )
        self.main_ui.picarro_pushbutton_stdname_suggestion_apply.clicked.connect(
            self.apply_std_suggestion
        )

    def __init_tablewidget_dimensions__(self):
        table = self.main_ui.picarro_tablewidget
        table.verticalHeader().setStyleSheet("QHeaderView { font-size: 10pt; }")
        table.horizontalHeader().setStyleSheet("QHeaderView { font-size: 10pt; }")

        table.verticalHeader().setFixedWidth(50)  # Width of the Row header
        table.setColumnWidth(0, 99)
        table.setColumnWidth(1, 98)

        table.horizontalHeader().setFixedHeight(24)  # Height of the Column header
        table.setRowHeight(0, 32)
        table.setRowHeight(1, 32)
        table.setRowHeight(2, 32)

    ####################################################################
    def update_stdname_combobox(self):
        std_names = self.picarro_prvd.stdtrue_df["name"]
        self.main_ui.picarro_combobox_stdname_user.clear()
        self.main_ui.picarro_combobox_stdname_user.addItems(std_names)

    def apply_std_suggestion(self):
        std_sugg = self.main_ui.picarro_lineedit_stdname_suggestion.text()
        index = self.main_ui.picarro_combobox_stdname_user.findText(std_sugg)
        if index != -1:  # Value not found
            self.main_ui.picarro_combobox_stdname_user.setCurrentIndex(index)
            self.main_ui.picarro_lineedit_stdname_suggestion.setStyleSheet(
                "color: 'black';"
            )

    ####################################################################
    # Calibration file management

    def __append_to_calibration_file(self):
        filename = self.main_ui.picarro_lineedit_calib_filename.text()
        if filename == "":
            utils.show_popup("Output calibration file path not set!")
            return

        self.injection_prop_dict[
            "standard"
        ] = self.main_ui.picarro_combobox_stdname_user.currentText()
        self.injection_prop_dict[
            "dataset"
        ] = self.main_ui.picarro_lineedit_dataset.text()

        # Convert dict to DataFrame
        parameters_df = pd.DataFrame([self.injection_prop_dict])

        # Add 'keep' column. Injection are valid by default.
        parameters_df["keep"] = True

        parameters_df = parameters_df[
            parameters_df.columns.reindex(
                [
                    "dataset",
                    "standard",
                    "plateau_start",
                    "plateau_end",
                    "plateau_duration",
                    "valid_start",
                    "valid_end",
                    "valid_duration",
                    "H2O_mean",
                    "H2O_std",
                    "Delta_18_16_mean",
                    "Delta_18_16_std",
                    "Delta_D_H_mean",
                    "Delta_D_H_std",
                    "keep",
                ]
            )[0]
        ]

        parameters_df.to_csv(
            filename,
            sep="\t",
            mode="a",
            index=False,
            header=(not os.path.exists(filename) or os.stat(filename).st_size == 0),
        )
        self.picarro_prvd.update_calib_df(filename)

        msg = QMessageBox()
        msg.setIcon(QMessageBox.Information)
        msg.setWindowTitle("Injection added")
        msg.setText(
            "The injection's parameter has successfully been added in calibration file."
        )
        msg.exec_()

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

    def create_plot(self, picarro_df: pd.DataFrame):
        layout.layout.setSpacing(0.0)
        layout.setContentsMargins(0.0, 0.0, 0.0, 0.0)

        # Add it to the graphics view
        self.main_ui.picarro_graphicsview_injections.setCentralItem(layout)

        # Create plot items and add them to the layout
        # self.set_plots(layout)
        plot_h2o = self.__get_plot_item__(picarro_df, layout, 0, "H2O")
        plot_delta_18_16 = self.__get_plot_item__(picarro_df, layout, 1, "Delta_18_16")
        self.__add_second_curve__(picarro_df, layout, 1, "Delta_D_H")
        self.calib_line_item = pg.PlotCurveItem(pen=pg.mkPen("g"))
        plot_delta_18_16.addItem(self.calib_line_item)
        self.__update_calib_lines_data__()

        # Get the main windows view box
        main_range = plot_delta_18_16.getViewBox().viewRange()
        x_min = main_range[0][0]
        x_max = main_range[0][1]

        # Add "plateau" region
        self.plateau_region = pg.LinearRegionItem(
            brush=pg.mkBrush(color=[0, 0, 255, 40])
        )
        self.plateau_region.setZValue(10)
        self.plateau_region.setRegion([x_min, x_min + 15 * 60])
        self.plateau_region.sigRegionChanged.connect(
            self.__update_plateau_characteristics__
        )
        self.plateau_region.sigRegionChanged.connect(self.__adjust_regions__)
        plot_delta_18_16.addItem(self.plateau_region, ignoreBounds=True)

        # Add "valid area" region
        self.validarea_region = pg.LinearRegionItem(
            brush=pg.mkBrush(color=[0, 255, 0, 40])
        )
        self.validarea_region.setZValue(11)
        self.validarea_region.setRegion([x_min + 10 * 60, x_min + 15 * 60])
        self.validarea_region.sigRegionChanged.connect(
            self.__update_validarea_characteristics__
        )
        self.validarea_region.sigRegionChanged.connect(self.__adjust_regions__)
        plot_delta_18_16.addItem(self.validarea_region, ignoreBounds=True)

    def __get_plot_item__(
        self,
        picarro_df: pd.DataFrame,
        layout: pg.GraphicsLayout,
        rank: int,
        species: str,
    ):
        plot_item = layout.addPlot(
            rank, 0, axisItems={"bottom": utils.TimeAxisItem(orientation="bottom")}
        )
        species_series = picarro_df[species]
        step_curve.setData(
            x=utils.pd_time_to_epoch_ms(species_series.index),
            y=list(species_series),
            stepMode=False,
        )
        plot_item.setLabel("left", species)
    def __add_second_curve__(
        self,
        picarro_df: pd.DataFrame,
        layout: pg.GraphicsLayout,
        rank: int,
        species: str,
    ):
        # Get the plot item of the main curve in this layout position
        plot_item = layout.getItem(rank, 0)

        # P2 is the second plot item
        p2 = pg.ViewBox()
        plot_item.showAxis("right")
        plot_item.getAxis("right").linkToView(p2)
        plot_item.getAxis("right").setLabel(species, color="blue")
        step_curve = pg.PlotCurveItem(pen=pg.mkPen("b"))
        species_series = picarro_df[species]
        step_curve.setData(
            x=utils.pd_time_to_epoch_ms(species_series.index),
            y=list(species_series),
            stepMode=False,
        )
    def __update_calib_lines_data__(self):
        if len(self.picarro_prvd.calibs_df.index) == 0:
            return
        if len(self.picarro_prvd.picarro_df.index) == 0:
            return
        calibs_df = self.picarro_prvd.calibs_df.copy()
        calibs_df = calibs_df[
            (calibs_df["valid_start"] >= self.picarro_prvd.picarro_df.index.min())
            & (calibs_df["valid_end"] <= self.picarro_prvd.picarro_df.index.max())
        ]
        x = []
        y = []
        connect = []
        for index, row in calibs_df.iterrows():
            x += [row["valid_start"], row["valid_end"]]
            y += [row["Delta_18_16_mean"] + 1, row["Delta_18_16_mean"] + 1]
            connect += [True, False]

        self.calib_line_item.setData(
            x=utils.pd_time_to_epoch_ms(x),
            y=y,
            connect=np.array(connect),
            stepMode=False,
        )
    def __update_plateau_characteristics__(self, region: pg.LinearRegionItem):
        xmin = region.getRegion()[0]
        xmax = region.getRegion()[1]
        duration = QTime(0, 0, 0)
        duration = duration.addSecs(xmax - xmin)
        self.main_ui.picarro_timeedit_plateau_duration.setTime(duration)
        self.injection_prop_dict["plateau_start"] = utils.epoch_to_string(xmin)
        self.injection_prop_dict["plateau_end"] = utils.epoch_to_string(xmax)
        self.injection_prop_dict["plateau_duration"] = "{:.2f}".format(xmax - xmin)

    def __update_validarea_characteristics__(self, region: pg.LinearRegionItem):
        # Area's duration
        xmin = region.getRegion()[0]
        xmax = region.getRegion()[1]
        duration = QTime(0, 0, 0)
JOSSOUD Olivier's avatar
JOSSOUD Olivier committed
        duration = duration.addSecs(int(xmax - xmin))
        self.main_ui.picarro_timeedit_validarea_duration.setTime(duration)

        mean_sd_dict = self.picarro_prvd.get_mean_and_sd(
            first_date=datetime.datetime.fromtimestamp(xmin, tz=datetime.timezone.utc),
            last_date=datetime.datetime.fromtimestamp(xmax, tz=datetime.timezone.utc),
        )
        all_species = ["H2O", "Delta_18_16", "Delta_D_H"]
        for species in all_species:
            mean_item = QTableWidgetItem("{:.2f}".format(mean_sd_dict[species]["mean"]))
            mean_item.setFlags(Qt.ItemIsEnabled)  # Read only
            self.main_ui.picarro_tablewidget.setItem(
                all_species.index(species), 0, mean_item
            )

            sd_item = QTableWidgetItem("{:.2f}".format(mean_sd_dict[species]["std"]))
            sd_item.setFlags(Qt.ItemIsEnabled)  # Read only
            self.main_ui.picarro_tablewidget.setItem(
                all_species.index(species), 1, sd_item
            )
        if len(self.picarro_prvd.stdtrue_df.index) > 0:
            closest_std = self.picarro_prvd.stdtrue_df.iloc[
                (
                    self.picarro_prvd.stdtrue_df["Delta_18_16_mean"]
                    - mean_sd_dict["Delta_18_16"]["mean"]
                )
                .abs()
                .argsort()[:1]
            ]
            self.main_ui.picarro_lineedit_stdname_suggestion.setText(
                list(closest_std["name"])[0]
            )
            if (
                self.main_ui.picarro_lineedit_stdname_suggestion.text()
                != self.main_ui.picarro_combobox_stdname_user.currentText()
            ):
                self.main_ui.picarro_lineedit_stdname_suggestion.setStyleSheet(
                    "color: 'orange';"
                )
                self.main_ui.picarro_lineedit_stdname_suggestion.setStyleSheet(
                    "color: 'black';"
                )

        # Update Injection's properties dictionnary
        self.injection_prop_dict["valid_start"] = utils.epoch_to_string(xmin)
        self.injection_prop_dict["valid_end"] = utils.epoch_to_string(xmax)
        self.injection_prop_dict["valid_duration"] = "{:.2f}".format(xmax - xmin)
        for species in all_species:
            for metrics in ["mean", "std"]:
                self.injection_prop_dict[species + "_" + metrics] = "{:.2f}".format(
                    mean_sd_dict[species][metrics]
                )

    def __adjust_regions__(self, moved_region: pg.LinearRegionItem):
        xmin_plateau = self.plateau_region.getRegion()[0]
        xmax_plateau = self.plateau_region.getRegion()[1]
        plateau_width = xmax_plateau - xmin_plateau

        xmin_validarea = self.validarea_region.getRegion()[0]
        xmax_validarea = self.validarea_region.getRegion()[1]
        validarea_width = xmax_validarea - xmin_validarea

        if moved_region == self.validarea_region:
            if validarea_width > plateau_width - 10:
                self.plateau_region.setRegion([xmin_validarea - 10, xmax_plateau])
            elif xmin_validarea < xmin_plateau:
                self.plateau_region.setRegion(
                    [xmin_validarea, xmin_validarea + plateau_width]
                )
                self.plateau_region.setRegion(
                    [xmax_validarea - plateau_width, xmax_validarea]
                )
        elif moved_region == self.plateau_region:
            if xmin_plateau > xmin_validarea:
                self.validarea_region.setRegion(
                    [xmin_plateau, xmin_plateau + validarea_width]
                )
                self.validarea_region.setRegion(
                    [xmax_plateau - validarea_width, xmax_plateau]
                )