def _add_btn_layout(self):
        # add a DockArea for buttons
        area = DockArea()
        dock = Dock("Functions")
        area.addDock(dock)
        area.show()

        self.vlayout.addWidget(area, 1, 1)
        btn_layout = pg.LayoutWidget()
        dock.addWidget(btn_layout)
        self.btn_layout = btn_layout
#%%

"Test of image acquisition"
"à faire: des essais avec différents binning"
"à faire: des essais avec différents types de données"
sizex, sizey = test.getImageSize()
print("Image Size: ", sizex, sizey)
imagedata = numpy.zeros((sizex*sizey,), dtype = numpy.float32)
pointeur = imagedata.ctypes.data_as(ctypes.c_void_p)
#%%
app = QtWidgets.QApplication(sys.argv)
Form=DockArea()
Form=QtWidgets.QWidget()

prog = Image_View_Multicolor(Form);
Form.show()
sys.exit(app.exec_())
#%%
test.startFocus(0.1, "2d", 1)

time.sleep(10)
##
test.stopFocus()
#
#%%
mylib.figure_docked('image')
plt.imshow(imagedata.reshape((sizey,sizex)))
#%%
#Test of spectrum image acquisition
print("preparing spim acquisition")
sizex, sizey = test.getImageSize()
    PGWidget = CrossSectionImageView


if __name__ == '__main__':
    import sys
    import numpy as np
    app = QtGui.QApplication([])
    w = DockArea()
    w1 = CrosshairPlotWidget()
    w1.set_data(np.sin(np.linspace(0, 10, 100)))
    d = CloseableDock("Crosshair Plot", widget=w1)
    w.addDock(d)
    d2 = CrossSectionDock("Cross Section Dock")
    xs, ys = np.mgrid[-500:500, -500:500] / 100.
    rs = np.sqrt(xs**2 + ys**2)
    d2.set_data(rs)
    w.addDock(d2)
    #w2 = CrossSectionImageView()
    #w2.set_data(rs)
    #ts, xs, ys = np.mgrid[0:100, -50:50, -50:50]/20.
    #zs = np.sinc(xs**2 + ys**2 + ts)
    #w2 = MoviePlotWidget()
    #w2.set_data(zs)
    #l.addWidget(w2)
    #l.addWidget(w2.play_button)
    #l.addWidget(w2.stop_button)
    #l.addWidget(w2.h_cross_section_widget)
    #l.addWidget(w2.v_cross_section_widget)
    w.show()
    sys.exit(app.exec_())
Exemple #4
0
class ALD_Display(Measurement):
    '''
    - Generates the user interface layout including user input fields and \
    output fields displaying ALD sensor data
    - Creates and updates any plots with data stored in arrays.
    - This module also checks whether certain conditions in the ALD system environment are met.
    
    *LoggedQuantities* in \
    :class:`ALD_Recipe` are connected to indicators defined here within 
    :meth:`setup_conditions_widget`.
    
    This Measurement module and the 
    :class:`ALD_Display` module are interdependent and make calls to functions in the other module.
    These modules should be loaded by 
    :class:`ALD_App`
    in the following order:
    
    1. :class:`ALD_Recipe <ALD.ALD_recipes.ALD_recipe>`
    2. :class:`ALD_Display <ALD.ALD_recipes.ALD_display>`

    '''

    name = 'ALD_display'

    def __init__(self, app):
        Measurement.__init__(self, app)

    def setup(self):
        self.settings.New('RF_pulse_duration', dtype=int, initial=1)
        self.settings.New('history_length',
                          dtype=int,
                          initial=1e6,
                          vmin=1,
                          ro=True)
        self.settings.New('shutter_open', dtype=bool, initial=False, ro=True)
        self.settings.New('display_window', dtype=int, initial=1e4, vmin=1)

        self.setup_buffers_constants()
        self.resources = resources

        self.settings.New('save_path',
                          dtype=str,
                          initial=self.full_file_path,
                          ro=False)

        self.ch1_scaled = self.settings.New(name='ch1_pressure',
                                            dtype=float,
                                            initial=0.0,
                                            fmt='%.4e',
                                            si=True,
                                            spinbox_decimals=6,
                                            ro=True)
        self.ch2_scaled = self.settings.New(name='ch2_pressure',
                                            dtype=float,
                                            initial=0.0,
                                            fmt='%.4e',
                                            si=True,
                                            spinbox_decimals=6,
                                            ro=True)
        self.ch3_scaled = self.settings.New(name='ch3_pressure',
                                            dtype=float,
                                            initial=0.0,
                                            fmt='%.4e',
                                            si=True,
                                            spinbox_decimals=6,
                                            ro=True)

        if hasattr(self.app.hardware, 'seren_hw'):
            self.seren = self.app.hardware.seren_hw
            if self.app.measurements.seren.psu_connected:
                print('Seren PSU Connected')
            else:
                print('Connect Seren HW component first.')

        if hasattr(self.app.hardware, 'ald_shutter'):
            self.shutter = self.app.hardware.ald_shutter
        else:
            print('Connect ALD shutter HW component first.')

        if hasattr(self.app.hardware, 'lovebox'):
            self.lovebox = self.app.hardware.lovebox

        if hasattr(self.app.hardware, 'mks_146_hw'):
            self.mks146 = self.app.hardware.mks_146_hw

        if hasattr(self.app.hardware, 'ni_mfc1'):
            self.ni_mfc1 = self.app.hardware.ni_mfc1

        if hasattr(self.app.hardware, 'ni_mfc2'):
            self.ni_mfc2 = self.app.hardware.ni_mfc2

        if hasattr(self.app.hardware, 'mks_600_hw'):
            self.mks600 = self.app.hardware.mks_600_hw

        if hasattr(self.app.hardware, 'pfeiffer_vgc_hw'):
            self.vgc = self.app.hardware.pfeiffer_vgc_hw

        if hasattr(self.app.hardware, 'vat_throttle_hw'):
            self.vat = self.app.hardware.vat_throttle_hw

        if hasattr(self.app.measurements, 'ALD_Recipe'):
            self.recipe = self.app.measurements.ALD_Recipe
        else:
            print('ALD_Recipe not setup')

        self.ui_enabled = True
        if self.ui_enabled:
            self.ui_setup()

        self.connect_indicators()
        self.ui_initial_defaults()

    def connect_indicators(self):
        '''Connects *LoggedQuantities* to UI indicators using :meth:`connect_to_widget`'''
        self.recipe.settings.pumping.connect_to_widget(
            self.pump_down_indicator)
        self.recipe.settings.predeposition.connect_to_widget(
            self.pre_deposition_indicator)
        self.recipe.settings.deposition.connect_to_widget(
            self.deposition_indicator)
        self.recipe.settings.vent.connect_to_widget(self.vent_indicator)
        self.recipe.settings.pumped.connect_to_widget(self.pumped_indicator)
        self.recipe.settings.gases_ready.connect_to_widget(
            self.gases_ready_indicator)
        self.recipe.settings.substrate_hot.connect_to_widget(
            self.substrate_indicator)
        self.recipe.settings.recipe_running.connect_to_widget(
            self.recipe_running_indicator)
        self.recipe.settings.recipe_completed.connect_to_widget(
            self.recipe_ready_indicator)
        self.seren.settings.RF_enable.connect_to_widget(
            self.plasma_on_indicator)

    def update_subtable(self):
        """
        Updates subroutine table tableWidget (UI element)
        Called by :meth:`app.measurements.ALD_recipe.subroutine_sum` after the 
        subroutine time table summation has been calculated.
        """
        self.subtableModel.on_lq_updated_value()

    def update_table(self):
        """
        Updates the main time table tableWidget (UI element)
        Called by :attr:`app.measurements.ALD_recipe.sum_times` after the 
        time table summation has been calculated.
        """
        self.tableModel.on_lq_updated_value()

    def ui_setup(self):
        '''Calls all functions needed to set up UI programmatically.
        This object is called from :meth:`setup`'''
        self.ui = DockArea()
        self.layout = QtWidgets.QVBoxLayout()
        self.ui.show()
        self.ui.setWindowTitle('ALD Control Panel')
        self.ui.setLayout(self.layout)
        self.widget_setup()
        self.dockArea_setup()

    def dockArea_setup(self):
        """
        Creates dock objects and determines order of dockArea widget placement in UI.
        
        This function is called from 
        :meth:`ui_setup`
        """
        self.rf_dock = Dock('RF Settings')
        self.rf_dock.addWidget(self.rf_widget)
        self.ui.addDock(self.rf_dock)

        self.vgc_dock = Dock('VGC History')
        self.vgc_dock.addWidget(self.pressure_vgc_plot_widget)
        self.ui.addDock(self.vgc_dock,
                        position='left',
                        relativeTo=self.rf_dock)

        self.thermal_dock = Dock('Thermal History')
        self.thermal_dock.addWidget(self.thermal_widget)
        self.ui.addDock(self.thermal_dock,
                        position='right',
                        relativeTo=self.rf_dock)

        self.recipe_dock = Dock('Recipe Controls')
        self.recipe_dock.addWidget(self.recipe_control_widget)
        self.ui.addDock(self.recipe_dock, position='bottom')

        self.display_ctrl_dock = Dock('Display Controls')
        self.display_ctrl_dock.addWidget(self.display_control_widget)
        self.ui.addDock(self.display_ctrl_dock,
                        position='bottom',
                        relativeTo=self.recipe_dock)

        self.hardware_dock = Dock('Hardware')
        self.hardware_dock.addWidget(self.hardware_widget)
        self.ui.addDock(self.hardware_dock, position='top')

        self.conditions_dock = Dock('Conditions')
        self.conditions_dock.addWidget(self.conditions_widget)
        self.ui.addDock(self.conditions_dock,
                        position='left',
                        relativeTo=self.hardware_dock)

        self.operations_dock = Dock('Operations')
        self.operations_dock.addWidget(self.operations_widget)
        self.ui.addDock(self.operations_dock,
                        position='left',
                        relativeTo=self.conditions_dock)

    def load_ui_defaults(self):
        pass

    def save_ui_defaults(self):
        pass

    def ui_initial_defaults(self):
        '''Reverts the indicator arrays to their original state after deposition operations 
        have concluded or have been interrupted.
        This function is called from 
        :meth:`widget_setup` and 
        :meth:`setup`
        '''

        self.pump_down_button.setEnabled(False)
        self.pre_deposition_button.setEnabled(True)
        self.deposition_button.setEnabled(False)
        self.vent_button.setEnabled(False)

        self.pumped_button.setEnabled(False)
        self.gases_ready_button.setEnabled(False)
        self.plasma_on_button.setEnabled(False)
        self.substrate_button.setEnabled(False)
        self.recipe_running_button.setEnabled(False)
        self.recipe_complete_button.setEnabled(False)
        self.pre_deposition_button.clicked.connect(self.recipe.predeposition)
        self.deposition_button.clicked.connect(self.recipe.run_recipe)
        self.vent_button.clicked.connect(self.recipe.shutoff)

    def widget_setup(self):
        """
        Runs collection of widget setup functions each of which creates the widget 
        and then populates them.
        This function is called from :meth:`ui_setup`
        """
        self.cb_stylesheet = '''
        QCheckBox::indicator {
            width: 25px;
            height: 25px;
        }
        QCheckBox::indicator:checked {
            image: url(://icons//GreenLED.png);
        }
        QCheckBox::indicator:unchecked {
            image: url(://icons//RedLED.png);
        }
        '''
        self.setup_operations_widget()
        self.setup_conditions_widget()
        self.setup_thermal_control_widget()
        self.setup_rf_flow_widget()
        self.setup_vgc_pressure_widget()
        self.setup_recipe_control_widget()
        self.setup_display_controls()
        self.setup_hardware_widget()

        self.ui_initial_defaults()

    def setup_conditions_widget(self):
        """
        Creates conditions widget which is meant to provide end user with an 
        array of LED indicators (and greyed out pushbuttons serving as labels.)
        which serve as indicators of desired conditions within the ALD recipe process.
        """

        self.conditions_widget = QtWidgets.QGroupBox('Conditions Widget')
        self.conditions_widget.setLayout(QtWidgets.QGridLayout())
        self.conditions_widget.setStyleSheet(self.cb_stylesheet)

        self.pumped_indicator = QtWidgets.QCheckBox()
        self.pumped_button = QtWidgets.QPushButton('Pumped')
        self.conditions_widget.layout().addWidget(self.pumped_indicator, 0, 0)
        self.conditions_widget.layout().addWidget(self.pumped_button, 0, 1)

        self.gases_ready_indicator = QtWidgets.QCheckBox()
        self.gases_ready_button = QtWidgets.QPushButton('Gases Ready')
        self.conditions_widget.layout().addWidget(self.gases_ready_indicator,
                                                  1, 0)
        self.conditions_widget.layout().addWidget(self.gases_ready_button, 1,
                                                  1)

        self.substrate_indicator = QtWidgets.QCheckBox()
        self.substrate_button = QtWidgets.QPushButton('Stage Temp. Ready')
        self.conditions_widget.layout().addWidget(self.substrate_indicator, 2,
                                                  0)
        self.conditions_widget.layout().addWidget(self.substrate_button, 2, 1)

        self.recipe_running_indicator = QtWidgets.QCheckBox()
        self.recipe_running_button = QtWidgets.QPushButton('Recipe Running')
        self.conditions_widget.layout().addWidget(
            self.recipe_running_indicator, 3, 0)
        self.conditions_widget.layout().addWidget(self.recipe_running_button,
                                                  3, 1)

        self.plasma_on_indicator = QtWidgets.QCheckBox()
        self.plasma_on_button = QtWidgets.QPushButton('Plasma On')
        self.conditions_widget.layout().addWidget(self.plasma_on_indicator, 4,
                                                  0)
        self.conditions_widget.layout().addWidget(self.plasma_on_button, 4, 1)

        self.recipe_complete_indicator = QtWidgets.QCheckBox()
        self.recipe_complete_button = QtWidgets.QPushButton('Recipe Done')
        self.conditions_widget.layout().addWidget(
            self.recipe_complete_indicator, 5, 0)
        self.conditions_widget.layout().addWidget(self.recipe_complete_button,
                                                  5, 1)

    def setup_operations_widget(self):
        """Creates operations widget which is meant to provide end user with push buttons 
        which initiate specific subroutines of the ALD recipe process."""
        self.operations_widget = QtWidgets.QGroupBox('Operations Widget')
        self.operations_widget.setLayout(QtWidgets.QGridLayout())
        self.operations_widget.setStyleSheet(self.cb_stylesheet)

        self.pump_down_indicator = QtWidgets.QCheckBox()
        self.pump_down_button = QtWidgets.QPushButton('Pump Down')
        self.operations_widget.layout().addWidget(self.pump_down_indicator, 0,
                                                  0)
        self.operations_widget.layout().addWidget(self.pump_down_button, 0, 1)

        self.pre_deposition_indicator = QtWidgets.QCheckBox()
        self.pre_deposition_button = QtWidgets.QPushButton('Pre-deposition')
        self.operations_widget.layout().addWidget(
            self.pre_deposition_indicator, 1, 0)
        self.operations_widget.layout().addWidget(self.pre_deposition_button,
                                                  1, 1)

        self.deposition_indicator = QtWidgets.QCheckBox()
        self.deposition_button = QtWidgets.QPushButton('Deposition')
        self.operations_widget.layout().addWidget(self.deposition_indicator, 2,
                                                  0)
        self.operations_widget.layout().addWidget(self.deposition_button, 2, 1)

        self.vent_indicator = QtWidgets.QCheckBox()
        self.vent_button = QtWidgets.QPushButton('Vent')
        self.operations_widget.layout().addWidget(self.vent_indicator, 3, 0)
        self.operations_widget.layout().addWidget(self.vent_button, 3, 1)

    def setup_hardware_widget(self):
        """
        Creates Hardware widget which contains the Plasma and Temperature readout subpanels 
        and the Pressures and Flow subpanel. 
        This enclosing widget was created solely for the purpose of organizing subpanel 
        arrangement in UI.
        """
        self.hardware_widget = QtWidgets.QGroupBox('Hardware Widget')
        self.hardware_widget.setLayout(QtWidgets.QHBoxLayout())

        self.left_widget = QtWidgets.QWidget()
        self.left_widget.setLayout(QtWidgets.QVBoxLayout())
        self.left_widget.setMinimumWidth(350)

        self.right_widget = QtWidgets.QGroupBox('Pressures and Flow')
        self.right_widget.setLayout(QtWidgets.QHBoxLayout())
        self.right_widget.setMinimumHeight(250)
        self.temp_field_panel = QtWidgets.QGroupBox(
            'Temperature Readout Panel [' + u'\u00b0' + 'C]')
        self.temp_field_panel.setLayout(QtWidgets.QGridLayout())

        self.sp_temp_label = QtWidgets.QLabel('Set Point')
        self.sp_temp_field = QtWidgets.QDoubleSpinBox()
        self.lovebox.settings.sv_setpoint.connect_to_widget(self.sp_temp_field)

        self.pv_temp_label = QtWidgets.QLabel('Process Value')
        self.pv_temp_field = QtWidgets.QDoubleSpinBox()
        self.lovebox.settings.pv_temp.connect_to_widget(self.pv_temp_field)

        self.temp_field_panel.layout().addWidget(self.sp_temp_label, 0, 0)
        self.temp_field_panel.layout().addWidget(self.sp_temp_field, 0, 1)
        self.temp_field_panel.layout().addWidget(self.pv_temp_field, 0, 2)
        self.temp_field_panel.layout().addWidget(self.pv_temp_label, 0, 3)

        self.left_widget.layout().addWidget(self.temp_field_panel)

        self.hardware_widget.layout().addWidget(self.left_widget)
        self.hardware_widget.layout().addWidget(self.right_widget)
        self.setup_plasma_subpanel()
        self.setup_pressures_subpanel()

    def setup_plasma_subpanel(self):
        """
        Creates plasma subpanel which displays information relevant to the 
        connected RF power supply. Subpanel also includes fields allowing for 
        user defined setpoints to be sent to the power supply software side.

        Creates UI elements related to the ALD RF power supply,
        establishes signals and slots, as well as connections between UI elements 
        and their associated *LoggedQuantities*.
        """
        self.plasma_panel = QtWidgets.QGroupBox('Plasma Panel [W]')
        self.left_widget.layout().addWidget(self.plasma_panel)

        self.fwd_power_input_label = QtWidgets.QLabel('FWD Power \n Input')
        self.fwd_power_input = QtWidgets.QDoubleSpinBox()
        self.fwd_power_readout_label = QtWidgets.QLabel('FWD Power \n Readout')
        self.fwd_power_readout = QtWidgets.QDoubleSpinBox()

        self.plasma_left_panel = QtWidgets.QWidget()
        self.plasma_left_panel.setLayout(QtWidgets.QGridLayout())
        self.plasma_right_panel = QtWidgets.QWidget()
        self.plasma_right_panel.setLayout(QtWidgets.QGridLayout())
        self.plasma_right_panel.setStyleSheet(self.cb_stylesheet)

        self.plasma_left_panel.layout().addWidget(self.fwd_power_input_label,
                                                  0, 0)
        self.plasma_left_panel.layout().addWidget(self.fwd_power_input, 0, 1)
        self.plasma_left_panel.layout().addWidget(self.fwd_power_readout_label,
                                                  1, 0)
        self.plasma_left_panel.layout().addWidget(self.fwd_power_readout, 1, 1)

        self.rf_indicator = QtWidgets.QCheckBox()
        self.rf_pushbutton = QtWidgets.QPushButton('RF ON/OFF')
        if hasattr(self, 'shutter'):
            self.seren.settings.RF_enable.connect_to_widget(self.rf_indicator)
            self.rf_pushbutton.clicked.connect(self.seren.RF_toggle)

        self.rev_power_readout_label = QtWidgets.QLabel(
            'REFL Power \n Readout')
        self.rev_power_readout = QtWidgets.QDoubleSpinBox()

        self.plasma_right_panel.layout().addWidget(self.rf_indicator, 0, 1)
        self.plasma_right_panel.layout().addWidget(self.rf_pushbutton, 0, 0)
        self.plasma_right_panel.layout().addWidget(self.rev_power_readout, 1,
                                                   0)
        self.plasma_right_panel.layout().addWidget(
            self.rev_power_readout_label, 1, 1)

        self.seren.settings.set_forward_power.connect_to_widget(
            self.fwd_power_input)
        self.seren.settings.forward_power_readout.connect_to_widget(
            self.fwd_power_readout)
        self.seren.settings.reflected_power.connect_to_widget(
            self.rev_power_readout)

        self.plasma_panel.setLayout(QtWidgets.QHBoxLayout())
        self.plasma_panel.layout().addWidget(self.plasma_left_panel)
        self.plasma_panel.layout().addWidget(self.plasma_right_panel)

    def setup_pressures_subpanel(self):
        """Creates pressures subpanel which display pressure sensor measurements. 
        Subpanel includes measurement value fields and input fields which allow for 
        user defined setpoints to be sent to pressure controllers. 
        
        Creates UI elements related to ALD pressure controllers,
        establishes signals and slots, as well as connections between UI elements 
        and their associated *LoggedQuantities*.
        """
        self.flow_input_group = QtWidgets.QGroupBox('Flow Inputs')
        self.flow_output_group = QtWidgets.QGroupBox('Flow Outputs')
        self.pressures_group = QtWidgets.QGroupBox('Pressure Outputs')

        self.right_widget.layout().addWidget(self.flow_input_group)
        self.right_widget.layout().addWidget(self.flow_output_group)
        self.right_widget.layout().addWidget(self.pressures_group)

        self.flow_input_group.setStyleSheet(self.cb_stylesheet)
        self.flow_input_group.setLayout(QtWidgets.QGridLayout())
        self.flow_output_group.setLayout(QtWidgets.QGridLayout())
        self.flow_output_group.setMinimumWidth(100)
        self.pressures_group.setLayout(QtWidgets.QGridLayout())
        self.pressures_group.setMinimumWidth(158)

        self.MFC1_label = QtWidgets.QLabel('MFC1 (200 sccm) Flow')
        self.set_MFC1_field = QtWidgets.QDoubleSpinBox()
        self.MFC2_label = QtWidgets.QLabel('MFC2 (20 sccm) Flow')
        self.set_MFC2_field = QtWidgets.QDoubleSpinBox()
        self.MFC3_label = QtWidgets.QLabel('MFC3 (x sccm) Flow')
        self.set_MFC3_field = QtWidgets.QDoubleSpinBox()

        self.throttle_pressure_label = QtWidgets.QLabel(
            'Throttle Pressure \n [mTorr]')
        self.set_throttle_pressure_field = QtWidgets.QDoubleSpinBox()

        self.throttle_pos_label = QtWidgets.QLabel(
            'Throttle Valve \n Position')
        self.set_throttle_pos_field = QtWidgets.QDoubleSpinBox()

        self.shutter_indicator = QtWidgets.QCheckBox()
        self.shutter_pushbutton = QtWidgets.QPushButton('Shutter Open/Close')
        if hasattr(self, 'shutter'):
            self.shutter.settings.shutter_open.connect_to_widget(
                self.shutter_indicator)
            self.shutter_pushbutton.clicked.connect(
                self.shutter.shutter_toggle)

        self.input_spacer = QtWidgets.QSpacerItem(
            10, 30, QtWidgets.QSizePolicy.Minimum,
            QtWidgets.QSizePolicy.Expanding)

        self.flow_input_group.layout().addWidget(self.MFC1_label, 0, 0)
        self.flow_input_group.layout().addWidget(self.set_MFC1_field, 0, 1)
        self.flow_input_group.layout().addWidget(self.MFC2_label, 1, 0)
        self.flow_input_group.layout().addWidget(self.set_MFC2_field, 1, 1)
        self.flow_input_group.layout().addWidget(self.MFC3_label, 2, 0)
        self.flow_input_group.layout().addWidget(self.set_MFC3_field, 2, 1)

        self.flow_input_group.layout().addWidget(self.throttle_pressure_label,
                                                 3, 0)
        self.flow_input_group.layout().addWidget(
            self.set_throttle_pressure_field, 3, 1)
        self.flow_input_group.layout().addWidget(self.throttle_pos_label, 4, 0)
        self.flow_input_group.layout().addWidget(self.set_throttle_pos_field,
                                                 4, 1)
        self.flow_input_group.layout().addWidget(self.shutter_indicator, 5, 0)
        self.flow_input_group.layout().addWidget(self.shutter_pushbutton, 5, 1)
        self.flow_input_group.layout().addItem(self.input_spacer, 6, 0)

        self.read_MFC1_field = QtWidgets.QDoubleSpinBox()
        self.read_MFC2_field = QtWidgets.QDoubleSpinBox()
        self.read_MFC3_field = QtWidgets.QDoubleSpinBox()
        self.read_throttle_pressure_field = QtWidgets.QDoubleSpinBox()
        self.read_throttle_pos_field = QtWidgets.QDoubleSpinBox()
        self.output_spacer = QtWidgets.QSpacerItem(
            10, 30, QtWidgets.QSizePolicy.Minimum,
            QtWidgets.QSizePolicy.Expanding)

        self.flow_output_group.layout().addWidget(self.read_MFC1_field, 0, 0)
        self.flow_output_group.layout().addWidget(self.read_MFC2_field, 1, 0)
        self.flow_output_group.layout().addWidget(self.read_MFC3_field, 2, 0)

        self.flow_output_group.layout().addWidget(
            self.read_throttle_pressure_field, 3, 0)
        self.flow_output_group.layout().addWidget(self.read_throttle_pos_field,
                                                  4, 0)
        self.flow_output_group.layout().addItem(self.output_spacer, 5, 0)

        self.ch1_readout_field = QtWidgets.QDoubleSpinBox()
        self.ch1_readout_label = QtWidgets.QLabel('Ch1 Pressure \n [torr]')
        self.ch2_readout_field = QtWidgets.QDoubleSpinBox()
        self.ch2_readout_label = QtWidgets.QLabel('Ch2 Pressure \n [torr]')
        self.ch3_readout_field = QtWidgets.QDoubleSpinBox()
        self.ch3_readout_label = QtWidgets.QLabel('Ch3 Pressure \n [torr]')

        self.pressures_group.layout().addWidget(self.ch1_readout_label, 0, 0)
        self.pressures_group.layout().addWidget(self.ch1_readout_field, 0, 1)
        self.pressures_group.layout().addWidget(self.ch2_readout_label, 1, 0)
        self.pressures_group.layout().addWidget(self.ch2_readout_field, 1, 1)
        self.pressures_group.layout().addWidget(self.ch3_readout_label, 2, 0)
        self.pressures_group.layout().addWidget(self.ch3_readout_field, 2, 1)
        self.pressures_group.layout().addItem(self.output_spacer, 3, 0)

        if hasattr(self, 'ni_mfc1'):
            self.ni_mfc1.settings.write_mfc1.connect_to_widget(
                self.set_MFC1_field)
            self.ni_mfc1.settings.read_mfc1.connect_to_widget(
                self.read_MFC1_field)
            self.ni_mfc1.settings.write_mfc2.connect_to_widget(
                self.set_MFC2_field)
            self.ni_mfc1.settings.read_mfc2.connect_to_widget(
                self.read_MFC2_field)

        if hasattr(self, 'ni_mfc2'):
            self.ni_mfc2.settings.write_mfc3.connect_to_widget(
                self.set_MFC3_field)
            self.ni_mfc2.settings.read_mfc3.connect_to_widget(
                self.read_MFC3_field)

        if hasattr(self, 'mks600'):
            self.mks600.settings.sp_set_value.connect_to_widget(
                self.set_throttle_pressure_field)
            self.mks600.settings.set_valve_position.connect_to_widget(
                self.set_throttle_pos_field)

            self.mks600.settings.pressure.connect_to_widget(
                self.read_throttle_pressure_field)
            self.mks600.settings.read_valve_position.connect_to_widget(
                self.read_throttle_pos_field)
        if hasattr(self, 'vat'):
            self.vat.settings.write_position.connect_to_widget(
                self.set_throttle_pos_field)
            self.vat.settings.read_position.connect_to_widget(
                self.read_throttle_pos_field)

        self.vgc.settings.ch1_pressure_scaled.connect_to_widget(
            self.ch1_readout_field)
        self.vgc.settings.ch2_pressure_scaled.connect_to_widget(
            self.ch2_readout_field)
        self.vgc.settings.ch3_pressure_scaled.connect_to_widget(
            self.ch3_readout_field)

    def setup_thermal_control_widget(self):
        """Creates temperature plotting widget, UI elements meant to allow the user to monitor
        ALD system conditions, specifically temperature, through the use of live plots."""
        self.thermal_widget = QtWidgets.QGroupBox('Thermal Plot')
        self.thermal_widget.setLayout(QtWidgets.QVBoxLayout())
        self.thermal_channels = 1

        self.thermal_plot_widget = pg.GraphicsLayoutWidget()
        self.thermal_plot = self.thermal_plot_widget.addPlot(
            title='Temperature History')
        self.thermal_plot.showGrid(y=True)
        self.thermal_plot.addLegend()
        self.thermal_widget.layout().addWidget(self.thermal_plot_widget)
        self.thermal_plot_names = ['Heater Temperature']
        self.thermal_plot_lines = []
        for i in range(self.thermal_channels):
            color = pg.intColor(i)
            plot_line = self.thermal_plot.plot([1],
                                               pen=pg.mkPen(color, width=2),
                                               name=self.thermal_plot_names[i])
            self.thermal_plot_lines.append(plot_line)
        self.vLine1 = pg.InfiniteLine(angle=90, movable=False)
        self.hLine1 = pg.InfiniteLine(angle=0, movable=False)
        self.thermal_plot.addItem(self.vLine1)
        self.thermal_plot.addItem(self.hLine1)

    def setup_rf_flow_widget(self):
        """Creates RF/MFC plotting widget. UI elements are created, which are meant to 
        allow the user to monitor ALD system conditions through the use of live plots."""
        self.rf_widget = QtWidgets.QGroupBox('RF Plot')
        self.layout.addWidget(self.rf_widget)
        self.rf_widget.setLayout(QtWidgets.QVBoxLayout())

        self.rf_plot_widget = pg.GraphicsLayoutWidget()
        self.rf_plot = self.rf_plot_widget.addPlot(
            title='RF power and scaled MFC flow')
        self.rf_plot.showGrid(y=True)
        self.rf_plot.addLegend()
        self.rf_widget.layout().addWidget(self.rf_plot_widget)
        self.rf_plot_names = ['Forward', 'Reflected', 'MFC flow (scaled)']
        self.rf_plot_lines = []
        for i in range(self.RF_CHANS):
            color = pg.intColor(i)
            plot_line = self.rf_plot.plot([1],
                                          pen=pg.mkPen(color, width=2),
                                          name=self.rf_plot_names[i])
            self.rf_plot_lines.append(plot_line)
        self.vLine2 = pg.InfiniteLine(angle=90, movable=False)
        self.rf_plot.addItem(self.vLine2)

    def setup_vgc_pressure_widget(self):
        """Creates VGC plotting widget. UI elements are created, which are meant to 
        allow the user to monitor ALD system conditions through the use of live plots."""
        self.pressure_vgc_plot_widget = QtWidgets.QGroupBox('Pressure Plot')
        self.layout.addWidget(self.pressure_vgc_plot_widget)
        self.pressure_vgc_plot_widget.setLayout(QtWidgets.QVBoxLayout())

        self.vgc_plot_widget = pg.GraphicsLayoutWidget()

        self.vgc_plot = self.vgc_plot_widget.addPlot(title='Chamber Pressures')
        self.vgc_plot.setLogMode(y=True)
        self.vgc_plot.setYRange(-8, 2)
        self.vgc_plot.showGrid(y=True)
        self.vgc_plot.addLegend()
        self.pressure_vgc_plot_widget.layout().addWidget(self.vgc_plot_widget)
        self.vgc_plot_names = ['TKP_1', 'TKP_2', 'PKR_3']
        self.vgc_plot_lines = []
        for i in range(self.P_CHANS):
            color = pg.intColor(i)
            plot_line = self.vgc_plot.plot([1],
                                           pen=pg.mkPen(color, width=2),
                                           name=self.vgc_plot_names[i])
            self.vgc_plot_lines.append(plot_line)
        self.vLine3 = pg.InfiniteLine(angle=90, movable=False)
        self.vgc_plot.addItem(self.vLine3)

    def setup_shutter_control_widget(self):
        """
        Creates shutter control widget, UI elements related to ALD shutter controls,
        establishes signals and slots, as well as connections between UI elements 
        and their associated *LoggedQuantities*
        """
        self.shutter_control_widget = QtWidgets.QGroupBox('Shutter Controls')
        self.shutter_control_widget.setLayout(QtWidgets.QGridLayout())
        self.shutter_control_widget.setStyleSheet(self.cb_stylesheet)

        self.shutter_status = QtWidgets.QCheckBox(self.shutter_control_widget)
        self.shutter_control_widget.layout().addWidget(self.shutter_status, 0,
                                                       0)
        self.shutter.settings.shutter_open.connect_to_widget(
            self.shutter_status)

        self.shaul_shutter_toggle = QtWidgets.QPushButton(
            'Shaul\'s Huge Shutter Button')
        self.shaul_shutter_toggle.setMinimumHeight(200)
        font = self.shaul_shutter_toggle.font()
        font.setPointSize(24)
        self.shaul_shutter_toggle.setFont(font)
        self.shutter_control_widget.layout().addWidget(
            self.shaul_shutter_toggle, 0, 1)
        if hasattr(self, 'shutter'):
            self.shaul_shutter_toggle.clicked.connect(
                self.shutter.shutter_toggle)

    def setup_recipe_control_widget(self):
        """
        Creates recipe control widget, UI elements related to ALD recipe settings,
        establishes signals and slots, as well as connections between UI elements 
        and their associated *LoggedQuantities*
        
        The table widget consists of a hierarchy of PyQt5 classes.
        The structure of the table widget assumes the following form:
        
        * :class:`QWidget`
            * :class:`QTableView`
                * :class:`QTableModel`
        
        More specific to the case of the subroutine table:
        
        * :class:`QWidget` (:attr:`subroutine_table_widget`)
            * :class:`QTableView` (:attr:`subroutine_table`)
                * :class:`QTableModel` (:attr:`subtableModel`)
                
        And in the case of the main recipe table:
        
        * :class:`QWidget` (:attr:`table_widget`)
            * :class:`QTableView` (:attr:`pulse_table`)
                * :class:`QTableModel` (:attr:`tableModel`)
                
        See \
        :meth:`ALD.ALD_recipes.ALD_display.setup_recipe_control_widget` \
        for details.
        
        
        **Example of table creation using PyQt5 and ScopeFoundry:**
        
        .. highlight:: python
        .. code-block:: python
        
            self.table_widget = QtWidgets.QWidget()
            self.table_widget_layout = QtWidgets.QHBoxLayout()
            self.table_widget.setLayout(self.table_widget_layout)
    
            self.table_label = QtWidgets.QLabel('Table Label')
            self.table_widget.layout().addWidget(self.table_label)
    
            self.table = QtWidgets.QTableView()
            ## Optional height constraint.
            self.table.setMaximumHeight(65)
            
            names = ['List', 'of', 'column', 'labels']
            
            self.tableModel = ArrayLQ_QTableModel(self.displayed_array, col_names=names)
            self.table.setModel(self.tableModel)
            self.table_widget.layout().addWidget(self.table)
            
            ### Add widget to enclosing outer widget
            self.containing_widget.layout().addWidget(self.table_widget)
        
        Note that :class:`ArrayLQ_QTableModel` is a ScopeFoundry function containing PyQt5 code.
        For simplicity, it has been included in ScopeFoundry's core framework under ndarray_interactive.
        """
        self.recipe_control_widget = QtWidgets.QGroupBox('Recipe Controls')
        self.recLayout = QtWidgets.QVBoxLayout()
        self.recipe_control_widget.setLayout(self.recLayout)
        self.layout.addWidget(self.recipe_control_widget)

        ## Settings Widget
        self.settings_widget = QtWidgets.QWidget()
        self.settings_layout = QtWidgets.QGridLayout()
        self.settings_widget.setLayout(self.settings_layout)

        self.cycle_label = QtWidgets.QLabel('N Cycles')
        self.settings_widget.layout().addWidget(self.cycle_label, 0, 0)
        self.cycle_field = QtWidgets.QDoubleSpinBox()
        self.settings_widget.layout().addWidget(self.cycle_field, 0, 1)
        self.recipe_control_widget.layout().addWidget(self.settings_widget)

        self.recipe.settings.cycles.connect_to_widget(self.cycle_field)

        self.current_cycle_label = QtWidgets.QLabel('Cycles Completed')
        self.current_cycle_field = QtWidgets.QDoubleSpinBox()
        self.settings_widget.layout().addWidget(self.current_cycle_label, 0, 2)
        self.settings_widget.layout().addWidget(self.current_cycle_field, 0, 3)

        self.recipe.settings.cycles_completed.connect_to_widget(
            self.current_cycle_field)

        self.method_select_label = QtWidgets.QLabel('t3 Method')
        self.method_select_comboBox = QtWidgets.QComboBox()
        self.settings_widget.layout().addWidget(self.method_select_label, 1, 2)
        self.settings_widget.layout().addWidget(self.method_select_comboBox, 1,
                                                3)

        self.recipe.settings.t3_method.connect_to_widget(
            self.method_select_comboBox)

        # Subroutine Table Widget

        #         self.subroutine_table_widget = QtWidgets.QWidget()
        #         self.subroutine_layout = QtWidgets.QHBoxLayout()
        #         self.subroutine_table_widget.setLayout(self.subroutine_layout)
        #         self.subroutine_label = QtWidgets.QLabel('Subroutine [s]')
        #         self.subroutine_table_widget.layout().addWidget(self.subroutine_label)
        #
        #         self.subroutine_table = QtWidgets.QTableView()
        #         self.subroutine_table.setMaximumHeight(65)
        #         sub_names = ['Cycles', 't'+u'\u2080'+' PV2', 't'+u'\u2081'+' Purge']
        #         self.subtableModel = ArrayLQ_QTableModel(self.recipe.settings.subroutine, col_names=sub_names)
        #         self.subroutine_table.setModel(self.subtableModel)
        #         self.subroutine_table_widget.layout().addWidget(self.subroutine_table)
        #         self.recipe_control_widget.layout().addWidget(self.subroutine_table_widget)

        ## Main Table Widget
        self.table_widget = QtWidgets.QWidget()
        self.table_widget_layout = QtWidgets.QHBoxLayout()
        self.table_widget.setLayout(self.table_widget_layout)

        self.pulse_table = QtWidgets.QTableView()


        column_labels = ['t'+u'\u2080'+' Pre Purge', 't'+u'\u2081'+' (TiCl'+u'\u2084'+' PV)',\
                  't'+u'\u2082'+' Purge', 't'+u'\u2083'+' (N'+u'\u2082'+'/Shutter)', \
                 't'+u'\u2084'+' Purge', 't'+u'\u2085'+' Post Purge', u'\u03a3'+'t'+u'\u1d62']
        row_labels = ['Time [s]', 'Position (0,1000)']

        self.tableModel = ArrayLQ_QTableModel(
            self.recipe.settings.recipe_array,
            col_names=column_labels,
            row_names=row_labels)
        self.pulse_table.setModel(self.tableModel)
        self.table_widget.layout().addWidget(self.pulse_table)
        self.recipe_control_widget.layout().addWidget(self.table_widget)

        self.recipe_panel = QtWidgets.QWidget()
        self.recipe_panel_layout = QtWidgets.QGridLayout()
        self.recipe_panel.setLayout(self.recipe_panel_layout)
        self.recipe_panel.setStyleSheet(self.cb_stylesheet)

        self.start_button = QtWidgets.QPushButton('Start Recipe')
        self.start_button.clicked.connect(self.recipe.start)
        self.recipe_panel.layout().addWidget(self.start_button, 0, 1)

        self.abort_button = QtWidgets.QPushButton('Abort Recipe')
        self.abort_button.clicked.connect(self.recipe.interrupt)
        self.recipe_panel.layout().addWidget(self.abort_button, 0, 2)

        self.recipe_complete_subpanel = QtWidgets.QWidget()
        self.recipe_complete_subpanel.setLayout(QtWidgets.QHBoxLayout())
        self.recipe_ready_label = QtWidgets.QLabel('Recipe Complete')
        self.recipe_ready_indicator = QtWidgets.QCheckBox()
        self.recipe_complete_subpanel.layout().addWidget(
            self.recipe_ready_label)
        self.recipe_complete_subpanel.layout().addWidget(
            self.recipe_ready_indicator)
        self.recipe_panel.layout().addWidget(self.recipe_complete_subpanel, 0,
                                             3)

        self.recipe_control_widget.layout().addWidget(self.recipe_panel)

        self.recipe.settings.recipe_completed.connect_to_widget(
            self.recipe_ready_indicator)

    def setup_display_controls(self):
        """
        Creates a dockArea widget containing other parameters to be set by the 
        end user, including the length of plot time history and temperature data 
        export path.
        """

        self.display_control_widget = QtWidgets.QGroupBox(
            'Display Control Panel')
        self.display_control_widget.setLayout(QtWidgets.QVBoxLayout())

        self.field_panel = QtWidgets.QWidget()
        self.field_panel_layout = QtWidgets.QGridLayout()
        self.field_panel.setLayout(self.field_panel_layout)
        self.display_control_widget.layout().addWidget(self.field_panel)

        self.export_button = QtWidgets.QPushButton('Export Temperature Data')
        self.save_field = QtWidgets.QLineEdit('Directory')
        #         self.save_field.setMinimumWidth(200)
        #         self.save_field.setMaximumWidth(600)

        self.field_panel.layout().addWidget(self.export_button, 1, 0)
        self.field_panel.layout().addWidget(self.save_field, 1, 1)

        self.export_button.clicked.connect(self.export_to_disk)
        self.settings.save_path.connect_to_widget(self.save_field)

        plot_ui_list = ('display_window', 'history_length')
        self.field_panel.layout().addWidget(
            self.settings.New_UI(include=plot_ui_list), 2, 0)

    def setup_buffers_constants(self):
        '''Creates constants and storage arrays to be used in this module.'''
        home = os.path.expanduser("~")
        self.path = home + '\\Desktop\\'
        self.full_file_path = self.path + 'np_export'
        self.psu_connected = None
        self.HIST_LEN = self.settings.history_length.val
        self.WINDOW = self.settings.display_window.val
        self.RF_CHANS = 2
        self.T_CHANS = 1
        self.P_CHANS = 3
        self.history_i = 0
        self.index = 0

        self.rf_history = np.zeros((self.RF_CHANS, self.HIST_LEN))
        self.thermal_history = np.zeros((self.T_CHANS, self.HIST_LEN))
        self.pressure_history = np.zeros((self.P_CHANS, self.HIST_LEN))
        self.time_history = np.zeros((1, self.HIST_LEN), dtype='datetime64[s]')
        self.debug_mode = False

    def plot_routine(self):
        '''This function reads from *LoggedQuantities* and stores them in arrays. 
        These arrays are then plotted by :meth:`update_display`'''
        if self.debug_mode:
            RF_entry = np.random.rand(self.RF_CHANS, )

            T_entry = np.random.rand(self.T_CHANS, )
            P_entry = np.random.rang(self.P_CHANS, )
        else:
            RF_entry = np.array([self.seren.settings['forward_power_readout'], \
                             self.seren.settings['reflected_power']])
            T_entry = np.array([self.lovebox.settings['pv_temp']])
            P_entry = np.array([
                self.vgc.settings['ch1_pressure_scaled'],
                self.vgc.settings['ch2_pressure_scaled'],
                self.vgc.settings['ch3_pressure_scaled']
            ])

        time_entry = datetime.datetime.now()
        if self.history_i < self.HIST_LEN - 1:
            self.index = self.history_i % self.HIST_LEN
        else:
            self.index = self.HIST_LEN - 1
            self.rf_history = np.roll(self.rf_history, -1, axis=1)
            self.thermal_history = np.roll(self.thermal_history, -1, axis=1)
            self.pressure_history = np.roll(self.pressure_history, -1, axis=1)
            self.time_history = np.roll(self.time_history, -1, axis=1)
        self.rf_history[:, self.index] = RF_entry
        self.thermal_history[:, self.index] = T_entry
        self.pressure_history[:, self.index] = P_entry
        self.time_history[:, self.index] = time_entry
        self.history_i += 1

    def export_to_disk(self):
        """
        Exports numpy data arrays to disk. Writes to 
        :attr:`self.settings.save_path`
        Function connected to and called by 
        :attr:`self.export_button`
        """
        path = self.settings['save_path']
        np.save(path + '_temperature.npy', self.thermal_history)
        np.save(path + '_times.npy', self.time_history)

    def update_display(self):
        """
        **IMPORTANT:** *Do not call this function. The core framework already does so.*
        
        Built in ScopeFoundry function is called repeatedly. 
        Its purpose is to update UI plot objects.
        This particular function updates plot objects depicting 
        stage temperature, RF power levels, and the MFC flow rate and setpoint.
        """
        self.WINDOW = self.settings.display_window.val
        self.vLine1.setPos(self.WINDOW)
        self.vLine2.setPos(self.WINDOW)
        self.vLine3.setPos(self.WINDOW)

        lovebox_level = self.lovebox.settings['sv_setpoint']
        self.hLine1.setPos(lovebox_level)

        lower = self.index - self.WINDOW

        for i in range(self.T_CHANS):
            if self.index >= self.WINDOW:
                self.thermal_plot_lines[i].setData(
                    self.thermal_history[i, lower:self.index + 1])

            else:
                self.thermal_plot_lines[i].setData(
                    self.thermal_history[i, :self.index + 1])
                self.vLine1.setPos(self.index)
#                 self.vLine2.setPos(self.index)

        for i in range(self.RF_CHANS):
            if self.index >= self.WINDOW:
                self.rf_plot_lines[i].setData(
                    self.rf_history[i, lower:self.index + 1])
            else:
                self.rf_plot_lines[i].setData(self.rf_history[i, :self.index +
                                                              1])
                #                 self.vLine1.setPos(self.index)
                self.vLine2.setPos(self.index)

        for i in range(self.P_CHANS):
            if self.index >= self.WINDOW:
                self.vgc_plot_lines[i].setData(
                    self.pressure_history[i, lower:self.index + 1])

            else:
                self.vgc_plot_lines[i].setData(
                    self.pressure_history[i, :self.index + 1])
                self.vLine3.setPos(self.index)

    def conditions_check(self):
        """Checks ALD system conditions. Conditions that are met appear with 
        green LED indicators in the Conditions Widget groupBox."""
        self.pumped_check()
        if hasattr(self, 'mks146'):
            self.gases_check()
        else:
            pass
#             print('mks146 not currently active.')

        self.deposition_check()
        self.substrate_check()
        self.vent_check()

    def run(self):
        dt = 0.1
        while not self.interrupt_measurement_called:
            self.conditions_check()
            self.plot_routine()
            time.sleep(dt)

    def pumped_check(self):
        '''Checks if ALD system is properly pumped down.'''
        Z = 1e-3
        P = self.vgc.settings['ch3_pressure_scaled']
        self.recipe.settings['pumped'] = (P < Z)
#         if self.recipe.settings['pumped']:
#             self.pre_deposition_button.setEnabled(True)
#             self.pre_deposition_button.clicked.connect(self.recipe.predeposition)
#         else:
#             self.pre_deposition_button.setEnabled(False)

    def gases_check(self):
        '''Checks if MFC flow and system pressure conditions are ideal
        for deposition. 

        Should its conditions be satisfied, its *LoggedQuantity* is updated
        and its respective LED indicator in the Conditions Widget groupBox will appear green.'''
        flow = self.mks146.settings['MFC0_flow']
        condition = (0.7 <= flow)
        self.recipe.settings['gases_ready'] = condition

    def substrate_check(self):
        '''
        Checks if stage is adequately heated.
        
        Should its condition be satisfied, its *LoggedQuantity* is updated 
        and its respective LED indicator in the Conditions Widget groupBox will appear green.
        '''
        T = self.lovebox.settings['sv_setpoint']
        pv = self.lovebox.settings['pv_temp']
        condition = (0.9 * T <= pv <= 1.1 * T)
        self.recipe.settings['substrate_hot'] = condition

    def deposition_check(self):
        """
        Checks if deposition conditions are met.
        
        Conditions include:
         * System pumped
         * Gases ready
         * RF enabled
         * Substrate temperature hot.
         
        Button which initiates deposition is enabled only upon the 
        satisfaction of the above 4 requirements.
        """
        condition1 = self.recipe.settings['pumped']
        condition2 = self.recipe.settings['gases_ready']
        condition3 = self.seren.settings['RF_enable']
        condition4 = self.recipe.settings['substrate_hot']
        if condition1 and condition2 and condition3 and condition4:
            self.deposition_button.setEnabled(True)
        else:
            self.deposition_button.setEnabled(False)

    def vent_check(self):
        """
        Checks whether predeposition and deposition stages have been completed. 
        
        Should its conditions be satisfied, its *LoggedQuantity* is updated 
        and its respective LED indicator in the Conditions Widget groupBox will appear green.
        """
        if self.recipe.dep_complete and self.recipe.predep_complete:
            self.vent_button.setEnabled(True)
        else:
            self.vent_button.setEnabled(False)
        pass
Exemple #5
0
class Pfeiffer_VGC_Measure(Measurement):
    """
    This class creates a measurement tab in the ScopeFoundry MDI interface 
    a widget contained in which, contains live plotting features and export features. 
    
    This allows the user to not only monitor the pressure history with respect to time, 
    but also dump the recorded arrays, :attr:`self.pressure_history` and :attr:`self.time_history` 
    to export .npy files.
    """

    name = "pfeiffer_vgc_measure"

    def __init__(self, app):
        Measurement.__init__(self, app)

    def setup(self):
        """Establishes *LoggedQuantities* and their initial values."""
        self.settings.New('display_window', dtype=int, initial=1e4, vmin=200)
        self.settings.New('history_length', dtype=int, initial=1e6, vmin=1000)
        self.setup_buffers_constants()
        self.settings.New('save_path',
                          dtype=str,
                          initial=self.full_file_path,
                          ro=False)

        self.ui_enabled = True
        if self.ui_enabled:
            self.ui_setup()

        if hasattr(self.app.hardware, 'pfeiffer_vgc_hw'):
            self.vgc = self.app.hardware['pfeiffer_vgc_hw']
        else:
            print("Connect Pfeiffer HW component first.")

    def ui_setup(self):
        '''Calls all functions needed to set up UI programmatically.
        This object is called from :meth:`setup`'''
        self.ui = DockArea()
        self.layout = QtWidgets.QVBoxLayout()
        self.ui.show()
        self.ui.setLayout(self.layout)
        self.ui.setWindowTitle('Pfeiffer Controller Pressure History')
        self.widget_setup()
        self.dockArea_setup()

    def dockArea_setup(self):
        """
        Creates dock objects and determines order of dockArea widget placement in UI.
        
        This function is called from 
        :meth:`ui_setup`
        """
        self.ui.addDock(name='Pressure History',
                        position='top',
                        widget=self.group_widget)
        self.ui.addDock(name='NumPy Export',
                        position='bottom',
                        widget=self.export_widget)

    def widget_setup(self):
        """
        Runs collection of widget setup functions each of which creates the widget 
        and then populates them.
        This function is called from 
        :meth:`ui_setup`
        """
        self.setup_plot_group_widget()
        self.setup_export_widget()

    def setup_export_widget(self):
        """
        Creates export widget in measurement UI
        
        Called from 
        :meth:`widget_setup`
        """
        self.export_widget = QtWidgets.QGroupBox('Pressure Export')
        self.export_widget.setLayout(QtWidgets.QHBoxLayout())
        self.export_widget.setMaximumHeight(100)
        self.export_button = QtWidgets.QPushButton('Export Pressure Data')
        self.save_field = QtWidgets.QLineEdit('Directory')
        self.export_widget.layout().addWidget(self.export_button)
        self.export_widget.layout().addWidget(self.save_field)
        self.export_button.clicked.connect(self.export_to_disk)
        self.settings.save_path.connect_to_widget(self.save_field)

    def setup_plot_group_widget(self):
        """
        Creates plot objects in measurement UI. These are updated by 
        :meth:`update_display`
        
        Called from 
        :meth:`widget_setup`
        """
        self.group_widget = QtWidgets.QGroupBox('Pfeiffer VGC Measure')
        self.group_widget.setLayout(QtWidgets.QVBoxLayout())

        self.control_widget = QtWidgets.QWidget()
        self.control_widget.setLayout(QtWidgets.QHBoxLayout())

        self.group_widget.layout().addWidget(self.control_widget, stretch=0)

        self.display_label = QtWidgets.QLabel('Display Window')
        self.history_label = QtWidgets.QLabel('History Length')
        self.display_field = QtWidgets.QLineEdit()
        self.history_field = QtWidgets.QLineEdit()

        self.control_widget.layout().addWidget(self.display_label)
        self.control_widget.layout().addWidget(self.display_field)
        self.control_widget.layout().addWidget(self.history_label)
        self.control_widget.layout().addWidget(self.history_field)

        self.settings.display_window.connect_to_widget(self.display_field)
        self.settings.history_length.connect_to_widget(self.history_field)

        self.start_button = QtWidgets.QPushButton('Start')
        self.stop_button = QtWidgets.QPushButton('Stop')
        self.control_widget.layout().addWidget(self.start_button)
        self.control_widget.layout().addWidget(self.stop_button)
        self.start_button.clicked.connect(self.start)
        self.stop_button.clicked.connect(self.interrupt)

        self.plot_widget = pg.GraphicsLayoutWidget()

        self.plot = self.plot_widget.addPlot(title='Chamber Pressures')
        self.plot.setLogMode(y=True)
        self.plot.setYRange(-8, 2)
        self.plot.showGrid(y=True)
        self.plot.addLegend()
        self.plot_names = ['TKP_1', 'TKP_2', 'PKR_3', 'MAN_4']
        self.plot_lines = []
        for i in range(self.NUM_CHANS):
            color = pg.intColor(i)
            plot_line = self.plot.plot([1],
                                       pen=pg.mkPen(color, width=2),
                                       name=self.plot_names[i])
            self.plot_lines.append(plot_line)
        self.vLine = pg.InfiniteLine(angle=90, movable=False)
        self.plot.addItem(self.vLine)

        self.group_widget.layout().addWidget(self.plot_widget)

    def db_connect(self):
        """
        Creates and establishes database object to be utilized in 
        measurement routine as a method of data transcription.
        
        Currently not in use.
        """
        self.database = SQLite_Wrapper()
        self.server_connected = True
        self.database.setup_table()
        self.database.setup_index()

    def setup_buffers_constants(self):
        """
        Creates constants for use in live plotting features.
        
        This function reads from the following logged quantities and their initial values 
        to create initial arrays.
        
        ==================  ==========  ==========================================================
        **LoggedQuantity**  **Type**    **Description**
        history_length      int         Length of data array to record pressure values over time.

        display_window      int         Section of data array to be displayed in live plot.
        ==================  ==========  ==========================================================

        Called once from 
        :meth:`setup`
        """
        home = os.path.expanduser("~")
        self.path = home + '\\Desktop\\'
        self.full_file_path = self.path + 'np'
        self.HIST_LEN = self.settings.history_length.val
        self.WINDOW = self.settings.display_window.val
        self.NUM_CHANS = 4
        self.history_i = 0
        self.index = 0
        self.pressure_history = np.zeros((self.NUM_CHANS, self.HIST_LEN))
        self.time_history = np.zeros((1, self.HIST_LEN), dtype='datetime64[s]')
        self.debug_mode = False

    def read_pressures(self):
        """
        Reads data off of *LoggedQuantities* and stores the in numpy array
        
        Called from 
        :meth:`routine`
        
        :returns: Numpy array of pressure values taken from Pfeiffer Vacuum Gauge controller.
        """
        measurements = []
        for i in (1, 2, 3):
            _measure = self.vgc.settings['ch{}_pressure_scaled'.format(i)]
            measurements.append(_measure)
        return np.array(measurements)

    def routine(self):
        """
        Reads data off of *LoggedQuantities* and stores them in Numpy array
        :attr:`self.pressure_history`
        
        :attr:`self.read_pressures` is called to obtain pressures from Pfeiffer VGC. 
        This includes for following measurements:
        
        * Pirani Gauge 
        * Compact Full Range Gauge 
        
        A direct read from logged quantity 
        :attr:`app.hardware.mks_600_hw.settings.pressure`
        yields a pressure reading from the manometer installed on the MKS 600 
        pressure regulator.
        """
        if self.debug_mode:
            readout = np.random.rand(3, )
            man_readout = np.random.rand(1, )
        else:
            readout = self.read_pressures()
            if hasattr(self.app.hardware, 'mks_600_hw'):
                man_readout = np.array(
                    self.app.hardware['mks_600_hw'].settings['pressure'])
            else:
                man_readout = np.array(0)

        time_entry = datetime.datetime.now()
        if self.history_i < self.HIST_LEN - 1:
            self.index = self.history_i % self.HIST_LEN
        else:
            self.index = self.HIST_LEN - 1
            self.pressure_history = np.roll(self.pressure_history, -1, axis=1)
            self.time_history = np.roll(self.time_history, -1, axis=1)
        self.pressure_history[:3, self.index] = readout
        self.pressure_history[3, self.index] = man_readout + 1e-5
        self.history_i += 1

    def export_to_disk(self):
        """
        Exports numpy data arrays to disk. Writes to 
        :attr:`self.settings.save_path`
        Function connected to and called by 
        :attr:`self.export_button`
        """
        path = self.settings['save_path']
        np.save(path + '_pressures.npy', self.pressure_history)
        np.save(path + '_times.npy', self.time_history)

    def update_display(self):
        """
        Updates plots with data written to Numpy array
        :attr:`self.pressure_history`
        This function is automatically called repeatedly when 
        the measurement module is started.
        """
        self.WINDOW = self.settings.display_window.val
        self.vLine.setPos(self.WINDOW)

        lower = self.index - self.WINDOW

        for i in range(self.NUM_CHANS):
            if self.index >= self.WINDOW:
                self.plot_lines[i].setData(
                    self.pressure_history[i, lower:self.index + 1])
            else:
                self.plot_lines[i].setData(
                    self.pressure_history[i, :self.index + 1])
                self.vLine.setPos(self.index)

    def reconnect_server(self):
        """Connects to database wrapper.
        Currently not in use."""
        self.database.connect()

    def disconnect_server(self):
        """Disconnects from database wrapper.
        Currently not in use.
        """
        self.database.closeout()

    def run(self):
        dt = 0.05
        self.HIST_LEN = self.settings['history_length']
        while not self.interrupt_measurement_called:
            self.vgc.settings.ch1_pressure.read_from_hardware()
            self.vgc.settings.ch2_pressure.read_from_hardware()
            self.vgc.settings.ch3_pressure.read_from_hardware()
            self.routine()
            time.sleep(dt)
Exemple #6
0
class FileWidget(QWidget):

    file_scrambled = pyqtSignal(str)

    def __init__(self,
                 file_name,
                 with_dots,
                 subplots=False,
                 subplots_link=False,
                 *args,
                 **kwargs):

        super().__init__(*args, **kwargs)
        uic.loadUi(HERE.joinpath("..", "ui", "file_widget.ui"), self)

        self._timer = QTimer()
        self._timer.timeout.connect(self._mark_active_plot)

        file_name = Path(file_name)
        self.subplots = subplots
        self.subplots_link = subplots_link

        self.file_name = file_name
        self.progress = None
        self.mdf = None
        self.info = None
        self.info_index = None
        self.with_dots = with_dots

        progress = QProgressDialog(f'Opening "{self.file_name}"', "", 0, 100,
                                   self.parent())

        progress.setWindowModality(Qt.ApplicationModal)
        progress.setCancelButton(None)
        progress.setAutoClose(True)
        progress.setWindowTitle("Opening measurement")
        icon = QIcon()
        icon.addPixmap(QPixmap(":/open.png"), QIcon.Normal, QIcon.Off)
        progress.setWindowIcon(icon)
        progress.show()

        if file_name.suffix.lower() == ".erg":
            progress.setLabelText("Converting from erg to mdf")
            try:
                from mfile import ERG

                self.mdf = ERG(file_name).export_mdf()
            except Exception as err:
                print(err)
                return
        else:

            if file_name.suffix.lower() == ".dl3":
                progress.setLabelText("Converting from dl3 to mdf")
                datalyser_active = any(proc.name() == 'Datalyser3.exe'
                                       for proc in psutil.process_iter())
                try:
                    import win32com.client

                    index = 0
                    while True:
                        mdf_name = file_name.with_suffix(f".{index}.mdf")
                        if mdf_name.exists():
                            index += 1
                        else:
                            break

                    datalyser = win32com.client.Dispatch(
                        "Datalyser3.Datalyser3_COM")
                    if not datalyser_active:
                        try:
                            datalyser.DCOM_set_datalyser_visibility(False)
                        except:
                            pass
                    datalyser.DCOM_convert_file_mdf_dl3(
                        file_name, str(mdf_name), 0)
                    if not datalyser_active:
                        datalyser.DCOM_TerminateDAS()
                    file_name = mdf_name
                except Exception as err:
                    print(err)
                    return

            target = MDF
            kwargs = {"name": file_name, "callback": self.update_progress}

            self.mdf = run_thread_with_progress(
                self,
                target=target,
                kwargs=kwargs,
                factor=33,
                offset=0,
                progress=progress,
            )

            if self.mdf is TERMINATED:
                return

        progress.setLabelText("Loading graphical elements")

        progress.setValue(35)

        self.filter_field = SearchWidget(self.mdf.channels_db, self)

        progress.setValue(37)

        splitter = QSplitter(self)
        splitter.setOrientation(Qt.Vertical)

        channel_and_search = QWidget(splitter)

        self.channels_tree = TreeWidget(channel_and_search)
        self.search_field = SearchWidget(self.mdf.channels_db,
                                         channel_and_search)
        self.filter_tree = TreeWidget()

        self.search_field.selectionChanged.connect(
            partial(
                self.new_search_result,
                tree=self.channels_tree,
                search=self.search_field,
            ))
        self.filter_field.selectionChanged.connect(
            partial(self.new_search_result,
                    tree=self.filter_tree,
                    search=self.filter_field))

        vbox = QVBoxLayout(channel_and_search)
        vbox.setSpacing(2)
        self.advanced_search_btn = QPushButton("", channel_and_search)
        icon = QIcon()
        icon.addPixmap(QPixmap(":/search.png"), QIcon.Normal, QIcon.Off)
        self.advanced_search_btn.setIcon(icon)
        self.advanced_search_btn.setToolTip(
            "Advanced search and select channels")
        self.advanced_search_btn.clicked.connect(self.search)
        vbox.addWidget(self.search_field)

        vbox.addWidget(self.channels_tree, 1)

        hbox = QHBoxLayout()

        self.clear_channels_btn = QPushButton("", channel_and_search)
        self.clear_channels_btn.setToolTip("Reset selection")
        icon = QIcon()
        icon.addPixmap(QPixmap(":/erase.png"), QIcon.Normal, QIcon.Off)
        self.clear_channels_btn.setIcon(icon)
        self.clear_channels_btn.setObjectName("clear_channels_btn")

        self.load_channel_list_btn = QPushButton("", channel_and_search)
        self.load_channel_list_btn.setToolTip("Load channel selection list")
        icon1 = QIcon()
        icon1.addPixmap(QPixmap(":/open.png"), QIcon.Normal, QIcon.Off)
        self.load_channel_list_btn.setIcon(icon1)
        self.load_channel_list_btn.setObjectName("load_channel_list_btn")

        self.save_channel_list_btn = QPushButton("", channel_and_search)
        self.save_channel_list_btn.setToolTip("Save channel selection list")
        icon2 = QIcon()
        icon2.addPixmap(QPixmap(":/save.png"), QIcon.Normal, QIcon.Off)
        self.save_channel_list_btn.setIcon(icon2)
        self.save_channel_list_btn.setObjectName("save_channel_list_btn")

        self.select_all_btn = QPushButton("", channel_and_search)
        self.select_all_btn.setToolTip("Select all channels")
        icon1 = QIcon()
        icon1.addPixmap(QPixmap(":/checkmark.png"), QIcon.Normal, QIcon.Off)
        self.select_all_btn.setIcon(icon1)

        hbox.addWidget(self.load_channel_list_btn)
        hbox.addWidget(self.save_channel_list_btn)
        line = QFrame()
        line.setFrameShape(QFrame.VLine)
        line.setFrameShadow(QFrame.Sunken)
        hbox.addWidget(line)
        hbox.addWidget(self.select_all_btn)
        hbox.addWidget(self.clear_channels_btn)
        line = QFrame()
        line.setFrameShape(QFrame.VLine)
        line.setFrameShadow(QFrame.Sunken)
        hbox.addWidget(line)
        hbox.addWidget(self.advanced_search_btn)
        line = QFrame()
        line.setFrameShape(QFrame.VLine)
        line.setFrameShadow(QFrame.Sunken)
        hbox.addWidget(line)
        self.plot_btn = QPushButton("", channel_and_search)
        self.plot_btn.setToolTip("Plot selected channels")
        icon3 = QIcon()
        icon3.addPixmap(QPixmap(":/graph.png"), QIcon.Normal, QIcon.Off)
        self.plot_btn.setIcon(icon3)
        self.plot_btn.setObjectName("plot_btn")
        hbox.addWidget(self.plot_btn)
        hbox.addSpacerItem(
            QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
        vbox.addLayout(hbox)

        self.dock_area = DockArea()
        self.splitter.addWidget(self.dock_area)

        self.filter_layout.addWidget(self.filter_field, 0, 0, 1, 1)

        self.channels_tree.itemDoubleClicked.connect(self.show_channel_info)
        self.filter_tree.itemDoubleClicked.connect(self.show_channel_info)

        self.channels_layout.insertWidget(0, splitter)
        self.filter_layout.addWidget(self.filter_tree, 1, 0, 8, 1)

        groups_nr = len(self.mdf.groups)

        self.channels_tree.setHeaderLabel("Channels")
        self.channels_tree.setToolTip(
            "Double click channel to see extended information")
        self.filter_tree.setHeaderLabel("Channels")
        self.filter_tree.setToolTip(
            "Double click channel to see extended information")

        flags = None

        for i, group in enumerate(self.mdf.groups):
            channel_group = QTreeWidgetItem()
            filter_channel_group = QTreeWidgetItem()
            channel_group.setText(0, f"Channel group {i}")
            filter_channel_group.setText(0, f"Channel group {i}")
            channel_group.setFlags(channel_group.flags() | Qt.ItemIsTristate
                                   | Qt.ItemIsUserCheckable)
            filter_channel_group.setFlags(filter_channel_group.flags()
                                          | Qt.ItemIsTristate
                                          | Qt.ItemIsUserCheckable)

            self.channels_tree.addTopLevelItem(channel_group)
            self.filter_tree.addTopLevelItem(filter_channel_group)

            group_children = []
            filter_children = []

            for j, ch in enumerate(group.channels):
                entry = i, j

                name = self.mdf.get_channel_name(i, j)
                channel = TreeItem(entry)
                if flags is None:
                    flags = channel.flags() | Qt.ItemIsUserCheckable
                channel.setFlags(flags)
                channel.setText(0, name)
                channel.setCheckState(0, Qt.Unchecked)
                group_children.append(channel)

                channel = TreeItem(entry)
                channel.setFlags(flags)
                channel.setText(0, name)
                channel.setCheckState(0, Qt.Unchecked)
                filter_children.append(channel)

            if self.mdf.version >= "4.00":
                for j, ch in enumerate(group.logging_channels, 1):
                    name = ch.name
                    entry = i, -j

                    channel = TreeItem(entry)
                    channel.setFlags(flags)
                    channel.setText(0, name)
                    channel.setCheckState(0, Qt.Unchecked)
                    group_children.append(channel)

                    channel = TreeItem(entry)
                    channel.setFlags(flags)
                    channel.setText(0, name)
                    channel.setCheckState(0, Qt.Unchecked)
                    filter_children.append(channel)

            channel_group.addChildren(group_children)
            filter_channel_group.addChildren(filter_children)

            del group_children
            del filter_children

            progress.setValue(37 + int(53 * (i + 1) / groups_nr))

        progress.setValue(90)

        self.resample_format.insertItems(0, SUPPORTED_VERSIONS)
        index = self.resample_format.findText(self.mdf.version)
        if index >= 0:
            self.resample_format.setCurrentIndex(index)
        self.resample_compression.insertItems(
            0, ("no compression", "deflate", "transposed deflate"))
        self.resample_split_size.setValue(10)
        self.resample_btn.clicked.connect(self.resample)

        self.filter_format.insertItems(0, SUPPORTED_VERSIONS)
        index = self.filter_format.findText(self.mdf.version)
        if index >= 0:
            self.filter_format.setCurrentIndex(index)
        self.filter_compression.insertItems(
            0, ("no compression", "deflate", "transposed deflate"))
        self.filter_split_size.setValue(10)
        self.filter_btn.clicked.connect(self.filter)

        self.convert_format.insertItems(0, SUPPORTED_VERSIONS)
        self.convert_compression.insertItems(
            0, ("no compression", "deflate", "transposed deflate"))
        self.convert_split_size.setValue(10)
        self.convert_btn.clicked.connect(self.convert)

        self.cut_format.insertItems(0, SUPPORTED_VERSIONS)
        index = self.cut_format.findText(self.mdf.version)
        if index >= 0:
            self.cut_format.setCurrentIndex(index)
        self.cut_compression.insertItems(
            0, ("no compression", "deflate", "transposed deflate"))
        self.cut_split_size.setValue(10)
        self.cut_btn.clicked.connect(self.cut)

        self.cut_interval.setText("Unknown measurement interval")

        progress.setValue(99)

        self.empty_channels.insertItems(0, ("zeros", "skip"))
        self.mat_format.insertItems(0, ("4", "5", "7.3"))
        self.oned_as.insertItems(0, ("row", "column"))
        self.export_type.insertItems(
            0, ("csv", "excel", "hdf5", "mat", "parquet"))
        self.export_btn.clicked.connect(self.export)
        self.export_type.currentTextChanged.connect(self.export_changed)
        self.export_type.setCurrentIndex(-1)

        # self.channels_tree.itemChanged.connect(self.select)
        self.plot_btn.clicked.connect(self.plot_pyqtgraph)
        self.clear_filter_btn.clicked.connect(self.clear_filter)
        self.clear_channels_btn.clicked.connect(self.clear_channels)

        self.aspects.setCurrentIndex(0)

        progress.setValue(100)

        self.load_channel_list_btn.clicked.connect(self.load_channel_list)
        self.save_channel_list_btn.clicked.connect(self.save_channel_list)
        self.load_filter_list_btn.clicked.connect(self.load_filter_list)
        self.save_filter_list_btn.clicked.connect(self.save_filter_list)

        self.scramble_btn.clicked.connect(self.scramble)

        self._dock_names = UniqueDB()
        self.active_plot = ""

    def export_changed(self, name):
        if name == 'parquet':
            self.export_compression.setEnabled(True)
            self.export_compression.clear()
            self.export_compression.addItems(['GZIP', 'SNAPPY'])
            self.export_compression.setCurrentIndex(-1)
        elif name == 'hdf5':
            self.export_compression.setEnabled(True)
            self.export_compression.clear()
            self.export_compression.addItems(["gzip", "lzf", "szip"])
            self.export_compression.setCurrentIndex(-1)
        elif name == 'mat':
            self.export_compression.setEnabled(True)
            self.export_compression.clear()
            self.export_compression.addItems(["enabled", "disabled"])
            self.export_compression.setCurrentIndex(-1)
        else:
            self.export_compression.clear()
            self.export_compression.setEnabled(False)

    def set_line_style(self, with_dots=None):
        if with_dots is not None:

            self.with_dots = with_dots

            current_plot = self.get_current_plot()
            if current_plot:
                current_plot.plot.update_lines(with_dots=with_dots)

    def set_subplots_link(self, subplots_link):
        self.subplots_link = subplots_link
        if subplots_link:
            viewbox = None
            for dock in self.dock_area.docks.values():
                for plt in dock.widgets:
                    if viewbox is None:
                        viewbox = plt.plot.viewbox
                    else:
                        plt.plot.viewbox.setXLink(viewbox)
        else:
            for dock in self.dock_area.docks.values():
                for plt in dock.widgets:
                    plt.plot.viewbox.setXLink(None)

    def save_all_subplots(self):
        file_name, _ = QFileDialog.getSaveFileName(
            self,
            "Select output measurement file",
            "",
            "MDF version 4 files (*.mf4)",
        )

        if file_name:
            with MDF() as mdf:
                for dock in self.dock_area.docks.values():
                    for plt in dock.widgets:

                        mdf.append(plt.plot.signals)
                mdf.save(file_name, overwrite=True)

    def search(self):
        dlg = AdvancedSearch(self.mdf.channels_db, parent=self)
        dlg.setModal(True)
        dlg.exec_()
        result = dlg.result
        if result:
            iterator = QTreeWidgetItemIterator(self.channels_tree)

            dg_cntr = -1
            ch_cntr = 0

            while iterator.value():
                item = iterator.value()
                if item.parent() is None:
                    iterator += 1
                    dg_cntr += 1
                    ch_cntr = 0
                    continue

                if (dg_cntr, ch_cntr) in result:
                    item.setCheckState(0, Qt.Checked)

                iterator += 1
                ch_cntr += 1

    def save_channel_list(self):
        file_name, _ = QFileDialog.getSaveFileName(
            self, "Select output channel list file", "", "TXT files (*.txt)")
        if file_name:
            with open(file_name, "w") as output:
                iterator = QTreeWidgetItemIterator(self.channels_tree)

                signals = []
                while iterator.value():
                    item = iterator.value()
                    if item.parent() is None:
                        iterator += 1
                        continue

                    if item.checkState(0) == Qt.Checked:
                        signals.append(item.text(0))

                    iterator += 1

                output.write("\n".join(signals))

    def load_channel_list(self):
        file_name, _ = QFileDialog.getOpenFileName(self,
                                                   "Select channel list file",
                                                   "", "TXT files (*.txt)")

        if file_name:
            with open(file_name, "r") as infile:
                channels = [line.strip() for line in infile.readlines()]
                channels = [name for name in channels if name]

            iterator = QTreeWidgetItemIterator(self.channels_tree)

            while iterator.value():
                item = iterator.value()
                if item.parent() is None:
                    iterator += 1
                    continue

                channel_name = item.text(0)
                if channel_name in channels:
                    item.setCheckState(0, Qt.Checked)
                    channels.pop(channels.index(channel_name))
                else:
                    item.setCheckState(0, Qt.Unchecked)

                iterator += 1

    def save_filter_list(self):
        file_name, _ = QFileDialog.getSaveFileName(
            self, "Select output filter list file", "", "TXT files (*.txt)")

        if file_name:
            with open(file_name, "w") as output:
                iterator = QTreeWidgetItemIterator(self.filter_tree)

                signals = []
                while iterator.value():
                    item = iterator.value()
                    if item.parent() is None:
                        iterator += 1
                        continue

                    if item.checkState(0) == Qt.Checked:
                        signals.append(item.text(0))

                    iterator += 1

                output.write("\n".join(signals))

    def load_filter_list(self):
        file_name, _ = QFileDialog.getOpenFileName(self,
                                                   "Select filter list file",
                                                   "", "TXT files (*.txt)")

        if file_name:
            with open(file_name, "r") as infile:
                channels = [line.strip() for line in infile.readlines()]
                channels = [name for name in channels if name]

            iterator = QTreeWidgetItemIterator(self.filter_tree)

            while iterator.value():
                item = iterator.value()
                if item.parent() is None:
                    iterator += 1
                    continue

                channel_name = item.text(0)
                if channel_name in channels:
                    item.setCheckState(0, Qt.Checked)
                    channels.pop(channels.index(channel_name))
                else:
                    item.setCheckState(0, Qt.Unchecked)

                iterator += 1

    def compute_cut_hints(self):
        # TODO : use master channel physical min and max values
        times = []
        groups_nr = len(self.mdf.groups)
        for i in range(groups_nr):
            master = self.mdf.get_master(i)
            if len(master):
                times.append(master[0])
                times.append(master[-1])
            QApplication.processEvents()

        if len(times):
            time_range = min(times), max(times)

            self.cut_start.setRange(*time_range)
            self.cut_stop.setRange(*time_range)

            self.cut_interval.setText(
                "Cut interval ({:.6f}s - {:.6f}s)".format(*time_range))
        else:
            self.cut_start.setRange(0, 0)
            self.cut_stop.setRange(0, 0)

            self.cut_interval.setText("Empty measurement")

    def update_progress(self, current_index, max_index):
        self.progress = current_index, max_index

    def show_channel_info(self, item, column):
        if item and item.parent():
            group, index = item.entry

            channel = self.mdf.get_channel_metadata(group=group, index=index)

            msg = ChannelInfoDialog(channel, self)
            msg.show()

    def clear_filter(self):
        iterator = QTreeWidgetItemIterator(self.filter_tree)

        while iterator.value():
            item = iterator.value()
            item.setCheckState(0, Qt.Unchecked)

            if item.parent() is None:
                item.setExpanded(False)

            iterator += 1

    def clear_channels(self):
        iterator = QTreeWidgetItemIterator(self.channels_tree)

        while iterator.value():
            item = iterator.value()
            item.setCheckState(0, Qt.Unchecked)

            if item.parent() is None:
                item.setExpanded(False)

            iterator += 1

    def get_current_plot(self):
        if self.active_plot:
            return self.dock_area.docks[self.active_plot].widgets[0]
        else:
            return None

    def new_search_result(self, tree, search):
        group_index, channel_index = search.entries[search.current_index]

        grp = self.mdf.groups[group_index]
        channel_count = len(grp.channels)

        iterator = QTreeWidgetItemIterator(tree)

        group = -1
        index = 0
        while iterator.value():
            item = iterator.value()
            if item.parent() is None:
                iterator += 1
                group += 1
                index = 0
                continue

            if group == group_index:

                if (channel_index >= 0 and index == channel_index
                        or channel_index < 0
                        and index == -channel_index - 1 + channel_count):
                    tree.scrollToItem(item, QAbstractItemView.PositionAtTop)
                    item.setSelected(True)

            index += 1
            iterator += 1

    def close(self):
        mdf_name = self.mdf.name
        self.mdf.close()
        if self.file_name.suffix.lower() == ".dl3":
            mdf_name.unlink()

    def convert(self, event):
        version = self.convert_format.currentText()

        if version < "4.00":
            filter = "MDF version 3 files (*.dat *.mdf)"
        else:
            filter = "MDF version 4 files (*.mf4)"

        split = self.convert_split.checkState() == Qt.Checked
        if split:
            split_size = int(self.convert_split_size.value() * 1024 * 1024)
        else:
            split_size = 0

        self.mdf.configure(write_fragment_size=split_size)

        compression = self.convert_compression.currentIndex()

        file_name, _ = QFileDialog.getSaveFileName(
            self, "Select output measurement file", "", filter)

        if file_name:

            progress = setup_progress(
                parent=self,
                title="Converting measurement",
                message=
                f'Converting "{self.file_name}" from {self.mdf.version} to {version}',
                icon_name="convert",
            )

            # convert self.mdf
            target = self.mdf.convert
            kwargs = {"version": version}

            mdf = run_thread_with_progress(
                self,
                target=target,
                kwargs=kwargs,
                factor=50,
                offset=0,
                progress=progress,
            )

            if mdf is TERMINATED:
                progress.cancel()
                return

            mdf.configure(write_fragment_size=split_size)

            # then save it
            progress.setLabelText(f'Saving converted file "{file_name}"')

            target = mdf.save
            kwargs = {
                "dst": file_name,
                "compression": compression,
                "overwrite": True
            }

            run_thread_with_progress(
                self,
                target=target,
                kwargs=kwargs,
                factor=50,
                offset=50,
                progress=progress,
            )

    def resample(self, event):
        version = self.resample_format.currentText()
        raster = self.raster.value()

        if version < "4.00":
            filter = "MDF version 3 files (*.dat *.mdf)"
        else:
            filter = "MDF version 4 files (*.mf4)"

        split = self.resample_split.checkState() == Qt.Checked
        if split:
            split_size = int(self.resample_split_size.value() * 1024 * 1024)
        else:
            split_size = 0

        self.mdf.configure(write_fragment_size=split_size)

        compression = self.resample_compression.currentIndex()

        file_name, _ = QFileDialog.getSaveFileName(
            self, "Select output measurement file", "", filter)

        if file_name:
            progress = setup_progress(
                parent=self,
                title="Resampling measurement",
                message=f'Resampling "{self.file_name}" to {raster}s raster ',
                icon_name="resample",
            )

            # resample self.mdf
            target = self.mdf.resample
            kwargs = {"raster": raster, "version": version}

            mdf = run_thread_with_progress(
                self,
                target=target,
                kwargs=kwargs,
                factor=66,
                offset=0,
                progress=progress,
            )

            if mdf is TERMINATED:
                progress.cancel()
                return

            mdf.configure(write_fragment_size=split_size)

            # then save it
            progress.setLabelText(f'Saving resampled file "{file_name}"')

            target = mdf.save
            kwargs = {
                "dst": file_name,
                "compression": compression,
                "overwrite": True
            }

            run_thread_with_progress(
                self,
                target=target,
                kwargs=kwargs,
                factor=34,
                offset=66,
                progress=progress,
            )

    def cut(self, event):
        version = self.cut_format.currentText()
        start = self.cut_start.value()
        stop = self.cut_stop.value()
        time_from_zero = self.cut_time_from_zero.checkState() == Qt.Checked

        if self.whence.checkState() == Qt.Checked:
            whence = 1
        else:
            whence = 0

        if version < "4.00":
            filter = "MDF version 3 files (*.dat *.mdf)"
        else:
            filter = "MDF version 4 files (*.mf4)"

        split = self.cut_split.checkState() == Qt.Checked
        if split:
            split_size = int(self.cut_split_size.value() * 1024 * 1024)
        else:
            split_size = 0

        self.mdf.configure(write_fragment_size=split_size)

        compression = self.cut_compression.currentIndex()

        file_name, _ = QFileDialog.getSaveFileName(
            self, "Select output measurement file", "", filter)

        if file_name:
            progress = setup_progress(
                parent=self,
                title="Cutting measurement",
                message='Cutting "{self.file_name}" from {start}s to {stop}s',
                icon_name="cut",
            )

            # cut self.mdf
            target = self.mdf.cut
            kwargs = {
                "start": start,
                "stop": stop,
                "whence": whence,
                "version": version,
                "time_from_zero": time_from_zero,
            }

            mdf = run_thread_with_progress(
                self,
                target=target,
                kwargs=kwargs,
                factor=66,
                offset=0,
                progress=progress,
            )

            if mdf is TERMINATED:
                progress.cancel()
                return

            mdf.configure(write_fragment_size=split_size)

            # then save it
            progress.setLabelText(f'Saving cut file "{file_name}"')

            target = mdf.save
            kwargs = {
                "dst": file_name,
                "compression": compression,
                "overwrite": True
            }

            run_thread_with_progress(
                self,
                target=target,
                kwargs=kwargs,
                factor=34,
                offset=66,
                progress=progress,
            )

    def export(self, event):
        export_type = self.export_type.currentText()

        single_time_base = self.single_time_base.checkState() == Qt.Checked
        time_from_zero = self.time_from_zero.checkState() == Qt.Checked
        use_display_names = self.use_display_names.checkState() == Qt.Checked
        empty_channels = self.empty_channels.currentText()
        mat_format = self.mat_format.currentText()
        raster = self.export_raster.value()
        oned_as = self.oned_as.currentText()
        reduce_memory_usage = self.reduce_memory_usage.checkState(
        ) == Qt.Checked
        compression = self.export_compression.currentText()

        filters = {
            "csv": "CSV files (*.csv)",
            "excel": "Excel files (*.xlsx)",
            "hdf5": "HDF5 files (*.hdf)",
            "mat": "Matlab MAT files (*.mat)",
            "parquet": "Apache Parquet files (*.parquet)",
        }

        file_name, _ = QFileDialog.getSaveFileName(self, "Select export file",
                                                   "", filters[export_type])

        if file_name:
            thr = Thread(
                target=self.mdf.export,
                kwargs={
                    "fmt": export_type,
                    "filename": file_name,
                    "single_time_base": single_time_base,
                    "use_display_names": use_display_names,
                    "time_from_zero": time_from_zero,
                    "empty_channels": empty_channels,
                    "format": mat_format,
                    "raster": raster,
                    "oned_as": oned_as,
                    "reduce_memory_usage": reduce_memory_usage,
                    "compression": compression,
                },
            )

            progress = QProgressDialog(f"Exporting to {export_type} ...",
                                       "Abort export", 0, 100)
            progress.setWindowModality(Qt.ApplicationModal)
            progress.setCancelButton(None)
            progress.setAutoClose(True)
            progress.setWindowTitle("Running export")
            icon = QIcon()
            icon.addPixmap(QPixmap(":/export.png"), QIcon.Normal, QIcon.Off)
            progress.setWindowIcon(icon)

            thr.start()

            cntr = 0

            while thr.is_alive():
                cntr += 1
                progress.setValue(cntr % 98)
                sleep(0.1)

            progress.cancel()

    def plot_pyqtgraph(self, event):
        try:
            iter(event)
            signals = event
        except:

            iterator = QTreeWidgetItemIterator(self.channels_tree)

            group = -1
            index = 0
            signals = []
            while iterator.value():
                item = iterator.value()
                if item.parent() is None:
                    iterator += 1
                    group += 1
                    index = 0
                    continue

                if item.checkState(0) == Qt.Checked:
                    group, index = item.entry
                    ch = self.mdf.groups[group].channels[index]
                    if not ch.component_addr:
                        signals.append((None, group, index))

                index += 1
                iterator += 1

            signals = self.mdf.select(signals)

            signals = [
                sig for sig in signals
                if not sig.samples.dtype.names and len(sig.samples.shape) <= 1
            ]

            signals = natsorted(signals, key=lambda x: x.name)

        if signals:
            if not self.subplots:
                wid = self.splitter.widget(1)
                wid.setParent(None)
                self.dock_area = DockArea(self.splitter)
                self.splitter.addWidget(self.dock_area)

            count = len(self.dock_area.docks)
            self.dock_area.hide()
            dock_name = self._dock_names.get_unique_name('Plot')
            dock = Dock(dock_name, size=(1, 1), closable=True)
            self.dock_area.addDock(dock)

            dock.label.sigClicked.connect(
                partial(self.mark_active_plot, dock_name))
            dock.sigClosed.connect(self.close_plot)

            plot = Plot(signals, self.with_dots)
            plot.plot.update_lines(force=True)
            plot.clicked.connect(partial(self.mark_active_plot, dock_name))
            plot.close_request.connect(partial(self.close_plot, dock))

            dock.addWidget(plot)

            self.dock_area.show()

            if count and self.subplots_link:
                plot.plot.viewbox.setXLink(
                    self.get_current_plot().plot.viewbox)

            self.splitter.setSizes([100, 100])

            self.mark_active_plot(dock_name)

        QApplication.processEvents()

    def close_plot(self, dock):
        self.dock_area.hide()
        dock_name = dock.label.text()
        self.dock_area.docks.pop(dock_name)
        if self.active_plot == dock_name:
            if self.dock_area.docks:
                new_active_plot = list(self.dock_area.docks)[0]
                self.mark_active_plot(new_active_plot)
        if not self.dock_area.docks:
            self.active_plot = ""
        self.dock_area.show()

    def mark_active_plot(self, plot_name):
        self.active_plot = plot_name
        self._timer.start(5)

    def _mark_active_plot(self):
        plot_name = self.active_plot

        for dock in self.dock_area.docks.values():
            if dock.label.text() == plot_name:
                dock.label.setStyleSheet("""DockLabel {
                background-color : rgb(94, 178, 226);
            }""")
            else:
                dock.label.setStyleSheet("""DockLabel {
                background-color : rgb(145, 145, 145);
            }""")

    def filter(self, event):
        iterator = QTreeWidgetItemIterator(self.filter_tree)

        group = -1
        index = 0
        channels = []
        while iterator.value():
            item = iterator.value()
            if item.parent() is None:
                iterator += 1
                group += 1
                index = 0
                continue

            if item.checkState(0) == Qt.Checked:
                channels.append((None, group, index))

            index += 1
            iterator += 1

        version = self.filter_format.itemText(
            self.filter_format.currentIndex())

        if version < "4.00":
            filter = "MDF version 3 files (*.dat *.mdf)"
        else:
            filter = "MDF version 4 files (*.mf4)"

        split = self.filter_split.checkState() == Qt.Checked
        if split:
            split_size = int(self.filter_split_size.value() * 1024 * 1024)
        else:
            split_size = 0

        self.mdf.configure(write_fragment_size=split_size)

        compression = self.filter_compression.currentIndex()

        file_name, _ = QFileDialog.getSaveFileName(
            self, "Select output measurement file", "", filter)

        if file_name:
            progress = setup_progress(
                parent=self,
                title="Filtering measurement",
                message=f'Filtering selected channels from "{self.file_name}"',
                icon_name="filter",
            )

            # filtering self.mdf
            target = self.mdf.filter
            kwargs = {"channels": channels, "version": version}

            mdf = run_thread_with_progress(
                self,
                target=target,
                kwargs=kwargs,
                factor=66,
                offset=0,
                progress=progress,
            )

            if mdf is TERMINATED:
                progress.cancel()
                return

            mdf.configure(write_fragment_size=split_size)

            # then save it
            progress.setLabelText(f'Saving filtered file "{file_name}"')

            target = mdf.save
            kwargs = {
                "dst": file_name,
                "compression": compression,
                "overwrite": True
            }

            run_thread_with_progress(
                self,
                target=target,
                kwargs=kwargs,
                factor=34,
                offset=66,
                progress=progress,
            )

    def scramble(self, event):

        progress = setup_progress(
            parent=self,
            title="Scrambling measurement",
            message=f'Scrambling "{self.file_name}"',
            icon_name="scramble",
        )

        # scrambling self.mdf
        target = MDF.scramble
        kwargs = {"name": self.file_name, "callback": self.update_progress}

        mdf = run_thread_with_progress(
            self,
            target=target,
            kwargs=kwargs,
            factor=100,
            offset=0,
            progress=progress,
        )

        if mdf is TERMINATED:
            progress.cancel()
            return

        self.file_scrambled.emit(
            str(Path(self.file_name).with_suffix(".scrambled.mf4")))