Exemple #1
0
class PlotCurve(CtrlNode):
    """Generates a plot curve from x/y data"""
    nodeName = 'PlotCurve'
    uiTemplate = [
        ('color', 'color'),
    ]

    def __init__(self, name):
        CtrlNode.__init__(self,
                          name,
                          terminals={
                              'x': {
                                  'io': 'in'
                              },
                              'y': {
                                  'io': 'in'
                              },
                              'plot': {
                                  'io': 'out'
                              }
                          })
        self.item = PlotDataItem()

    def process(self, x, y, display=True):
        #print "scatterplot process"
        if not display:
            return {'plot': None}

        self.item.setData(x, y, pen=self.ctrls['color'].color())
        return {'plot': self.item}
Exemple #2
0
 def update_line(self, index: int, lineplot: pg.PlotDataItem, orientation: str):
     """Template function for creating callbacks which update every PlotDataItem according to current cursor
     position."""
     linedata = self.data_slice1d(index)
     if orientation == 'h':
         lineplot.setData(self.data.axes[index], linedata)
     else:
         lineplot.setData(linedata, self.data.axes[index])
Exemple #3
0
 def update_line(self,
                 index: int,
                 lineplot: pg.PlotDataItem,
                 orientation: str,
                 _=None):
     """Template function for creating callbacks which update every PlotDataItem according to current cursor
     position."""
     x = self.cursor.get_cut(index).squeeze()
     if orientation == 'h':
         lineplot.setData(self.data.axes[index], x.values)
     else:
         lineplot.setData(x.values, self.data.axes[index])
class ComponentPlotWidget(SpectraPlotWidget):
    def __init__(self, *args, **kwargs):
        super(ComponentPlotWidget, self).__init__(linePos=800,
                                                  txtPosRatio=0.35,
                                                  *args,
                                                  **kwargs)
        self.cross = PlotDataItem([800], [0],
                                  symbolBrush=(255, 255, 255),
                                  symbolPen=(255, 255, 255),
                                  symbol='+',
                                  symbolSize=25)
        self._x, self._y = None, None
        self.ymax, self.zmax = 0, 100

    def getEnergy(self):
        if self._y is not None:
            x_val = self.line.value()
            idx = val2ind(x_val, self._x)
            x_val = self._x[idx]
            y_val = self._y[idx]
            txt_html = f'<div style="text-align: center"><span style="color: #FFF; font-size: 12pt">\
                                                 X = {x_val: .2f}, Y = {y_val: .4f}</div>'

            self.txt.setHtml(txt_html)
            self.cross.setData([x_val], [y_val])

    def plot(self, x, y, *args, **kwargs):
        # set up infinity line and get its position
        plot_item = self.plotItem.plot(x, y, *args, **kwargs)
        self.addItem(self.line)
        self.addItem(self.cross)
        x_val = self.line.value()
        idx = val2ind(x_val, x)
        x_val = x[idx]
        y_val = y[idx]
        txt_html = f'<div style="text-align: center"><span style="color: #FFF; font-size: 12pt">\
                                     X = {x_val: .2f}, Y = {y_val: .4f}</div>'

        self.txt.setHtml(txt_html)
        self.txt.setZValue(self.zmax - 1)
        self.cross.setData([x_val], [y_val])
        self.cross.setZValue(self.zmax)
        ymax = max(y)
        if ymax > self.ymax:
            self.ymax = ymax
        self._x, self._y = x, y
        r = self.txtPosRatio
        self.txt.setPos(r * x[-1] + (1 - r) * x[0], self.ymax)
        self.addItem(self.txt)
        return plot_item
Exemple #5
0
class PlotCurve(CtrlNode):
    """Generates a plot curve from x/y data"""

    nodeName = "PlotCurve"
    uiTemplate = [("color", "color")]

    def __init__(self, name):
        CtrlNode.__init__(self, name, terminals={"x": {"io": "in"}, "y": {"io": "in"}, "plot": {"io": "out"}})
        self.item = PlotDataItem()

    def process(self, x, y, display=True):
        # print "scatterplot process"
        if not display:
            return {"plot": None}

        self.item.setData(x, y, pen=self.ctrls["color"].color())
        return {"plot": self.item}
Exemple #6
0
class PlotCurve(CtrlNode):
    """Generates a plot curve from x/y data"""
    nodeName = 'PlotCurve'
    uiTemplate = [
        ('color', 'color'),
    ]
    
    def __init__(self, name):
        CtrlNode.__init__(self, name, terminals={
            'x': {'io': 'in'},
            'y': {'io': 'in'},
            'plot': {'io': 'out'}
        })
        self.item = PlotDataItem()
    
    def process(self, x, y, display=True):
        #print "scatterplot process"
        if not display:
            return {'plot': None}
        
        self.item.setData(x, y, pen=self.ctrls['color'].color())
        return {'plot': self.item}
    def set_experiment_view(self):
        self.ui.graphicsView.clear()
        plot = PlotDataItem(pen="k")

        duration = (self.ui.spinBoxExperimentDurationMinutes.value() *
                    60) + self.ui.spinBoxExperimentDurationSeconds.value()
        xs = np.linspace(0, duration, 2)
        ys = [1, 1]
        plot.setData(xs, ys)
        self.ui.graphicsView.addItem(plot)

        for ii in range(len(self.Stims.df)):
            start = self.Stims.df.time_on.iloc[ii]
            stop = self.Stims.df.time_off.iloc[ii]

            if self.Stims.df.message_on.iloc[ii].startswith("w"):
                box = LinearRegionItem(values=(start, stop),
                                       brush=(255, 255, 250, 230),
                                       movable=False)
                self.ui.graphicsView.addItem(box)
            elif self.Stims.df.message_on.iloc[ii].startswith("n"):
                on = self.Stims.df.message_on.iloc[ii][1:]
                r = int(on[:3])
                g = int(on[3:6])
                b = int(on[6:])
                box = LinearRegionItem(values=(start, stop),
                                       brush=(r, g, b, 50),
                                       movable=False)
                self.ui.graphicsView.addItem(box)
            elif self.Stims.df.message_on.iloc[ii].startswith("v"):
                box = LinearRegionItem(values=(start, stop),
                                       brush="k",
                                       movable=False)
                self.ui.graphicsView.addItem(box)

        self.ui.comboBoxSelectStimId.clear()
        for stim_id in set(self.Stims.df.id):
            self.ui.comboBoxSelectStimId.addItem(stim_id)
Exemple #8
0
class Evaluator(QtGui.QMainWindow):
    """docstring for CXIWindow"""
    def __init__(self):
        super(Evaluator, self).__init__()
        # load and adjust layout
        dir_ = os.path.dirname(os.path.abspath(__file__))
        uic.loadUi(dir_ + '/' + 'layout.ui', self)
        self.splitterH.setSizes([self.width() * 0.7, self.width() * 0.3])
        self.splitterV.setSizes([self.height() * 0.7, self.height() * 0.3])

        self.labelItem = PlotDataItem(pen=pg.mkPen('y',
                                                   width=1,
                                                   style=QtCore.Qt.SolidLine),
                                      name='labels')
        self.probItem = PlotDataItem(pen=pg.mkPen('c',
                                                  width=1,
                                                  style=QtCore.Qt.SolidLine),
                                     name='prob')
        self.evalPlot.addItem(self.labelItem)
        self.evalPlot.addItem(self.probItem)

        # initilize some parameters
        self.frame_id = 0

        # setup menu slots
        self.actionOpenNpz.triggered.connect(self.loadNpz)
        self.actionOpenModel.triggered.connect(self.loadModel)

        # setup parameter tree
        params_list = [
            {
                'name':
                u'文件信息',
                'type':
                'group',
                'children': [
                    {
                        'name': u'路径',
                        'type': 'str',
                        'value': u'未设置',
                        'readonly': True
                    },
                    {
                        'name': u'帧数',
                        'type': 'str',
                        'value': u'未设置',
                        'readonly': True
                    },
                    {
                        'name': u'长X高',
                        'type': 'str',
                        'value': u'未设置',
                        'readonly': True
                    },
                    {
                        'name': u'模型',
                        'type': 'str',
                        'value': u'未设置',
                        'readonly': True
                    },
                ]
            },
            {
                'name':
                u'基本操作',
                'type':
                'group',
                'children': [
                    {
                        'name': u'当前帧',
                        'type': 'int',
                        'value': self.frame_id
                    },
                    {
                        'name': u'开始测试',
                        'type': 'action'
                    },
                ]
            },
        ]
        self.params = Parameter.create(name='params',
                                       type='group',
                                       children=params_list)
        self.parameterTree.setParameters(self.params, showTop=False)

        # parameter connection
        self.params.param(u'基本操作', u'当前帧').sigValueChanged.connect(
            self.frameChangedSlot)
        self.params.param(u'基本操作', u'开始测试').sigActivated.connect(self.eval)

    def eval(self):
        cnn_dir = join(dirname(dirname(abspath(__file__))), 'cnn')
        sys.path.insert(0, cnn_dir)
        import cnn_eval
        ckpt_file = self.model_file[:-5]

        probs = cnn_eval.eval(data_file=self.data_file,
                              ckpt_file=ckpt_file,
                              crop_size=(100, 270))
        self.probs = probs
        self.probItem.setData(probs[:, 1])

    def loadModel(self):
        fpath = str(
            QtGui.QFileDialog.getOpenFileName(self, '打开文件', '',
                                              'META File (*.meta)'))
        self.model_file = fpath
        self.params.param(u'文件信息', u'模型').setValue(fpath)

    def frameChangedSlot(self, _, frame_id):
        self.frame_id = int(frame_id)
        self.updateDisp()

    def loadNpz(self):
        fpath = str(
            QtGui.QFileDialog.getOpenFileName(self, '打开文件', '',
                                              'NPZ File (*.npz)'))
        self.data_file = fpath
        self.data = np.load(fpath)

        self.params.param(u'文件信息', u'路径').setValue(fpath)
        shape = self.data['frames'].shape
        self.params.param(u'文件信息', u'帧数').setValue(shape[0])
        self.params.param(u'文件信息',
                          u'长X高').setValue('%dX%d' % (shape[2], shape[1]))
        self.updateDisp()
        self.updatePlot()

    def updateDisp(self):
        image = self.data['frames'][self.frame_id].T
        self.imageView.setImage(image,
                                autoRange=False,
                                autoLevels=False,
                                autoHistogramRange=False)

    def updatePlot(self):
        labels = self.data['labels']
        self.labelItem.setData(labels)
class makeTimeseriesCurveNode(NodeWithCtrlWidget):
    """Prepare Timeseries for plotting. Generate curve that can be viewed with node *TimeseriesPlot*
    and pd.Series with datetime stored in Index
    """
    nodeName = "Make Curve"
    uiTemplate = [
        {'name': 'Y:signal', 'type': 'list', 'value': None, 'default': None, 'values': [None], 'tip': 'Signal Data-Values (Y-axis)'},
        {'name': 'X:datetime', 'type': 'list', 'value': None, 'default': None, 'values': [None], 'tip': 'Datetime Values (X-axis)'},
        {'name': 'tz correct', 'type': 'float', 'value': 0, 'default': 0, 'suffix': ' hours', 'tip': '<float>\nONLY FOR CURVE!!!\nTimezone correction\nNumber of hours to add/substract from result. Due to missing\ntimezone settings it may be nessesary to use this parameter.\nCheck the results manually with *TimeseriesPlot* Node'},
        {'name': 'color', 'type': 'color', 'tip': 'Curve color'},
        {'name': 'Display Line', 'type': 'bool', 'value': True, 'tip': 'display line-curve between data points', 'children': [
            {'name': 'Style', 'type': 'list', 'tip': 'Style', 'values':
                {'solid': QtCore.Qt.SolidLine,
                 'dash': QtCore.Qt.DashLine,
                 'dash-dot': QtCore.Qt.DashDotLine,
                 'dot-dot': QtCore.Qt.DotLine,
                 'dash-dot-dot': QtCore.Qt.DashDotDotLine }},
            {'name': 'Linewidth', 'type': 'float', 'value': 1., 'limits': (0., 20.), 'step': 0.1,  'tip': 'Linewidth'},
        ]},
        {'name': 'Display Data Points', 'type': 'bool', 'value': False, 'tip': 'display data points as scatter', 'children': [
            {'name': 'Symbol', 'type': 'list', 'tip': 'Symbol for data points', 'value': 'o', 'values':
                {'circle': 'o',
                 'triangle': 't',
                 'square': 's',
                 'pentagon': 'p',
                 'hexagon': 'h',
                 'star': 'star',
                 'cross': '+',
                 'diamond': 'd'}},
            {'name': 'Size', 'type': 'int', 'value': 5, 'limits': (0, 1000), 'tip': 'Symbol size'},
        ]},
        ]

    def __init__(self, name, parent=None):
        super(makeTimeseriesCurveNode, self).__init__(name, parent=parent, terminals={'df': {'io': 'in'}, 'pd.Series': {'io': 'out'}, 'Curve': {'io': 'out'}}, color=(150, 150, 250, 150))
        self._plotRequired = False
        self.item = PlotDataItem(clipToView=False)
    
    def _createCtrlWidget(self, **kwargs):
        return makeTimeseriesCurveNodeCtrlWidget(**kwargs)
        
    def process(self, df):
        if df is None:
            del self.item
            self.item = None
            return {'Curve': None, 'pd.Series': None }
        if self.item is None:
            self.item = PlotDataItem(clipToView=False)

        colname = [col for col in df.columns if isNumpyNumeric(df[col].dtype)]
        self._ctrlWidget.param('Y:signal').setLimits(colname)
        colname = [col for col in df.columns if isNumpyDatetime(df[col].dtype)]
        self._ctrlWidget.param('X:datetime').setLimits(colname)

        with BusyCursor():
            kwargs = self.ctrlWidget().prepareInputArguments()
            
            #self.item = PlotDataItem(clipToView=False)
            t = df[kwargs['X:datetime']].values
            # part 1
            timeSeries = pd.DataFrame(data=df[kwargs['Y:signal']].values, index=t, columns=[kwargs['Y:signal']])

            # part 2
            #   convert time
            b = t.astype(np.dtype('datetime64[s]'))
            timeStamps = b.astype(np.int64)-kwargs['tz correct']*60*60+time.timezone
            #   now create curve
            pen = fn.mkPen(color=kwargs['color'], width=kwargs['width'], style=kwargs['style'])
            self.item.setData(timeStamps, df[kwargs['Y:signal']].values, pen=pen, name=kwargs['Y:signal'])
            
            self.item.setSymbol(kwargs['symbol'])
            if kwargs['symbol'] is not None:
                self.item.setSymbolPen(kwargs['color'])
                self.item.setSymbolBrush(kwargs['color'])
                self.item.setSymbolSize(kwargs['symbolSize'])
        return {'Curve': self.item, 'pd.Series': timeSeries }
Exemple #10
0
class MapViewWidget(DynImageView):
    sigShowSpectra = Signal(int)

    def __init__(self, *args, **kwargs):
        super(MapViewWidget, self).__init__(*args, **kwargs)
        # self.scene.sigMouseMoved.connect(self.showSpectra)
        self.scene.sigMouseClicked.connect(self.showSpectra)
        self.view.invertY(True)

        # add arrow
        # self.arrow = ArrowItem(angle=60, headLen=15, tipAngle=45, baseAngle=30, brush = (200, 80, 20))
        # self.arrow.setPos(0, 0)
        self.cross = PlotDataItem([0], [0],
                                  symbolBrush=(200, 0, 0),
                                  symbolPen=(200, 0, 0),
                                  symbol='+',
                                  symbolSize=16)

        self.view.addItem(self.cross)
        self.cross.hide()
        #add txt
        self.txt = TextItem('', anchor=(0, 0))
        self.addItem(self.txt)

    def setEnergy(self, lineobject):
        E = lineobject.value()
        # map E to index
        i = val2ind(E, self.wavenumbers)
        # print('E:', E, 'wav:', self.wavenumbers[i])
        self.setCurrentIndex(i)

    def showSpectra(self, event):

        pos = event.pos()
        if self.view.sceneBoundingRect().contains(
                pos
        ):  # Note, when axes are added, you must get the view with self.view.getViewBox()
            mousePoint = self.view.mapSceneToView(pos)
            x, y = int(mousePoint.x()), int(mousePoint.y())
            y = self.row - y - 1
            try:
                ind = self.rc2ind[(y, x)]
                self.sigShowSpectra.emit(ind)
                # print(x, y, ind, x + y * self.n_col)

                #update arrow
                self.cross.setData([x + 0.5], [self.row - y - 0.5])
                self.cross.show()
                # update text
                self.txt.setHtml(
                    f'<div style="text-align: center"><span style="color: #FFF; font-size: 8pt">X: {x}</div>\
            <div style="text-align: center"><span style="color: #FFF; font-size: 8pt">Y: {y}</div>\
            <div style="text-align: center"><span style="color: #FFF; font-size: 8pt">Point: #{ind}</div>'
                )
            except Exception:
                self.cross.hide()

    def setHeader(self, header: NonDBHeader, field: str, *args, **kwargs):
        self.header = header
        self.field = field

        imageEvent = next(header.events(fields=['image']))
        self.rc2ind = imageEvent['rc_index']
        self.wavenumbers = imageEvent['wavenumbers']
        # make lazy array from document
        data = None
        try:
            data = header.meta_array(field)
            self.row = data.shape[1]
            self.col = data.shape[2]
            self.txt.setPos(self.col, 0)
        except IndexError:
            msg.logMessage(
                'Header object contained no frames with field '
                '{field}'
                '.', msg.ERROR)

        if data is not None:
            # kwargs['transform'] = QTransform(1, 0, 0, -1, 0, data.shape[-2])
            self.setImage(img=data, *args, **kwargs)
            self._data = data

    def updateImage(self, autoHistogramRange=True):
        super(MapViewWidget, self).updateImage(autoHistogramRange)
        self.ui.roiPlot.setVisible(False)

    def setImage(self, img, **kwargs):
        super(MapViewWidget, self).setImage(img, **kwargs)
        self.ui.roiPlot.setVisible(False)

    def makeMask(self, thresholds):
        peak1550 = val2ind(1550, self.wavenumbers)
        thr1550 = thresholds[0]
        mask = self._data[peak1550] > thr1550
        mask = mask.astype(np.int)
        return mask
class imMobilize(QtWidgets.QWidget):
    def __init__(self, parent=None, *args):

        # run the widget, get the form
        QtWidgets.QWidget.__init__(self, parent, *args)

        self.ui = Ui_Form()
        self.ui.setupUi(self)
        os.chdir("D:\\")
        self.working_directory_selected = False
        """
        ==============================================================
        Connecting the buttons and labels in the serial control group
        ==============================================================
        """

        self.arduino = Arduino()
        # populate serial available combobox
        self.update_serial_available()
        # connect scan button
        self.ui.buttonSerialScan.clicked.connect(self.update_serial_available)

        # connect connect/disconnect button
        self.ui.buttonSerialConnect.clicked.connect(
            self.serial_connect_disconnect)
        # try:
        #     self.serial_connect_disconnect()
        # except:
        #     QtWidgets.QMessageBox.warning(self, "No controller found", "Could not connect to a suitable controller \n Connect Manually.")

        self.ui.lineeditWriteToSerial.editingFinished.connect(
            self.write_to_serial)
        self.ui.buttonWriteToSerial.clicked.connect(self.write_to_serial)

        self.ui.checkBoxReadSerialLive.clicked.connect(self.listen_to_serial)

        self.logged_temperature = np.array([])
        self.temp_plot_xvals = np.array([])
        """
        =====================================================================
        Connecting the buttons and controls in the light stim controls group
        =====================================================================
        """

        # connect the save_stim button
        self.ui.buttonSaveStim.clicked.connect(self.save_lightstim)

        # Style the three sliders (color the handles)
        # self.ui.sliderRed.setStyleSheet("QSlider::handle:vertical {background-color: red; border: 1px outset; border-radius: 3px;}")
        # self.ui.sliderGreen.setStyleSheet("QSlider::handle:vertical {background-color: lime; border: 1px outset; border-radius: 3px;}")
        # self.ui.sliderBlue.setStyleSheet("QSlider::handle:vertical {background-color: cyan; border: 1px outset; border-radius: 3px;}")

        self.ui.toggleWhiteColor.valueChanged.connect(
            self.toggle_lightstim_type)
        self.toggle_lightstim_type()
        """
        ====================================================================
        Connecting buttons and controls in vibration stimuli group
        ====================================================================
        """

        self.ui.buttonSaveVibrationStim.clicked.connect(
            self.save_vibration_stim)
        """
        =====================================================================
        Connecting the buttons and controls in the stimuli manager group
        =====================================================================
        """

        self.Stims = StimuliManager(arduino=self.arduino)
        self.set_experiment_view()
        self.set_lightstim_name_lineedit()
        self.set_vibrationstim_name_lineedit()
        self.ui.buttonDeleteSelectedStim.clicked.connect(
            self.delete_selected_stim)
        self.ui.buttonDeleteAllStims.clicked.connect(self.delete_all_stims)
        self.ui.buttonLoadStimulusProfile.clicked.connect(self.load_stimuli)
        self.ui.buttonSaveStimulusProfile.clicked.connect(self.save_stimuli)
        """
        =====================================================================
        Connecting the buttons and controls in the MetaData entry group
        =====================================================================
        """
        self.ui.buttonSetWorkingDirectory.clicked.connect(
            self.set_working_directory)

        self.ui.labelMetaDataDrugName.setVisible(False)
        self.ui.lineeditMetaDataDrugName.setVisible(False)

        self.ui.labelMetaDataGenetics.setVisible(False)
        self.ui.lineeditMetaDataGenetics.setVisible(False)

        self.ui.spinBoxExperimentDurationMinutes.valueChanged.connect(
            self.set_experiment_view)
        self.ui.spinBoxExperimentDurationSeconds.valueChanged.connect(
            self.set_experiment_view)

        self.ui.checkBoxExperimentAutonaming.clicked.connect(
            self.set_experiment_autonaming)
        self.set_experiment_autonaming()

        self.experiment_live = False
        self.ui.buttonStartExperiment.clicked.connect(self.start_experiment)

        self.path = os.curdir
        self.experiment_name = self.ui.lineeditExperimentName.text()
        self.experiment_path = os.path.join(self.path,
                                            "Exp_" + self.experiment_name)
        """
        ======================================================================
        Connecting buttons and controls to the camera manager group
        ======================================================================
        """

        self.detect_cameras()
        self.ui.buttonCameraConnectDisconnect.clicked.connect(
            self.camera_connect_disconnect)
        self.ui.buttonCameraPreview.clicked.connect(self.toggle_preview)
        self.ui.buttonScanForCameras.clicked.connect(self.detect_cameras)

        self.ui.spinBoxFramerate.valueChanged.connect(
            self.set_camera_framerate)

        for x in range(-2, -12, -1):
            self.ui.comboboxCameraExposure.addItem(str(2**x))

        self.ui.comboboxCameraExposure.currentTextChanged.connect(
            self.set_camera_exposure)
        self.ui.sliderCameraBrightness.sliderMoved.connect(
            self.set_camera_brightness)
        self.ui.sliderCameraGamma.sliderMoved.connect(self.set_camera_gamma)

        self.ui.buttonCameraResetProperties.clicked.connect(
            self.reset_camera_properties)

        #This is for IR light, so actually belongs to arduino, but it is located here in the GUI.
        self.ui.sliderIRLight.sliderReleased.connect(self.set_IR_light)
        """
        ======================================================================
        Connecting buttons and controls to the video manager
        ======================================================================
        """

        self.set_autonaming(True)

        self.ui.buttonSetPath.clicked.connect(self.set_path)

        self.ui.checkboxAutoNaming.toggled.connect(self.set_autonaming)
        self.ui.buttonRecordVideo.clicked.connect(self.record_video)

    def detect_cameras(self):
        self.ui.comboBoxConnectedCameras.clear()
        for ii in range(3):
            cap = cv2.VideoCapture(ii)
            if not cap.isOpened():
                pass
            else:
                cam = "Camera " + str(ii + 1)
                self.ui.comboBoxConnectedCameras.addItem(cam)

    def camera_connect_disconnect(self):
        if not hasattr(self, "cam") or not self.cam.cap.isOpened():
            camera_number = int(self.ui.comboBoxConnectedCameras.currentText().
                                split(" ")[1]) - 1

            self.cam = Camera(camera=camera_number)
            if self.cam.cap.isOpened():
                self.ui.groupBoxCameraControls.setEnabled(True)
                self.ui.groupBoxVideoControls.setEnabled(True)
                self.ui.comboBoxConnectedCameras.setEnabled(False)

                self.ui.labelCameraConnectionStatus.setText(
                    "Connected to Camera " + str(camera_number + 1))
                self.ui.labelCameraConnectionStatus.setStyleSheet(
                    "color: green")
                self.ui.buttonCameraConnectDisconnect.setText("Disconnect")
                self.ui.buttonCameraConnectDisconnect.setStyleSheet(
                    "color: red")

                t = self.ui.comboboxCameraExposure.findText(
                    str(2**(self.cam.exposure)))
                self.ui.comboboxCameraExposure.setCurrentIndex(t)
                self.ui.sliderCameraBrightness.setValue(self.cam.brightness)
                self.ui.labelCameraBrighness.setNum(self.cam.brightness)
                self.ui.sliderCameraGamma.setValue(self.cam.gamma * 10)
                self.ui.labelCameraGamma.setNum(self.cam.gamma)
                self.ui.sliderCameraGamma.setEnabled(False)

        else:
            self.toggle_preview(False)
            self.cam.stop()
            self.cam.cap.release()
            self.ui.buttonCameraPreview.setChecked(False)
            self.ui.groupBoxCameraControls.setEnabled(False)
            self.ui.groupBoxVideoControls.setEnabled(False)
            self.ui.comboBoxConnectedCameras.setEnabled(True)

            self.ui.buttonCameraConnectDisconnect.setText("Connect")
            self.ui.buttonCameraConnectDisconnect.setStyleSheet("color: black")
            self.ui.labelCameraConnectionStatus.setText(
                "Status: Not Connected")
            self.ui.labelCameraConnectionStatus.setStyleSheet("color: black")

    def toggle_lightstim_type(self):
        val = self.ui.toggleWhiteColor.value()
        if val == 1:
            self.lightStimType = "White"
            self.ui.labelLightStimTypeLED.setEnabled(False)
            self.ui.labelLightStimTypeWhite.setEnabled(True)
            self.ui.widgetLEDSliders.setEnabled(False)

            self.ui.toggleWhiteColor.setStyleSheet(
                "QSlider::handle:horizontal {background-color: rgba(255,255,255,255); border: 2px outset; width: 10px; height: 10px; border-radius: 5px;}"
                "QSlider::groove:horizontal {height: 10px; width: 30px; background-color: rgba(255, 0,0, 100); border-radius: 5px; border: 1px solid;}"
            )

            self.ui.sliderRed.setStyleSheet(
                "QSlider::handle:vertical {background-color: grey; border: 1px outset; border-radius: 3px;}"
            )
            self.ui.sliderGreen.setStyleSheet(
                "QSlider::handle:vertical {background-color: grey; border: 1px outset; border-radius: 3px;}"
            )
            self.ui.sliderBlue.setStyleSheet(
                "QSlider::handle:vertical {background-color: grey; border: 1px outset; border-radius: 3px;}"
            )

        if val == 0:
            self.lightStimType = "LED"
            self.ui.labelLightStimTypeLED.setEnabled(True)
            self.ui.labelLightStimTypeWhite.setEnabled(False)
            self.ui.widgetLEDSliders.setEnabled(True)

            self.ui.toggleWhiteColor.setStyleSheet(
                "QSlider::handle:horizontal {background-color: rgba(255,100,0,100); border: 2px outset; width: 10px; height: 10px; border-radius: 5px;}"
                "QSlider::groove:horizontal {height: 10px; width: 30px; background-color: rgba(255, 255, 255,100); border-radius: 5px; border: 1px solid;}"
            )

            self.ui.sliderRed.setStyleSheet(
                "QSlider::handle:vertical {background-color: red; border: 1px outset; border-radius: 3px;}"
            )
            self.ui.sliderGreen.setStyleSheet(
                "QSlider::handle:vertical {background-color: lime; border: 1px outset; border-radius: 3px;}"
            )
            self.ui.sliderBlue.setStyleSheet(
                "QSlider::handle:vertical {background-color: cyan; border: 1px outset; border-radius: 3px;}"
            )

    def set_experiment_view(self):
        self.ui.graphicsView.clear()
        plot = PlotDataItem(pen="k")

        duration = (self.ui.spinBoxExperimentDurationMinutes.value() *
                    60) + self.ui.spinBoxExperimentDurationSeconds.value()
        xs = np.linspace(0, duration, 2)
        ys = [1, 1]
        plot.setData(xs, ys)
        self.ui.graphicsView.addItem(plot)

        for ii in range(len(self.Stims.df)):
            start = self.Stims.df.time_on.iloc[ii]
            stop = self.Stims.df.time_off.iloc[ii]

            if self.Stims.df.message_on.iloc[ii].startswith("w"):
                box = LinearRegionItem(values=(start, stop),
                                       brush=(255, 255, 250, 230),
                                       movable=False)
                self.ui.graphicsView.addItem(box)
            elif self.Stims.df.message_on.iloc[ii].startswith("n"):
                on = self.Stims.df.message_on.iloc[ii][1:]
                r = int(on[:3])
                g = int(on[3:6])
                b = int(on[6:])
                box = LinearRegionItem(values=(start, stop),
                                       brush=(r, g, b, 50),
                                       movable=False)
                self.ui.graphicsView.addItem(box)
            elif self.Stims.df.message_on.iloc[ii].startswith("v"):
                box = LinearRegionItem(values=(start, stop),
                                       brush="k",
                                       movable=False)
                self.ui.graphicsView.addItem(box)

        self.ui.comboBoxSelectStimId.clear()
        for stim_id in set(self.Stims.df.id):
            self.ui.comboBoxSelectStimId.addItem(stim_id)

    # def checkStimSafeguard(self):
    #     if self.ui.checkboxDirectStim.isChecked() is False:
    #         self.ui.listwidgetAllStims.itemDoubleClicked.disconnect(self.single_stim)
    #     elif self.ui.checkboxDirectStim.isChecked():
    #         self.ui.listwidgetAllStims.itemDoubleClicked.connect(self.single_stim)

    def update_serial_available(self):
        """Scans the available comports and updates the combobox with the resulst"""
        self.ui.comboboxSerialAvailable.clear()
        self.arduino.scan_comports()
        for port in list(self.arduino.port_dict.keys()):
            self.ui.comboboxSerialAvailable.addItem(port)

    def serial_connect_disconnect(self):
        """Connects to selected serial if disconnected and vice versa"""
        if self.ui.buttonSerialConnect.text().lower() == "connect":
            port = self.ui.comboboxSerialAvailable.currentText()
            self.arduino.connect(port)
            if self.arduino.ser.isOpen():
                self.ui.labelSerialConnected.setText("Connected: " +
                                                     self.arduino.port)
                self.ui.labelSerialConnected.setStyleSheet("color: green")
                self.ui.buttonSerialConnect.setText("Disconnect")
                self.ui.checkBoxReadSerialLive.setEnabled(True)
                self.ui.sliderIRLight.setEnabled(True)

        elif self.ui.buttonSerialConnect.text().lower() == "disconnect":
            self.ui.checkBoxReadSerialLive.setChecked(False)
            self.arduino.disconnect()
            if self.arduino.ser.isOpen() == False:
                self.ui.labelSerialConnected.setText("Status: Not connected")
                self.ui.labelSerialConnected.setStyleSheet("color: black")
                self.ui.buttonSerialConnect.setText("Connect")
                self.ui.checkBoxReadSerialLive.setEnabled(False)
                self.ui.checkBoxReadSerialLive.setChecked(False)
                self.ui.sliderIRLight.setEnabled(False)

    def listen_to_serial(self):
        """Starts a thread that listens on the self.Arduino.ser serial connection and updates the GUI with incoming messages"""
        if self.ui.checkBoxReadSerialLive.isChecked():
            self.ui.graphicsViewSerial.clear()
            self.temperature_plot = PlotDataItem(pen=(0, 153, 153))
            self.temperature_plot_2 = PlotDataItem(pen=(255, 128, 0))
            self.temperature_plot.setDownsampling(auto=True, method="mean")
            self.temperature_plot_2.setDownsampling(auto=True, method="mean")
            self.ui.graphicsViewSerial.addItem(self.temperature_plot)
            self.ui.graphicsViewSerial.addItem(self.temperature_plot_2)
            t = Thread(target=self.start_listening, args=())
            t.start()

    def start_listening(self):
        """To be started in separate thread: listens to Serial port and updates gui via separate method if new message comes in"""
        self.arduino.ser.flushInput()
        self.logged_temperature = np.array([])
        self.logged_temperature_2 = np.array([])
        self.logged_temperature_time = np.array([])
        start = time.time()
        while self.ui.checkBoxReadSerialLive.isChecked(
        ) and self.arduino.ser.isOpen():
            if self.arduino.ser.in_waiting > 0:
                message = self.arduino.ser.readline().decode()
                elapsed_time = time.time() - start
                self.update_serial_display(message, np.round(elapsed_time, 2))
            else:
                time.sleep(0.01)
        else:
            pass

    def update_serial_display(self, message, elapsed_time):
        temp1, temp2 = message.split(" ")
        self.ui.labelSerialLiveIncoming.setText(str(elapsed_time))
        self.ui.labelSerialLiveTemperature.setText(message)
        temp1 = float(temp1)
        temp2 = float(temp2)
        self.logged_temperature_time = np.hstack(
            [self.logged_temperature_time, elapsed_time])
        self.logged_temperature = np.hstack([self.logged_temperature, temp1])
        self.logged_temperature_2 = np.hstack(
            [self.logged_temperature_2, temp2])

        self.temperature_plot.setData(x=self.logged_temperature_time,
                                      y=self.logged_temperature)
        self.temperature_plot_2.setData(x=self.logged_temperature_time,
                                        y=self.logged_temperature_2)

    def write_to_serial(self):
        """Send message in the lineEdit widget to the serial port"""
        message = self.ui.lineeditWriteToSerial.text()
        self.arduino.write(message)
        self.ui.lineeditWriteToSerial.clear()

    def set_lightstim_name_lineedit(self):
        c = 1
        while "LightStim" + str(len(self.Stims.df) + c) in self.Stims.df.id:
            c += 1
        newname = "LightStim" + str(len(self.Stims.df) + c)
        self.ui.lineeditLightStimName.setText(newname)

    def save_lightstim(self):
        """Takes the current settings in the lightstim box and adds them to stim manager"""

        start_time = self.ui.spinboxStimStart.value()
        stop_time = self.ui.spinboxStimDuration.value() + start_time
        stim_id = self.ui.lineeditLightStimName.text()

        if self.lightStimType == "LED":
            start_message = "n"
            for slider in [
                    self.ui.sliderRed, self.ui.sliderGreen, self.ui.sliderBlue
            ]:
                start_message += str(slider.value()).zfill(3)
            stop_message = "nC"

        elif self.lightStimType == "White":
            start_message = "wS"
            stop_message = "wC"

        self.Stims.add_stimulus(start_time, stop_time, start_message,
                                stop_message, stim_id)
        self.set_lightstim_name_lineedit()

        self.ui.comboBoxSelectStimId.clear()
        stim_ids = list(set(self.Stims.df.id))
        stim_ids.sort()
        for stim_id in stim_ids:
            self.ui.comboBoxSelectStimId.addItem(stim_id)
        self.set_experiment_view()

    def set_vibrationstim_name_lineedit(self):
        c = 1
        while "VibrationStim" + str(len(self.Stims.df) +
                                    c) in self.Stims.df.id:
            c += 1
        newname = "VibrationStim" + str(len(self.Stims.df) + c)
        self.ui.lineeditVibrationStimName.setText(newname)

    def save_vibration_stim(self):
        """takes the current settings in the vibration stim box and adds it to the stim manager"""
        start_time = self.ui.spinBoxVibrationStart.value()
        duration = self.ui.spinBoxVibrationDuration.value()
        freq = self.ui.spinBoxVibrationFrequency.value()
        start_message = "v" + str(freq).zfill(3) + str(int(
            duration * 1000)).zfill(4)
        stop_message = ""
        stim_id = self.ui.lineeditVibrationStimName.text()
        self.Stims.add_stimulus(start_time, start_time + duration,
                                start_message, stop_message, stim_id)
        self.set_vibrationstim_name_lineedit()
        self.ui.comboBoxSelectStimId.clear()
        stim_ids = list(set(self.Stims.df.id))
        stim_ids.sort()
        for stim_id in stim_ids:
            self.ui.comboBoxSelectStimId.addItem(stim_id)
        self.set_experiment_view()

    def delete_selected_stim(self):
        stim_id = self.ui.comboBoxSelectStimId.currentText()

        self.Stims.delete_stimulus(stim_id)
        self.set_experiment_view()

    def delete_all_stims(self):
        self.Stims.delete_all_stims()
        self.set_experiment_view()

    def load_stimuli(self):
        file = QtWidgets.QFileDialog.getOpenFileName(self,
                                                     "Select a Stimuli File")
        file = file[0]
        if os.path.exists(file) and file.endswith(".txt"):
            df = pd.read_csv(file, delimiter="\t")
            self.Stims.df = df
            self.set_experiment_view()
        elif not file:
            pass
        else:
            QtWidgets.QMessageBox.warning(self, "Invalid Filename",
                                          "Invalid Stimulus File selected.")

    def save_stimuli(self):
        filename_selected = False
        while not filename_selected:
            filename = QtWidgets.QFileDialog.getSaveFileName(
                self, "Save Stimuli file as")[0]
            if not filename:
                break
            else:
                if not filename.endswith(".txt"):
                    filename += ".txt"
                self.Stims.df.to_csv(filename, sep="\t")
                filename_selected = True

    def toggle_preview(self, event):
        if event:
            self.cam.preview()
            self.ui.buttonCameraPreview.setText("Stop")
            self.ui.buttonCameraPreview.setStyleSheet("color: red")
        else:
            self.ui.buttonCameraPreview.setText("Preview")
            self.ui.buttonCameraPreview.setStyleSheet("color: Black")
            self.cam.stop()

    def set_autonaming(self, bool):
        if bool:
            self.ui.lineeditVideoName.setText(
                time.strftime("%Y%m%d_%H%M%S_video"))

    def set_experiment_autonaming(self):
        if self.ui.checkBoxExperimentAutonaming.isChecked():
            name = time.strftime("%Y%m%d_%H%M%S")
            name += "_"
            name += str(self.ui.spinboxCrowdsize.value())
            name += "_"
            name += str(self.ui.spinBoxExperimentDurationMinutes.value()) + "m"
            name += str(
                self.ui.spinBoxExperimentDurationSeconds.value()) + "s_"
            if self.ui.checkboxMetaDataDrugs.isChecked():
                name += self.ui.lineeditMetaDataDrugName.text() + "_"
            else:
                name += "None_"
            if self.ui.checkboxMetaDataGenetics.isChecked():
                name += self.ui.lineeditMetaDataGenetics.text() + "_"
            else:
                name += "None_"
            if len(self.Stims.df) > 0:
                name += "Light"
            else:
                name += "None"
            self.ui.lineeditExperimentName.setText(name)
        else:

            pass

    def set_camera_framerate(self, fps):
        self.cam.framerate = fps


#        if self.cam.do_gamma == False:
#            self.ui.sliderCameraGamma.setVisible(False)
#            self.ui.labelCameraGamma.setVisible(False)
#        else:
#            self.ui.sliderCameraGamma.setVisible(True)
#            self.ui.labelCameraGamma.setVisible(True)

    def set_camera_brightness(self, brightness):
        self.cam.brightness = brightness

    def set_camera_gamma(self, gamma):
        gamma = gamma / 10
        self.cam.gamma = gamma
        self.ui.labelCameraGamma.setNum(gamma)

    def set_camera_exposure(self, exp):
        # If set exposure is longer than framerate allows, then adjust framerate
        exp = float(exp)

        if exp > (1 / self.cam.framerate):
            print(
                "\n WARNING: Adjusting framerate to accomodate for exposure \n"
            )
            #            self.set_camera_framerate(1 / exp)
            self.ui.spinBoxFramerate.setValue(int(1 / exp))

        exp = np.log2(exp)
        self.cam.exposure = exp

    def set_IR_light(self):
        val = self.ui.sliderIRLight.value()
        self.arduino.write("i" + str(val).zfill(3))
        sys.stdout.write('\n IR Light set to {}. {}% of power'.format(
            val, (val / 255) * 100))

    def set_path(self):
        self.path = QtGui.QFileDialog.getExistingDirectory()
        print(self.path)

    def set_working_directory(self):
        self.working_directory_selected = False
        while not self.working_directory_selected:
            self.path = QtGui.QFileDialog.getExistingDirectory()
            if os.path.exists(self.path):
                os.chdir(self.path)
                self.working_directory_selected = True
            else:
                q = QtWidgets.QMessageBox.question(
                    self, "No path selected",
                    "Do you wish to not select a path? \n Path then defaults to D:\\"
                )
                if q == QtWidgets.QMessageBox.Yes:
                    self.path = "D:\\"
                    self.working_directory_selected = True

    def reset_camera_properties(self):
        self.cam.brightness = 0
        self.ui.sliderCameraBrightness.setValue(0)
        self.ui.labelCameraBrighness.setNum(0)

        self.cam.exposure = -5.0
        t = self.ui.comboboxCameraExposure.findText(str(2**-5))
        self.ui.comboboxCameraExposure.setCurrentIndex(t)

        self.cam.gamma = 1
        self.ui.sliderCameraGamma.setValue(10)
        self.ui.labelCameraGamma.setNum(1)

        self.cam.framerate = 30
        self.ui.spinBoxFramerate.setValue(30)

    def record_video(self, event):

        if event:
            self.ui.buttonRecordVideo.setText("Stop")
            self.ui.buttonRecordVideo.setStyleSheet("color:red")

            self.ui.groupBoxCameraControls.setEnabled(False)
            self.ui.groupBoxCameraConnection.setEnabled(False)

            if self.ui.checkboxAutoNaming.isChecked():
                self.ui.lineeditVideoName.setText(
                    time.strftime("%Y%m%d_%H%M" + "_experiment"))

            name = os.path.join(self.path, self.ui.lineeditVideoName.text())

            duration = self.ui.spinBoxVideoTimeMinutes.value() * 60
            duration += self.ui.spinBoxVideoTimeSeconds.value()
            preview = self.ui.checkBoxPreviewWhileRecording.isChecked()
            self.cam.video(name, duration, preview)

            t = Thread(target=self.wait_for_recording_end)
            t.start()

        else:
            self.ui.groupBoxCameraControls.setEnabled(True)
            self.ui.groupBoxCameraConnection.setEnabled(True)
            self.ui.buttonRecordVideo.setText("Record")
            self.ui.buttonRecordVideo.setStyleSheet("color:Black")
            self.cam.stop()

    def wait_for_recording_end(self):
        time.sleep(0.1)
        while self.cam.alive == True:
            time.sleep(0.1)
        else:
            self.record_video(False)

    #            self.ui.buttonRecordVideo.setChecked(False)
    #
    """
    ==========================================================================================
    METHODS TO RUN AN ENTIRE EXPERIMENT
    ==========================================================================================
    """

    def start_experiment(self, event):
        if event:
            if not hasattr(self, "cam"):
                QtWidgets.QMessageBox.warning(
                    self, "Not Connected Warning",
                    "No open connection with Camera")
                self.ui.buttonStartExperiment.setChecked(False)
            elif not self.cam.cap.isOpened():
                QtWidgets.QMessageBox.warning(
                    self, "Not Connected Warning",
                    "No open connection with Camera")
                self.ui.buttonStartExperiment.setChecked(False)
            elif not self.arduino.connected:
                QtWidgets.QMessageBox.warning(
                    self, "Not Connected Warning",
                    "No open connection with Controller")
                self.ui.buttonStartExperiment.setChecked(False)
            else:
                if not self.working_directory_selected:
                    QtWidgets.QMessageBox.warning(
                        self, "No valid working directory",
                        "Select a directory to save your experiment")
                    self.set_working_directory()

                self.experiment_live = True
                self.ui.buttonStartExperiment.setStyleSheet(
                    'color: rgba(255,0,0,255)')
                self.ui.buttonStartExperiment.setText('Interrupt')

                self.set_experiment_autonaming()
                self.ui.widgetMetaData.setEnabled(False)
                self.experiment_name = self.ui.lineeditExperimentName.text()

                self.experiment_path = os.path.join(
                    self.path, "Exp_" + self.experiment_name)
                if not os.path.exists(
                        self.experiment_path) or not os.path.isdir(
                            self.experiment_path):
                    os.mkdir(self.experiment_path)

                if self.ui.checkBoxReadSerialLive.isChecked():
                    self.ui.checkBoxReadSerialLive.setChecked(False)
                self.ui.checkBoxReadSerialLive.setChecked(True)
                self.listen_to_serial()

                duration = (
                    self.ui.spinBoxExperimentDurationMinutes.value() *
                    60) + self.ui.spinBoxExperimentDurationSeconds.value()
                preview = self.ui.checkBoxPreviewWhileRecording.isChecked()
                self.cam.video(
                    os.path.join(self.experiment_path, self.experiment_name),
                    duration, preview)
                self.Stims.start_stimuli()
                t = Thread(target=self.wait_for_experiment_end, args=())
                t.start()
        else:
            self.experiment_live = False
            self.cam.stop()
            self.ui.checkBoxReadSerialLive.setChecked(False)
            self.ui.buttonStartExperiment.setStyleSheet("color: Black")
            self.ui.buttonStartExperiment.setText("Start Experiment")

            df = pd.DataFrame()
            df["date"] = [time.strftime("%Y%m%d")]
            df["time"] = [time.strftime("%H%M%S")]
            df["duration"] = [
                (self.ui.spinBoxExperimentDurationMinutes.value() * 60) +
                self.ui.spinBoxExperimentDurationSeconds.value()
            ]
            df["drugs"] = [self.ui.lineeditMetaDataDrugName.text()]
            df["genetics"] = [self.ui.lineeditMetaDataGenetics.text()]
            df["age"] = [self.ui.spinboxAge]
            df["framerate"] = [self.cam.framerate]
            df["dechorionated"] = [
                self.ui.checkboxMetaDataDechorionation.isChecked()
            ]
            df["exposture"] = [
                float(self.ui.comboboxCameraExposure.currentText())
            ]
            df["gamma"] = [self.cam.gamma]
            df["brightness"] = [self.cam.brightness]
            df["infrared"] = [self.ui.sliderIRLight.value()]

            df.to_csv(os.path.join(self.experiment_path, "metadata.txt"),
                      sep="\t")

            self.Stims.df.to_csv(os.path.join(self.experiment_path,
                                              "stimuli_profile.txt"),
                                 sep="\t")
            df = pd.DataFrame(np.hstack([
                self.logged_temperature_time.reshape(-1, 1),
                self.logged_temperature.reshape(-1, 1),
                self.logged_temperature_2.reshape(-1, 1)
            ]),
                              columns=["time", "temperature", "temperature2"])
            df.set_index("time", inplace=True)
            df.to_csv(os.path.join(self.experiment_path,
                                   "logged_temperatures.txt"),
                      sep="\t")
            self.ui.widgetMetaData.setEnabled(True)

    def wait_for_experiment_end(self):
        time.sleep(0.2)  # Give camera a chance to start.
        while self.cam.alive:
            time.sleep(0.2)
        else:
            if self.experiment_live:
                self.start_experiment(False)
                self.ui.buttonStartExperiment.setChecked(False)

    # def start_experiment(self):
    #
    #     if self.experiment_live:
    #         self.experiment_live = False
    #         self.cam.stop()
    #         self.ui.checkBoxReadSerialLive.setChecked(False)
    #         self.ui.buttonStartExperiment.setStyleSheet("color: Black")
    #         self.ui.buttonStartExperiment.setText("Start Experiment")
    #
    #         df = pd.DataFrame()
    #         df["date"] = [time.strftime("%Y%m%d")]
    #         df["time"] = [time.strftime("%H%M%S")]
    #         df["duration"] = [(self.ui.spinBoxExperimentDurationMinutes.value() * 60) + self.ui.spinBoxExperimentDurationSeconds.value()]
    #         df["drugs"] = [self.ui.lineeditMetaDataDrugName.text()]
    #         df["genetics"] = [self.ui.lineeditMetaDataGenetics.text()]
    #         df["framerate"] = [self.cam.framerate]
    #         df["dechorionated"] = [self.ui.checkboxMetaDataDechorionation.isChecked()]
    #         df["exposture"] = [float(self.ui.comboboxCameraExposure.currentText())]
    #         df["gamma"] = [self.cam.brightness]
    #         df["brightness"] = [self.cam.brightness]
    #         df["infrared"] = [self.ui.sliderIRLight.value]
    #
    #
    #         df.to_csv(os.path.join(self.experiment_path, "metadata.txt"), sep="\t")
    #
    #         self.Stims.df.to_csv(os.path.join(self.experiment_path, "stimuli_profile.txt"), sep="\t")
    #         df = pd.DataFrame(np.hstack([self.logged_temperature_time.reshape(-1,1), self.logged_temperature.reshape(-1,1), self.logged_temperature_2.reshape(-1,1)]), columns = ["time", "temperature", "temperature2"])
    #         df.set_index("time", inplace=True)
    #         df.to_csv(os.path.join(self.experiment_path, "logged_temperatures.txt"), sep="\t")
    #
    #
    #         self.ui.groupboxMetaData.setEnabled(True)
    #
    #     elif not self.experiment_live:
    #         if not hasattr(self, "cam"):
    #             QtWidgets.QMessageBox.warning(self, "Not Connected Warning", "No open connection with Camera or Controller")
    #         elif self.cam.cap.isOpened() != True or self.arduino.ser.isOpen() != True:
    #             QtWidgets.QMessageBox.warning(self, "Not Connected Warning", "No open connection with Camera or Controller")
    #         else:
    #             self.experiment_live = True
    #             self.ui.buttonStartExperiment.setStyleSheet('color: rgba(255,0,0,255)')
    #             self.ui.buttonStartExperiment.setText('Interrupt')
    #
    #             self.set_experiment_autonaming()
    #             self.ui.groupboxMetaData.setEnabled(False)
    #             self.experiment_name = self.ui.lineeditExperimentName.text()
    #
    #             self.experiment_path = os.path.join(self.path, "Exp_" + self.experiment_name)
    #             if not os.path.exists(self.experiment_path) or not os.path.isdir(self.experiment_path):
    #                 os.mkdir(self.experiment_path)
    #
    #             if self.ui.checkBoxReadSerialLive.isChecked():
    #                 self.ui.checkBoxReadSerialLive.setChecked(False)
    #             self.ui.checkBoxReadSerialLive.setChecked(True)
    #             self.listen_to_serial()
    #
    #             duration = (self.ui.spinBoxExperimentDurationMinutes.value() * 60) + self.ui.spinBoxExperimentDurationSeconds.value()
    #             self.cam.video(os.path.join(self.experiment_path, self.experiment_name), duration)
    #             self.Stims.start_stimuli()
    #             t = Thread(target=self.wait_for_experiment_end, args=())
    #             t.start()

    # def wait_for_experiment_end(self):
    #     time.sleep(0.2)  # Give camera a chance to start.
    #     while self.cam.alive:
    #         time.sleep(0.1)
    #     else:
    #         if self.experiment_live:
    #             self.start_experiment()

    def closeEvent(self, event):
        QtWidgets.QMessageBox.warning(self, "Closing",
                                      "Nothing you can do about it")
        self.arduino.ser.close()
        self.cam.cap.release()
        event.accept()
        print("yay")
class baselinePlotWidget(SpectraPlotWidget):
    def __init__(self):
        super(baselinePlotWidget, self).__init__()
        self.line.setValue(800)
        self.txt = TextItem('', anchor=(0, 0))
        self.cross = PlotDataItem([800], [0], symbolBrush=(255, 255, 0), symbolPen=(255, 255, 0),
                                  symbol='+',symbolSize=20)
        self.line.sigPositionChanged.connect(self.getMu)
        self._mu = None

    def plot(self, x, y, *args, **kwargs):
        # set up infinity line and get its position
        self.plotItem.plot(x, y, *args, **kwargs)
        self.addItem(self.line)
        self.addItem(self.cross)
        x_val = self.line.value()
        if x_val == 0:
            y_val = 0
        else:
            idx = val2ind(x_val, self.wavenumbers)
            x_val = self.wavenumbers[idx]
            y_val = y[idx]

        if not self._meanSpec:
            txt_html = f'<div style="text-align: center"><span style="color: #FFF; font-size: 12pt">\
                            Spectrum #{self.spectrumInd}</div>'
        else:
            txt_html = f'<div style="text-align: center"><span style="color: #FFF; font-size: 12pt">\
                             {self._mean_title}</div>'

        txt_html += f'<div style="text-align: center"><span style="color: #FFF; font-size: 12pt">\
                             X = {x_val: .2f}, Y = {y_val: .4f}</div>'
        self.txt = TextItem(html=txt_html, anchor=(0, 0))
        ymax = np.max(y)
        self._y = y
        r = self.txtPosRatio
        self.txt.setPos(r * x[-1] + (1 - r) * x[0], 0.95 * ymax)
        self.cross.setData([x_val], [y_val])
        self.addItem(self.txt)

    def getMu(self):
        if self._mu is not None:
            x_val = self.line.value()
            if x_val == 0:
                y_val = 0
            else:
                idx = val2ind(x_val, self._x)
                x_val = self._x[idx]
                y_val = self._mu[idx]
            txt_html = f'<div style="text-align: center"><span style="color: #FFF; font-size: 12pt">\
                                                 X = {x_val: .2f}, Y = {y_val: .4f}</div>'
            self.txt.setHtml(txt_html)
            self.cross.setData([x_val], [y_val])

    def addDataCursor(self, x, y):
        self.addItem(self.line)
        self.addItem(self.cross)
        ymax = np.max(y)
        self.txt.setText('')
        r = self.txtPosRatio
        self.txt.setPos(r * x[-1] + (1 - r) * x[0], 0.95 * ymax)
        self.addItem(self.txt)
        self.getMu()

    def plotBase(self, dataGroup, plotType='raw'):
        """
        make plots for Larch Group object
        :param dataGroup: Larch Group object
        :return:
        """
        # add legend
        self.plotItem.addLegend(offset=(-1, -1))
        x = self._x = dataGroup.energy # self._x, self._mu for getEnergy
        y = self._mu = dataGroup.specTrim
        n = len(x) # array length
        self._y = None  # disable getEnergy func
        if plotType == 'raw':
            self.plotItem.plot(x, y, name='Raw', pen=mkPen('w', width=2))
        elif plotType == 'rubber_base':
            self.plotItem.plot(x, y, name='Raw', pen=mkPen('w', width=2))
            self.plotItem.plot(x, dataGroup.rubberBaseline, name='Rubberband baseline', pen=mkPen('g', style=Qt.DotLine, width=2))
            self.plotItem.plot(dataGroup.wav_anchor, dataGroup.spec_anchor, symbol='o', symbolPen='r', symbolBrush=0.5)
        elif plotType == 'kohler_base':
            self.plotItem.plot(x, y, name='Raw', pen=mkPen('w', width=2))
            self.plotItem.plot(x, dataGroup.kohlerBaseline, name='Kohler EMSC baseline', pen=mkPen('g', style=Qt.DotLine, width=2))
        elif plotType == 'rubberband':
            y = self._mu = dataGroup.rubberDebased
            self.plotItem.plot(x, y, name='Rubberband debased', pen=mkPen('r', width=2))
        elif plotType == 'kohler':
            y = self._mu = dataGroup.kohlerDebased
            self.plotItem.plot(x, y, name='Kohler EMSC debased', pen=mkPen('r', width=2))
        elif plotType == 'deriv2_rubberband':
            y = self._mu = dataGroup.rubberDebased # for data cursor
            scale, offset = self.alignTwoCurve(dataGroup.rubberDebased[n//4:n*3//4], dataGroup.deriv2_rubber[n//4:n*3//4])
            deriv2Scaled = dataGroup.deriv2_rubber * scale + offset
            ymin, ymax = np.min(y), np.max(y)
            self.getViewBox().setYRange(ymin, ymax, padding=0.1)
            self.plotItem.plot(x, y, name='Rubberband debased', pen=mkPen('r', width=2))
            self.plotItem.plot(x, deriv2Scaled, name='2nd derivative (scaled, Rubberband)', pen=mkPen('g', width=2))
        elif plotType == 'deriv2_kohler':
            y = self._mu = dataGroup.kohlerDebased
            scale, offset = self.alignTwoCurve(dataGroup.kohlerDebased[n//4:n*3//4], dataGroup.deriv2_kohler[n//4:n*3//4])
            deriv2Scaled = dataGroup.deriv2_kohler * scale + offset
            ymin, ymax = np.min(y), np.max(y)
            self.getViewBox().setYRange(ymin, ymax, padding=0.1)
            self.plotItem.plot(x, y, name='Rubberband debased', pen=mkPen('r', width=2))
            self.plotItem.plot(x, deriv2Scaled, name='2nd derivative (scaled, Kohler)', pen=mkPen('g', width=2))
        # add infinityline, cross
        self.addDataCursor(x, y)

    def alignTwoCurve(self, y1, y2):
        """
        Align the scale of y2 to that of y1
        :param y1: the main curve
        :param y2: the curve to be aligned
        :return:
        scale: scale factor
        offset: y offset
        """
        y1Range, y2Range = np.max(y1) - np.min(y1), np.max(y2) - np.min(y2)
        scale = y1Range / y2Range
        y = y2 * scale
        offset = np.max(y1) - np.max(y)
        return scale, offset
class SpectraPlotWidget(PlotWidget):
    sigEnergyChanged = Signal(object)

    def __init__(self, linePos=650, txtPosRatio=0.35, invertX=True, *args, **kwargs):
        """
        A widget to display a 1D spectrum
        :param linePos: the initial position of the InfiniteLine
        :param txtPosRatio: a coefficient that determines the relative position of the textItem
        :param invertX: whether to invert X-axis
        """
        super(SpectraPlotWidget, self).__init__(*args, **kwargs)
        self._data = None
        assert (txtPosRatio >= 0) and (txtPosRatio <= 1), 'Please set txtPosRatio value between 0 and 1.'
        self.txtPosRatio = txtPosRatio
        self.positionmap = dict()
        self.wavenumbers = None
        self._meanSpec = True  # whether current spectrum is a mean spectrum
        self.line = InfiniteLine(movable=True)
        self.line.setPen((255, 255, 0, 200))
        self.line.setValue(linePos)
        self.line.sigPositionChanged.connect(self.sigEnergyChanged)
        self.line.sigPositionChanged.connect(self.getEnergy)
        self.addItem(self.line)
        self.cross = PlotDataItem([linePos], [0], symbolBrush=(255, 0, 0), symbolPen=(255, 0, 0), symbol='+',
                                  symbolSize=20)
        self.cross.setZValue(100)
        self.addItem(self.cross)
        self.txt = TextItem()
        self.getViewBox().invertX(invertX)
        self.spectrumInd = 0
        self.selectedPixels = None
        self._y = None

    def getEnergy(self, lineobject):
        if self._y is not None:
            x_val = lineobject.value()
            idx = val2ind(x_val, self.wavenumbers)
            x_val = self.wavenumbers[idx]
            y_val = self._y[idx]
            if not self._meanSpec:
                txt_html = toHtml(f'Spectrum #{self.spectrumInd}')
            else:
                txt_html = toHtml(f'{self._mean_title}')

            txt_html += toHtml(f'X = {x_val: .2f}, Y = {y_val: .4f}')
            self.txt.setHtml(txt_html)
            self.cross.setData([x_val], [y_val])

    def setHeader(self, header: NonDBHeader, field: str, *args, **kwargs):
        self.header = header
        self.field = field
        # get wavenumbers
        spectraEvent = next(header.events(fields=['spectra']))
        self.wavenumbers = spectraEvent['wavenumbers']
        self.N_w = len(self.wavenumbers)
        self.rc2ind = spectraEvent['rc_index']
        # make lazy array from document
        data = None
        try:
            data = header.meta_array(field)
        except IndexError:
            msg.logMessage(f'Header object contained no frames with field {field}.', msg.ERROR)

        if data is not None:
            # kwargs['transform'] = QTransform(1, 0, 0, -1, 0, data.shape[-2])
            self._data = data

    def showSpectra(self, i=0):
        if (self._data is not None) and (i < len(self._data)):
            self.getViewBox().clear()
            self._meanSpec = False
            self.spectrumInd = i
            self.plot(self.wavenumbers, self._data[i])

    def getSelectedPixels(self, selectedPixels):
        self.selectedPixels = selectedPixels
        # print(selectedPixels)

    def clearAll(self):
        # remove legend
        _legend = self.plotItem.legend
        if (_legend is not None) and (_legend.scene() is not None):
            _legend.scene().removeItem(_legend)
        self.getViewBox().clear()

    def showMeanSpectra(self):
        self._meanSpec = True
        self.getViewBox().clear()
        if self.selectedPixels is not None:
            n_spectra = len(self.selectedPixels)
            tmp = np.zeros((n_spectra, self.N_w))
            for j in range(n_spectra):  # j: jth selected pixel
                row_col = tuple(self.selectedPixels[j])
                tmp[j, :] = self._data[self.rc2ind[row_col]]
            self._mean_title = f'ROI mean of {n_spectra} spectra'
        else:
            n_spectra = len(self._data)
            tmp = np.zeros((n_spectra, self.N_w))
            for j in range(n_spectra):
                tmp[j, :] = self._data[j]
            self._mean_title = f'Total mean of {n_spectra} spectra'
        if n_spectra > 0:
            meanSpec = np.mean(tmp, axis=0)
        else:
            meanSpec = np.zeros_like(self.wavenumbers) + 1e-3
        self.plot(self.wavenumbers, meanSpec)

    def plot(self, x, y, *args, **kwargs):
        # set up infinity line and get its position
        self.plotItem.plot(x, y, *args, **kwargs)
        self.addItem(self.line)
        self.addItem(self.cross)
        x_val = self.line.value()
        if x_val == 0:
            y_val = 0
        else:
            idx = val2ind(x_val, self.wavenumbers)
            x_val = self.wavenumbers[idx]
            y_val = y[idx]

        if not self._meanSpec:
            txt_html = toHtml(f'Spectrum #{self.spectrumInd}')
        else:
            txt_html = toHtml(f'{self._mean_title}')

        txt_html += toHtml(f'X = {x_val: .2f}, Y = {y_val: .4f}')
        self.txt.setHtml(txt_html)
        ymax = np.max(y)
        self._y = y
        r = self.txtPosRatio
        self.txt.setPos(r * x[-1] + (1 - r) * x[0], ymax)
        self.cross.setData([x_val], [y_val])
        self.addItem(self.txt)
Exemple #14
0
class OrbitView(GraphicsLayoutWidget):
    def __init__(self,
                 orbit=None,
                 axis="X",
                 use_sector_ticks=True,
                 parent=None,
                 ymin=-1.0,
                 ymax=1.0,
                 name=None,
                 label=None,
                 units=None,
                 draw_timer=None,
                 magnet_list=None):
        super(OrbitView, self).__init__(parent=parent)
        axis = axis.lower()
        if axis not in ["x", "y", "tmit"]:
            raise Exception("Axis must be 'x', 'y', or 'tmit'")
        self.axis = axis
        self.use_sector_ticks = use_sector_ticks
        self.sector_ticks = [[], []]
        self.ci.layout.setSpacing(0.0)
        self.plotLabel = self.addLabel(text=name,
                                       row=0,
                                       col=0,
                                       rowspan=3,
                                       angle=-90)
        self.up_magnet_view = MagnetView(magnet_list=orbit, direction="up")
        self.up_magnet_view.hideAxis('left')
        self.up_magnet_view.hideAxis('bottom')
        self.ci.layout.setRowStretchFactor(0, 3)
        self.plotItem = self.addPlot(name=name, row=0, col=1)
        self.ci.layout.setRowStretchFactor(1, 0)
        self.down_magnet_view = MagnetView(magnet_list=orbit, direction="down")
        self.down_magnet_view.hideAxis('left')
        self.up_magnet_view.setXLink(self.plotItem)
        self.down_magnet_view.setXLink(self.plotItem)
        self.ci.layout.setRowStretchFactor(2, 0)
        self.show_magnet_buttons = False
        if axis != "tmit" and magnet_list is not None:
            self.show_magnet_views(True)
        self.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate
                                   )  # Greatly improves drawing performance.
        self.plotItem.setMouseEnabled(y=False)
        #Customize the right-click menu.
        self.plotItem.setMenuEnabled(enableMenu=False, enableViewBoxMenu=None)
        reset_view_range = QAction("Reset View Range", self.plotItem.vb.menu)
        reset_view_range.triggered.connect(self.reset_range)
        self.plotItem.vb.scene().contextMenu = []
        existing_menu_actions = self.plotItem.vb.menu.actions()
        self.plotItem.vb.menu.insertAction(existing_menu_actions[0],
                                           reset_view_range)
        for action in existing_menu_actions:
            if str(action.text()) == "View All":
                self.plotItem.vb.menu.removeAction(action)
        self.plotItem.showGrid(y=True)
        #self.plotItem.getAxis('left').setStyle(tickTextWidth=60)
        #self.plotItem.getAxis('left').setStyle(autoExpandTextSpace=False)
        #if label is not None:
        #   self.plotItem.getAxis('left').enableAutoSIPrefix(enable=False)
        #   self.plotItem.getAxis('left').setLabel(text=label, units=units)
        self.bpm_brush = QBrush(QColor(0, 255, 0))
        self.energy_bpm_brush = QBrush(QColor(100, 200, 255))
        self.ymin = ymin  #Y axis goes from self.ymin to self.ymax by default.
        self.ymax = ymax
        self.yminlimit = 10.0 * ymin  #This is the limit on the Y axis range.
        self.ymaxlimit = 10.0 * ymax  #This is the upper limit on the Y axis range.
        self.plotItem.setLimits(minYRange=0.04,
                                maxYRange=abs(self.ymaxlimit - self.yminlimit))
        self.axis_pen = QPen(QBrush(QColor(255, 255, 255)), 0)
        self.axis_pen.setCapStyle(Qt.FlatCap)
        self.bpm_pen = QPen(self.bpm_brush, 2)
        self.bpm_pen.setCosmetic(True)
        self.bpm_pen.setCapStyle(Qt.FlatCap)
        self.no_beam_brush = QBrush(QColor(0, 255, 0, 45))
        self.no_beam_pen = QPen(self.no_beam_brush, 2)
        self.no_beam_pen.setCosmetic(True)
        self.no_beam_pen.setCapStyle(Qt.FlatCap)
        self.energy_bpm_pen = QPen(self.energy_bpm_brush, 2)
        self.energy_bpm_pen.setCosmetic(True)
        self.energy_bpm_pen.setCapStyle(Qt.FlatCap)
        self.fit_brush = QBrush(QColor(255, 255, 255, 255))
        self.fit_pen = QPen(self.fit_brush, 0)
        self.fit_pen.setCosmetic(True)
        self.fit_pen.setCapStyle(Qt.FlatCap)
        self.axis_line = QGraphicsLineItem(0.0, 0.0, 1.0, 0.0)
        self.axis_line.setPen(self.axis_pen)
        self.plotItem.addItem(self.axis_line, ignoreBounds=True)
        self.lines = {}
        self.orbit = None
        self.needs_initial_range = True
        self.set_draw_timer(draw_timer)
        self._display_fit = False
        self.fit_data_item = None
        self.fit_options = {}
        if orbit is not None:
            self.set_orbit(orbit)

    def make_right_click_menu(self):
        menu = QMenu(self)
        return menu

    def display_fit(self, enabled=True):
        if enabled and self.fit_data_item is None:
            self.fit_data_item = PlotDataItem(pen=self.fit_pen)
            self.plotItem.addItem(self.fit_data_item)
        elif not enabled:
            self.plotItem.removeItem(self.fit_data_item)
            self.fit_data_item = None
        self._display_fit = enabled

    def set_draw_timer(self, new_timer, start=False):
        try:
            self.draw_timer.timeout.disconnect(self.redraw_bpms)
        except:
            pass
        if new_timer is None:
            new_timer = QTimer(self)
            new_timer.setInterval(int(1000 / 60))
        self.draw_timer = new_timer
        self.draw_timer.timeout.connect(self.redraw_bpms)
        if start:
            self.draw_timer.start()

    def set_orbit(self, orbit, reset_range=True):
        if self.orbit == orbit:
            return
        old_range = None
        old_zmax = None
        old_zmin = None
        if self.orbit is not None:
            old_range = self.plotItem.viewRect()
            old_zmax = self.orbit.zmax()
            old_zmin = self.orbit.zmin()
        self.clear_orbit()
        self.orbit = orbit
        extent = self.orbit.zmax() - self.orbit.zmin()
        self.plotItem.setLimits(xMin=self.orbit.zmin() - (0.02 * extent),
                                xMax=self.orbit.zmax() + (0.02 * extent))
        self.plotItem.enableAutoRange(enable=False)
        self.axis_line.setLine(self.orbit.zmin(), 0.0, self.orbit.zmax(), 0.0)
        for bpm in self.orbit:
            line = BPMLineItem(bpm)
            self.lines[bpm.name] = line
            self.set_pen_for_bpm(bpm)
            self.plotItem.addItem(self.lines[bpm.name])
        if self.use_sector_ticks and (old_zmax != orbit.zmax()
                                      or old_zmin != orbit.zmin()):
            self.sector_ticks = [[], []]
            self.sector_ticks[0] = self.orbit.sector_locations()
            unit_nums = [name.split(":")[-1] for name in self.orbit.names()]
            self.sector_ticks[1] = zip(self.orbit.z_vals(), unit_nums)
            self.plotItem.getAxis('bottom').setTicks(self.sector_ticks)
            self.plotItem.getAxis('bottom').setStyle(textFillLimits=[(0,
                                                                      0.72)])
            self.plotItem.showGrid(x=True)
        if reset_range or self.needs_initial_range:
            self.reset_range()
            self.needs_initial_range = False
        else:
            self.plotItem.setRange(old_range, padding=0.0, update=False)
        self.draw_timer.start()

    def show_magnet_views(self, enabled):
        if enabled == self.show_magnet_buttons:
            return
        self.show_magnet_buttons = enabled
        if enabled:
            self.addItem(self.up_magnet_view, row=1, col=1)
            self.addItem(self.down_magnet_view, row=2, col=1)
            self.up_magnet_view.setXLink(self.plotItem)
            self.down_magnet_view.setXLink(self.plotItem)
        else:
            self.removeItem(self.up_magnet_view)
            self.removeItem(self.down_magnet_view)

    def set_magnet_list(self, magnet_list):
        self.up_magnet_view.set_magnets(magnet_list, reset_range=False)
        self.down_magnet_view.set_magnets(magnet_list, reset_range=False)

    @pyqtSlot(bool)
    def reset_range(self, checked=False):
        self.plotItem.enableAutoRange(axis=ViewBox.XAxis)
        self.plotItem.setYRange(self.ymin, self.ymax)

    def wheelEvent(self, event):
        if event.modifiers() == Qt.ShiftModifier:
            numPixels = event.pixelDelta()
            numDegrees = event.angleDelta()
            if not numPixels.isNull():
                s = (1.005)**(numPixels.y())
            else:
                s = (1.005)**(numDegrees.y() * (-1.0 / 8.0))
            self.plotItem.vb.scaleBy(y=s)
        else:
            super(OrbitView, self).wheelEvent(event)

    def clear_orbit(self):
        self.draw_timer.stop()
        auto_range_x_enabled = self.plotItem.vb.state['autoRange'][0]
        auto_range_y_enabled = self.plotItem.vb.state['autoRange'][1]
        self.plotItem.enableAutoRange(enable=False)
        if self.orbit is None:
            return
        for bpm in self.orbit:
            self.plotItem.removeItem(self.lines[bpm.name])
        self.plotItem.enableAutoRange(x=auto_range_x_enabled,
                                      y=auto_range_y_enabled)
        self.lines = {}

    @pyqtSlot()
    def redraw_bpms(self):
        for bpm in self.orbit:
            self.set_pen_for_bpm(bpm)
            self.lines[bpm.name].setLine(bpm.z, 0.0, bpm.z, bpm[self.axis])
        self.update_fit()

    def set_pen_for_bpm(self, bpm):
        if bpm.severity(self.axis) != 0:
            self.lines[bpm.name].setPen(self.no_beam_pen)
        else:
            if bpm.is_energy_bpm:
                self.lines[bpm.name].setPen(self.energy_bpm_pen)
            else:
                self.lines[bpm.name].setPen(self.bpm_pen)

    def update_fit(self):
        if not self._display_fit:
            return
        if self.orbit.fit_data is None:
            if self.fit_data_item is not None:
                self.fit_data_item.hide()
            return
        fit_data = None
        if self.axis == 'x':
            fit_data = self.orbit.fit_data['xpos']
        elif self.axis == 'y':
            fit_data = self.orbit.fit_data['ypos']
        self.fit_data_item.show()
        self.fit_data_item.setData(x=self.orbit.fit_data['zs'], y=fit_data)

    @pyqtSlot()
    def stop(self):
        self.draw_timer.stop()

    @pyqtSlot()
    def start(self):
        if self.orbit is not None:
            self.draw_timer.start()

    def setXLink(self, view):
        return self.plotItem.setXLink(view.plotItem)

    def setYLink(self, view):
        return self.plotItem.setYLink(view.plotItem)
Exemple #15
0
class Screen(QWidget):
    def __init__(self):
        super().__init__()

        grid_layout = QGridLayout()
        self.setLayout(grid_layout)

        timer = QTimer(self)
        timer.timeout.connect(self.update_data)
        timer.start(1000)

        temp_title = QLabel(
            text='Tempture:',
            alignment=Qt.AlignCenter,
            styleSheet=TITLE_STYLES,
        )
        grid_layout.addWidget(temp_title)
        self.temp_data = PlotDataItem(temps)
        temp_widget = PlotWidget()
        temp_widget.addItem(self.temp_data)
        temp_widget.setXRange(0, max_history)
        temp_widget.setYRange(-10, 60)
        grid_layout.addWidget(temp_widget)

        fan_title = QLabel(
            text='Fan',
            alignment=Qt.AlignCenter,
            styleSheet=TITLE_STYLES,
        )
        grid_layout.addWidget(fan_title)
        self.fan_data = PlotDataItem(fans)
        fan_widget = PlotWidget()
        fan_widget.addItem(self.fan_data)
        fan_widget.setXRange(0, max_history)
        fan_widget.setYRange(0, 100)
        grid_layout.addWidget(fan_widget)

        last_log_title = QLabel(
            text='Lastest Event',
            alignment=Qt.AlignCenter,
            styleSheet=TITLE_STYLES,
        )
        grid_layout.addWidget(last_log_title)
        self.last_log = QLabel()
        self.last_log.resize(WINDOW_HEIGHT, WINDOW_WIDTH)
        self.last_log.setStyleSheet('font-weight: bold;'
                                    'background-color: grey;')
        grid_layout.addWidget(self.last_log)

        logs_title = QLabel(
            text='Event History',
            alignment=Qt.AlignCenter,
            styleSheet=TITLE_STYLES,
        )
        grid_layout.addWidget(logs_title)
        self.logs = QPlainTextEdit(self)
        self.logs.resize(WINDOW_HEIGHT, WINDOW_WIDTH)
        self.logs.resize(400, 200)
        grid_layout.addWidget(self.logs)

        self.setWindowTitle('Screen')

    def update_data(self):
        global counter

        # Fixme:  Foo data
        temps.append(counter)
        fans.append(counter)
        new_log = f'Event {counter} happend'

        counter += randint(-5, 10)
        self.temp_data.setData(temps[-max_history:])
        self.fan_data.setData(fans[-max_history:])

        if new_log:
            date_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            date_log = f'{date_time}: {new_log}'
            self.logs.setPlainText(f'{self.last_log.text()}'
                                   '\n'
                                   f'{self.logs.toPlainText()}')
            self.last_log.setText(date_log)

        self.update()
Exemple #16
0
class CurveItem:
    """Represents a curve to be plotted in a diagram."""

    def __init__(self, subscription_id, driver_addr, sig_name, y_axis,
                 linecolor, linestyle, linemarker):
        """
        Initializes an instance of class CurveItem.

        driver_addr - IcePAP driver address.
        sig_name    - Signal name.
        y_axis      - Y axis to plot against.
        """
        self.subscription_id = subscription_id
        self.driver_addr = driver_addr
        self.signal_name = sig_name
        self.y_axis = y_axis
        self.array_time = []
        self.array_val = []
        self.val_min = 0
        self.val_max = 0
        self.color = linecolor
        self.pen = {'color': linecolor,
                    'width': 1,
                    'style': linestyle}
        self.symbol = linemarker
        self.curve = None
        self.lock = RLock()
        self.signature = ''
        self.update_signature()

    def update_signature(self):
        """Sets the new value of the signature string."""
        self.signature = '{}:{}:{}'.format(self.driver_addr,
                                           self.signal_name,
                                           self.y_axis)

    def create_curve(self):
        """Creates a new plot item."""
        with self.lock:
            if self.symbol != '':
                self.curve = PlotDataItem(x=self.array_time,
                                          y=self.array_val,
                                          pen=self.pen,
                                          symbol=self.symbol,
                                          symbolBrush=QtGui.QBrush(self.color),
                                          symbolPen=self.color)
            else:
                self.curve = PlotDataItem(x=self.array_time,
                                          y=self.array_val,
                                          pen=self.pen)

        return self.curve

    def update_curve(self, time_min, time_max):
        """Updates the curve with recent collected data."""
        with self.lock:
            idx_min = self.get_time_index(time_min)
            idx_max = self.get_time_index(time_max)
            self.curve.setData(x=self.array_time[idx_min:idx_max],
                               y=self.array_val[idx_min:idx_max])

    def in_range(self, t):
        """
        Check to see if time is within range of collected data.

        t - Time value.
        Return: True if time is within range of collected data.
                Otherwise False.
        """
        with self.lock:
            if self.array_time and \
                    self.array_time[0] < t < self.array_time[-1]:
                return True
        return False

    def start_time(self):
        """
        Get time for first data sample.

        Return: Time of the first collected data sample. -1 if none.
        """
        with self.lock:
            if self.array_time:
                return self.array_time[0]
        return -1

    def collect(self, new_data):
        """Store new collected data."""
        with self.lock:
            if not self.array_val:
                self.val_min = self.val_max = new_data[0][1]
            for t, v in new_data:
                self.array_time.append(t)
                self.array_val.append(v)
                if v > self.val_max:
                    self.val_max = v
                elif v < self.val_min:
                    self.val_min = v

    def get_y(self, time_val):
        """
        Retrieve the signal value corresponding to the provided time value.

        t_val - Time value.
        Return: Signal value corresponding to an adjacent sample in time.
        """
        with self.lock:
            idx = self.get_time_index(time_val)
            return self.array_val[idx]

    def clear(self):
        self.array_time[:] = []
        self.array_val[:] = []

    def get_time_index(self, time_val):
        """
        Retrieve the sample index corresponding to the provided time value.

        t_val - Time value.
        Return: Index of a sample adjacent to the provided time value.
        """
        with self.lock:
            if not self.array_time:
                return -1
            if len(self.array_time) == 1:
                return 0
            time_min = self.array_time[0]
            time_max = self.array_time[-1]
            if time_val < time_min:
                return 0
            elif time_val > time_max:
                return len(self.array_time)
            delta_t = time_max - time_min
            t = time_val - time_min
            idx = int((t / delta_t) * len(self.array_time))
            while self.array_time[idx] > time_val:
                idx -= 1
            while self.array_time[idx] < time_val:
                idx += 1
            return idx
Exemple #17
0
class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        _dir = os.path.dirname(os.path.abspath(__file__))
        #uic.loadUi(_dir + '/' + 'layout.ui', self)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.profileWidget.hide()
        self.ui.smallDataWidget.hide()
        self.ui.splitter_3.setSizes([
            self.width() * 0.7, self.width() * 0.3
        ])  # splitter between image_view/plot_widget and file list
        self.ui.splitter_2.setSizes([
            self.height() * 0.7, self.height() * 0.3
        ])  # splitter between image_view and plot widget
        self.ui.splitter.setSizes([
            self.ui.splitter.width() / 3.,
            self.ui.splitter.width() / 3.,
            self.ui.splitter.width() / 3.
        ])
        self.setAcceptDrops(True)
        self.ui.fileList.setColumnWidth(0, 200)

        self.filepath = None  # current filepath
        self.imageData = None  # original image data, 2d or 3d
        self.imageShape = None
        self.dispData = None  # 2d data for plot
        self.dispShape = None
        self.mask = None
        self.curve = None
        self.h5obj = None
        self.acceptedFileTypes = [
            u'npy', u'npz', u'h5', u'mat', u'cxi', u'tif'
        ]

        self.dispItem = self.ui.imageView.getImageItem()
        self.ringItem = pg.ScatterPlotItem()
        self.centerMarkItem = pg.ScatterPlotItem()
        self.ui.imageView.getView().addItem(self.ringItem)
        self.ui.imageView.getView().addItem(self.centerMarkItem)

        self.curveItem = PlotDataItem()
        self.ui.curveWidget.addItem(self.curveItem)
        self.ui.curveWidget.getPlotItem().setTitle(title='Curve Plot')

        # basic operation for image and curve plot
        self.showImage = True
        self.axis = 'x'
        self.frameIndex = 0
        self.maskFlag = False
        self.imageLog = False
        self.binaryFlag = False
        self.FFT = False
        self.FFTSHIFT = False
        self.dispThreshold = 0
        self.center = [0, 0]
        self.showRings = False
        self.ringRadiis = []
        self.showCurvePlot = True

        self.profileItem = PlotDataItem(pen=pg.mkPen(
            'y', width=1, style=QtCore.Qt.SolidLine),
                                        name='profile')
        self.smoothItem = PlotDataItem(pen=pg.mkPen('g',
                                                    width=2,
                                                    style=QtCore.Qt.DotLine),
                                       name='smoothed profile')
        self.thresholdItem = PlotDataItem(pen=pg.mkPen('r'),
                                          width=1,
                                          style=QtCore.Qt.DashLine,
                                          name='threshold')
        self.ui.profileWidget.addLegend()
        self.ui.profileWidget.addItem(self.profileItem)
        self.ui.profileWidget.addItem(self.smoothItem)
        self.ui.profileWidget.addItem(self.thresholdItem)
        self.ui.profileWidget.getPlotItem().setTitle(title='Profile Plot')
        # profile option
        self.showProfile = False
        self.profileType = 'radial'
        self.profileMode = 'sum'
        # extrema search
        self.extremaSearch = False
        self.extremaType = 'max'
        self.extremaWinSize = 11
        self.extremaThreshold = 1.0  # ratio compared to mean value
        # angular option
        self.angularRmin = 0.
        self.angularRmax = np.inf
        # across center line option
        self.lineAngle = 0.
        self.lineWidth = 1
        # profile smoothing
        self.smoothFlag = False
        self.smoothWinSize = 15
        self.polyOrder = 3
        # small data option
        self.smallDataItem = pg.ScatterPlotItem()
        self.ui.smallDataWidget.addItem(self.smallDataItem)
        self.ui.smallDataWidget.getPlotItem().setTitle(title='Small Data')
        self.showSmallData = False
        self.smallDataFile = None
        self.smallDataset = None
        self.smallDataSorted = False
        self.smallDataPaths = None
        self.smallDataFrames = None
        self.smallData = None
        # display option
        self.imageAutoRange = False
        self.imageAutoLevels = False
        self.imageAutoHistogramRange = True
        self.curvePlotAutoRange = True
        self.curvePlotLog = False
        self.profilePlotAutoRange = True
        self.profilePlotLog = False
        self.smallDataPlotAutoRange = True
        self.smallDataPlotLog = False

        params_list = [{
            'name':
            'Data Info',
            'type':
            'group',
            'children': [{
                'name': 'File',
                'type': 'str',
                'value': 'not set',
                'readonly': True
            }, {
                'name': 'Dataset',
                'type': 'str',
                'value': 'not set',
                'readonly': True
            }, {
                'name': 'Mask',
                'type': 'str',
                'value': 'not set',
                'readonly': True
            }, {
                'name': 'Image Shape',
                'type': 'str',
                'value': 'unknown',
                'readonly': True
            }, {
                'name': 'Image Friedel Score',
                'type': 'float',
                'readonly': True
            }, {
                'name': 'Curve Length',
                'type': 'int',
                'readonly': 'True'
            }]
        }, {
            'name':
            'Basic Operation',
            'type':
            'group',
            'children': [{
                'name':
                'Image',
                'type':
                'group',
                'children': [
                    {
                        'name': 'Axis',
                        'type': 'list',
                        'values': ['x', 'y', 'z'],
                        'value': self.axis
                    },
                    {
                        'name': 'Frame Index',
                        'type': 'int',
                        'value': self.frameIndex
                    },
                    {
                        'name': 'Apply Mask',
                        'type': 'bool',
                        'value': self.imageLog
                    },
                    {
                        'name': 'Apply Log',
                        'type': 'bool',
                        'value': self.maskFlag
                    },
                    {
                        'name': 'Apply FFT',
                        'type': 'bool',
                        'value': self.FFT
                    },
                    {
                        'name': 'Apply FFT-SHIFT',
                        'type': 'bool',
                        'value': self.FFTSHIFT
                    },
                    {
                        'name': 'Binaryzation',
                        'type': 'bool',
                        'value': self.binaryFlag
                    },
                    {
                        'name': 'Threshold',
                        'type': 'float',
                        'value': self.dispThreshold
                    },
                    {
                        'name': 'Center x',
                        'type': 'int',
                        'value': self.center[1]
                    },
                    {
                        'name': 'Center y',
                        'type': 'int',
                        'value': self.center[0]
                    },
                    {
                        'name': 'Show Rings',
                        'type': 'bool'
                    },
                    {
                        'name': 'Ring Radiis',
                        'type': 'str',
                        'value': ''
                    },
                ]
            }, {
                'name': 'Curve Plot',
                'type': 'group',
                'children': [
                    {
                        'name': 'TODO',
                        'type': 'str'
                    },
                ]
            }]
        }, {
            'name':
            'Image to Feature Profile',
            'type':
            'group',
            'children': [{
                'name': 'Show Profile',
                'type': 'bool',
                'value': self.showProfile
            }, {
                'name': 'Feature',
                'type': 'list',
                'values': ['radial', 'angular', 'across center line'],
                'value': self.profileType
            }, {
                'name': 'Profile Mode',
                'type': 'list',
                'values': ['sum', 'mean'],
                'value': self.profileMode
            }, {
                'name':
                'Angular Option',
                'type':
                'group',
                'children': [
                    {
                        'name': 'R min',
                        'type': 'float',
                        'value': self.angularRmin
                    },
                    {
                        'name': 'R max',
                        'type': 'float',
                        'value': self.angularRmax
                    },
                ]
            }, {
                'name':
                'Across Center Line Option',
                'type':
                'group',
                'children': [
                    {
                        'name': 'Angle',
                        'type': 'float',
                        'value': self.lineAngle
                    },
                    {
                        'name': 'Width/pixel',
                        'type': 'int',
                        'value': self.lineWidth
                    },
                ]
            }, {
                'name':
                'Smoothing',
                'type':
                'group',
                'children': [
                    {
                        'name': 'Enable Smoothing',
                        'type': 'bool',
                        'value': self.smoothFlag
                    },
                    {
                        'name': 'Window Size',
                        'type': 'int',
                        'value': self.smoothWinSize
                    },
                    {
                        'name': 'Poly-Order',
                        'type': 'int',
                        'value': self.polyOrder
                    },
                ]
            }, {
                'name':
                'Extrema Search',
                'type':
                'group',
                'children': [
                    {
                        'name': 'Enable Extrema Search',
                        'type': 'bool',
                        'value': self.extremaSearch
                    },
                    {
                        'name': 'Extrema Type',
                        'type': 'list',
                        'values': ['max', 'min'],
                        'value': self.extremaType
                    },
                    {
                        'name': 'Extrema WinSize',
                        'type': 'int',
                        'value': self.extremaWinSize
                    },
                    {
                        'name': 'Extrema Threshold',
                        'type': 'float',
                        'value': self.extremaThreshold
                    },
                ]
            }]
        }, {
            'name':
            'Small Data',
            'type':
            'group',
            'children': [
                {
                    'name': 'Filepath',
                    'type': 'str'
                },
                {
                    'name': 'Dataset',
                    'type': 'str'
                },
                {
                    'name': 'Show data',
                    'type': 'bool',
                    'value': self.showSmallData
                },
                {
                    'name': 'Sort',
                    'type': 'bool',
                    'value': self.smallDataSorted
                },
            ]
        }, {
            'name':
            'Display Option',
            'type':
            'group',
            'children': [
                {
                    'name':
                    'Image',
                    'type':
                    'group',
                    'children': [
                        {
                            'name': 'autoRange',
                            'type': 'bool',
                            'value': self.imageAutoRange
                        },
                        {
                            'name': 'autoLevels',
                            'type': 'bool',
                            'value': self.imageAutoLevels
                        },
                        {
                            'name': 'autoHistogramRange',
                            'type': 'bool',
                            'value': self.imageAutoHistogramRange
                        },
                    ]
                },
                {
                    'name':
                    'Curve Plot',
                    'type':
                    'group',
                    'children': [
                        {
                            'name': 'autoRange',
                            'type': 'bool',
                            'value': self.curvePlotAutoRange
                        },
                        {
                            'name': 'Log',
                            'type': 'bool',
                            'value': self.curvePlotLog
                        },
                    ]
                },
                {
                    'name':
                    'Profile Plot',
                    'type':
                    'group',
                    'children': [
                        {
                            'name': 'autoRange',
                            'type': 'bool',
                            'value': self.profilePlotAutoRange
                        },
                        {
                            'name': 'Log',
                            'type': 'bool',
                            'value': self.profilePlotLog
                        },
                    ]
                },
                {
                    'name':
                    'Small Data Plot',
                    'type':
                    'group',
                    'children': [
                        {
                            'name': 'autoRange',
                            'type': 'bool',
                            'value': self.smallDataPlotAutoRange
                        },
                        {
                            'name': 'Log',
                            'type': 'bool',
                            'value': self.smallDataPlotLog
                        },
                    ]
                },
            ]
        }]
        self.params = Parameter.create(name='params',
                                       type='group',
                                       children=params_list)
        self.ui.parameterTree.setParameters(self.params, showTop=False)

        self.ui.fileList.itemDoubleClicked.connect(self.changeDatasetSlot)
        self.ui.fileList.customContextMenuRequested.connect(
            self.showFileMenuSlot)
        self.ui.imageView.scene.sigMouseMoved.connect(self.mouseMoved)
        self.smallDataItem.sigClicked.connect(self.smallDataClicked)
        self.ui.lineEdit.returnPressed.connect(self.addFilesSlot)

        self.params.param('Basic Operation', 'Image',
                          'Axis').sigValueChanged.connect(self.axisChangedSlot)
        self.params.param('Basic Operation', 'Image',
                          'Frame Index').sigValueChanged.connect(
                              self.frameIndexChangedSlot)
        self.params.param('Basic Operation', 'Image',
                          'Apply Mask').sigValueChanged.connect(
                              self.applyMaskSlot)
        self.params.param('Basic Operation', 'Image',
                          'Apply Log').sigValueChanged.connect(
                              self.applyImageLogSlot)
        self.params.param('Basic Operation', 'Image',
                          'Binaryzation').sigValueChanged.connect(
                              self.binaryImageSlot)
        self.params.param('Basic Operation', 'Image',
                          'Apply FFT').sigValueChanged.connect(
                              self.applyFFTSlot)
        self.params.param('Basic Operation', 'Image',
                          'Apply FFT-SHIFT').sigValueChanged.connect(
                              self.applyFFTSHIFTSlot)
        self.params.param('Basic Operation', 'Image',
                          'Binaryzation').sigValueChanged.connect(
                              self.binaryImageSlot)
        self.params.param('Basic Operation', 'Image',
                          'Threshold').sigValueChanged.connect(
                              self.setDispThresholdSlot)
        self.params.param('Basic Operation', 'Image',
                          'Center x').sigValueChanged.connect(
                              self.centerXChangedSlot)
        self.params.param('Basic Operation', 'Image',
                          'Center y').sigValueChanged.connect(
                              self.centerYChangedSlot)
        self.params.param('Basic Operation', 'Image',
                          'Show Rings').sigValueChanged.connect(
                              self.showRingsSlot)
        self.params.param('Basic Operation', 'Image',
                          'Ring Radiis').sigValueChanged.connect(
                              self.ringRadiiSlot)

        self.params.param('Image to Feature Profile',
                          'Show Profile').sigValueChanged.connect(
                              self.showProfileSlot)
        self.params.param('Image to Feature Profile',
                          'Feature').sigValueChanged.connect(
                              self.setProfileTypeSlot)
        self.params.param('Image to Feature Profile',
                          'Profile Mode').sigValueChanged.connect(
                              self.setProfileModeSlot)
        self.params.param('Image to Feature Profile', 'Angular Option',
                          'R min').sigValueChanged.connect(
                              self.setAngularRminSlot)
        self.params.param('Image to Feature Profile', 'Angular Option',
                          'R max').sigValueChanged.connect(
                              self.setAngularRmaxSlot)
        self.params.param(
            'Image to Feature Profile', 'Across Center Line Option',
            'Angle').sigValueChanged.connect(self.setLineAngleSlot)
        self.params.param(
            'Image to Feature Profile', 'Across Center Line Option',
            'Width/pixel').sigValueChanged.connect(self.setLineWidthSlot)
        self.params.param('Image to Feature Profile', 'Smoothing',
                          'Enable Smoothing').sigValueChanged.connect(
                              self.setSmoothSlot)
        self.params.param('Image to Feature Profile', 'Smoothing',
                          'Window Size').sigValueChanged.connect(
                              self.setWinSizeSlot)
        self.params.param('Image to Feature Profile', 'Smoothing',
                          'Poly-Order').sigValueChanged.connect(
                              self.setPolyOrderSlot)
        self.params.param('Image to Feature Profile', 'Extrema Search',
                          'Enable Extrema Search').sigValueChanged.connect(
                              self.setExtremaSearchSlot)
        self.params.param('Image to Feature Profile', 'Extrema Search',
                          'Extrema Type').sigValueChanged.connect(
                              self.setExtremaTypeSlot)
        self.params.param('Image to Feature Profile', 'Extrema Search',
                          'Extrema WinSize').sigValueChanged.connect(
                              self.setExtremaWinSizeSlot)
        self.params.param('Image to Feature Profile', 'Extrema Search',
                          'Extrema Threshold').sigValueChanged.connect(
                              self.setExtremaThresholdSlot)

        self.params.param('Small Data', 'Filepath').sigValueChanged.connect(
            self.setSmallDataFilepathSlot)
        self.params.param('Small Data', 'Dataset').sigValueChanged.connect(
            self.setSmallDatasetSlot)
        self.params.param('Small Data', 'Show data').sigValueChanged.connect(
            self.showSmallDataSlot)
        self.params.param('Small Data', 'Sort').sigValueChanged.connect(
            self.sortSmallDataSlot)

        self.params.param('Display Option', 'Image',
                          'autoRange').sigValueChanged.connect(
                              self.imageAutoRangeSlot)
        self.params.param('Display Option', 'Image',
                          'autoLevels').sigValueChanged.connect(
                              self.imageAutoLevelsSlot)
        self.params.param('Display Option', 'Image',
                          'autoHistogramRange').sigValueChanged.connect(
                              self.imageAutoHistogramRangeSlot)
        self.params.param('Display Option', 'Curve Plot',
                          'autoRange').sigValueChanged.connect(
                              self.curvePlotAutoRangeSlot)
        self.params.param('Display Option', 'Curve Plot',
                          'Log').sigValueChanged.connect(
                              self.curvePlotLogModeSlot)
        self.params.param('Display Option', 'Profile Plot',
                          'autoRange').sigValueChanged.connect(
                              self.profilePlotAutoRangeSlot)
        self.params.param('Display Option', 'Profile Plot',
                          'Log').sigValueChanged.connect(
                              self.profilePlotLogModeSlot)
        self.params.param('Display Option', 'Small Data Plot',
                          'autoRange').sigValueChanged.connect(
                              self.smallDataPlotAutoRangeSlot)
        self.params.param('Display Option', 'Small Data Plot',
                          'Log').sigValueChanged.connect(
                              self.smallDataPlotLogModeSlot)

    def addFilesSlot(self):
        filePattern = str(self.ui.lineEdit.text())
        #print_with_timestamp('file(s) pattern: %s' %filePattern)
        files = glob.glob(filePattern)
        #print_with_timestamp('adding file(s): %s \n Total Num: %d' %(str(files), len(files)))
        for i in xrange(len(files)):
            self.maybeAddFile(files[i])

    def smallDataClicked(self, _, points):
        _temp_file = '.temp.npy'
        pos = points[0].pos()
        index = int(points[0].pos()[0])
        if self.smallDataSorted:
            index = np.argsort(self.smallData)[index]
        filepath = self.smallDataPaths[index]
        frame = self.smallDataFrames[index]
        #print_with_timestamp('showing file: %s, frame: %d' %(filepath, frame))
        make_temp_file(filepath, frame, _temp_file)
        maybeExistIndex = self.ui.fileList.indexOf(_temp_file)
        if maybeExistIndex != -1:
            self.ui.fileList.takeTopLevelItem(maybeExistIndex)
        item = FileItem(filepath=_temp_file)
        self.ui.fileList.insertTopLevelItem(0, item)
        self.changeDatasetSlot(item, 0)

    def setSmallDataFilepathSlot(self, _, filepath):
        #print_with_timestamp('set filepath for small data: %s' % str(filepath))
        self.smallDataFile = filepath
        self.maybeShowSmallData()

    def setSmallDatasetSlot(self, _, dataset):
        #print_with_timestamp('set dataset for small data: %s' % str(dataset))
        self.smallDataset = dataset
        self.maybeShowSmallData()

    def showSmallDataSlot(self, _, showSmallData):
        #print_with_timestamp('set show small data: %s' % str(showSmallData))
        # self.showSmallData = showSmallData
        if showSmallData:
            self.ui.smallDataWidget.show()
        else:
            self.ui.smallDataWidget.hide()
        self.maybeShowSmallData()

    def sortSmallDataSlot(self, _, sort):
        #print_with_timestamp('set show small data sorted: %s' % str(sort))
        self.smallDataSorted = sort
        self.maybeShowSmallData()

    def dragEnterEvent(self, event):
        urls = event.mimeData().urls()
        for url in urls:
            if QtCore.QString(url.toLocalFile()).startsWith('/.file/id='):
                dropFile = getFilepathFromLocalFileID(url)
            else:
                dropFile = url.toLocalFile()
            fileInfo = QtCore.QFileInfo(dropFile)
            ext = fileInfo.suffix()
            if ext in self.acceptedFileTypes:
                event.accept()
                return None
        event.ignore()
        return None

    def dropEvent(self, event):
        urls = event.mimeData().urls()
        for url in urls:
            if QtCore.QString(url.toLocalFile()).startsWith('/.file/id='):
                dropFile = getFilepathFromLocalFileID(url)
            else:
                dropFile = url.toLocalFile()
            self.maybeAddFile(dropFile)

    def maybeAddFile(self, filepath):
        ext = QtCore.QFileInfo(filepath).suffix()
        if ext in self.acceptedFileTypes:
            maybeExistIndex = self.ui.fileList.indexOf(filepath)
            if maybeExistIndex != -1:
                self.ui.fileList.takeTopLevelItem(maybeExistIndex)
            item = FileItem(filepath=filepath)
            if item.childCount() > 0:
                self.ui.fileList.insertTopLevelItem(0, item)

    def loadData(self, filepath, datasetName):
        self.filepath = str(filepath)
        basename = os.path.basename(self.filepath)
        data = load_data(self.filepath, datasetName)
        if type(data) == list:
            if self.h5obj is not None:
                self.h5obj.close()
                self.h5obj = None
            self.h5obj = data[0]
            data = data[1]
        if len(data.shape) == 1:  # 1d curve
            if data.size == 1:  # single number
                return None
            else:
                self.curve = data
                return None
        self.imageData = data
        self.imageShape = data.shape
        _shape_str = ''
        if len(self.imageShape) == 2:
            _x, _y = self.imageShape
            _shape_str = 'x: %d, y: %d' % (_x, _y)
        elif len(self.imageShape) == 3:
            _x, _y, _z = self.imageShape
            _shape_str = 'x: %d, y: %d, z: %d' % (_x, _y, _z)
        self.params.param('Data Info', 'Image Shape').setValue(_shape_str)
        self.params.param('Data Info', 'File').setValue(basename)
        self.params.param('Data Info', 'Dataset').setValue(datasetName)
        if len(self.imageShape) == 3:
            self.dispShape = self.imageData.shape[1:3]
        else:
            self.dispShape = self.imageShape
        self.center = self.calcCenter()
        self.setCenterInfo()

    def setCenterInfo(self):
        self.params.param('Basic Operation', 'Image',
                          'Center x').setValue(self.center[0])
        self.params.param('Basic Operation', 'Image',
                          'Center y').setValue(self.center[1])

    def calcCenter(self):
        if len(self.imageShape) == 2:
            center = [self.imageShape[1] // 2, self.imageShape[0] // 2]
            return center
        assert len(self.imageShape) == 3
        if self.axis == 'x':
            center = [self.imageShape[2] // 2, self.imageShape[1] // 2]
        elif self.axis == 'y':
            center = [self.imageShape[2] // 2, self.imageShape[0] // 2]
        else:
            center = [self.imageShape[1] // 2, self.imageShape[0] // 2]
        return center

    def maybeShowSmallData(self):
        if self.showSmallData:
            if not self.ui.smallDataWidget.isVisible():
                self.ui.smallDataWidget.show()
            self.ui.smallDataWidget.setLabels(bottom='index', left='metric')
            if self.smallDataFile is not None and self.smallDataset is not None:
                paths, frames, smallData = load_smalldata(
                    self.smallDataFile, self.smallDataset)
                self.smallDataPaths = paths
                self.smallDataFrames = frames
                self.smallData = smallData
                if self.smallDataSorted:
                    index_array = np.argsort(self.smallData).astype(np.int32)
                    self.smallDataItem.setData(x=np.arange(
                        self.smallData.size),
                                               y=self.smallData[index_array])
                else:
                    self.smallDataItem.setData(x=np.arange(
                        self.smallData.size),
                                               y=self.smallData)

    def maybePlotProfile(self):
        if self.showProfile and self.dispData is not None:
            if self.mask is None:
                self.mask = np.ones_like(self.dispData)
            mask = self.mask.copy()
            if self.maskFlag == True:
                assert mask.shape == self.dispData.shape
            if self.profileType == 'radial':
                profile = calc_radial_profile(self.dispData,
                                              self.center,
                                              mask=mask,
                                              mode=self.profileMode)
            elif self.profileType == 'angular':
                annulus_mask = make_annulus(self.dispShape, self.angularRmin,
                                            self.angularRmax)
                profile = calc_angular_profile(self.dispData,
                                               self.center,
                                               mask=mask * annulus_mask,
                                               mode=self.profileMode)
            else:  # across center line
                profile = calc_across_center_line_profile(
                    self.dispData,
                    self.center,
                    angle=self.lineAngle,
                    width=self.lineWidth,
                    mask=self.mask,
                    mode=self.profileMode)
            if self.profilePlotLog == True:
                profile[profile < 1.] = 1.
            self.profileItem.setData(profile)
            if self.profileType == 'radial':
                self.ui.profileWidget.setTitle('Radial Profile')
                self.ui.profileWidget.setLabels(bottom='r/pixel')
            elif self.profileType == 'angular':
                self.ui.profileWidget.setTitle('Angular Profile')
                self.ui.profileWidget.setLabels(bottom='theta/deg')
            else:
                self.ui.profileWidget.setTitle('Across Center Line Profile')
                self.ui.profileWidget.setLabels(bottom='index/pixel')
            if self.smoothFlag == True:
                smoothed_profile = savgol_filter(profile, self.smoothWinSize,
                                                 self.polyOrder)
                if self.profilePlotLog == True:
                    smoothed_profile[smoothed_profile < 1.] = 1.
                self.smoothItem.setData(smoothed_profile)
            else:
                self.smoothItem.clear()
            profile_with_noise = profile.astype(np.float) + np.random.rand(
                profile.size
            ) * 1E-5  # add some noise to avoid same integer value in profile
            if self.extremaSearch == True:
                if self.extremaType == 'max':
                    extrema_indices = argrelmax(profile_with_noise,
                                                order=self.extremaWinSize)[0]
                else:
                    extrema_indices = argrelmin(profile_with_noise,
                                                order=self.extremaWinSize)[0]
                #print_with_timestamp('before filtered by threshold: %s' %str(extrema_indices))
                extremas = profile[extrema_indices]
                filtered_extrema_indices = extrema_indices[
                    extremas > self.extremaThreshold * profile.mean()]
                filtered_extremas = profile[filtered_extrema_indices]
                #print_with_timestamp('after filtered by threshold: %s' %(filtered_extrema_indices))
                self.thresholdItem.setData(
                    np.ones_like(profile) * profile.mean() *
                    self.extremaThreshold)
            else:
                self.thresholdItem.clear()

    def calcDispData(self):
        if self.imageData is None:
            return None
        elif len(self.imageShape) == 3:
            _x, _y, _z = self.imageShape
            if self.axis == 'x':
                if 0 <= self.frameIndex < _x:
                    dispData = self.imageData[self.frameIndex, :, :]
                else:
                    #print_with_timestamp("ERROR! Index out of range. %s axis frame %d" %(self.axis, self.frameIndex))
                    QtGui.QMessageBox.question(
                        self, 'Error',
                        "ERROR! Index out of range. %s axis frame %d" %
                        (self.axis, self.frameIndex), QtGui.QMessageBox.Ok)
                    return None
            elif self.axis == 'y':
                if 0 <= self.frameIndex < _y:
                    dispData = self.imageData[:, self.frameIndex, :]
                else:
                    #print_with_timestamp("ERROR! Index out of range. %s axis frame %d" %(self.axis, self.frameIndex))
                    QtGui.QMessageBox.question(
                        self, 'Error',
                        "ERROR! Index out of range. %s axis frame %d" %
                        (self.axis, self.frameIndex), QtGui.QMessageBox.Ok)
                    return None
            else:
                if 0 <= self.frameIndex < _z:
                    dispData = self.imageData[:, :, self.frameIndex]
                else:
                    #print_with_timestamp("ERROR! Index out of range. %s axis frame %d" %(self.axis, self.frameIndex))
                    QtGui.QMessageBox.question(
                        self, 'Error',
                        "ERROR! Index out of range. %s axis frame %d" %
                        (self.axis, self.frameIndex), QtGui.QMessageBox.Ok)
                    return None
        elif len(self.imageShape) == 2:
            dispData = self.imageData
        if isinstance(dispData, np.ndarray):
            dispData = dispData.copy()
        else:
            dispData = np.asarray(dispData).copy()
        if self.imageLog:
            dispData[dispData < 1.] = 1.
            dispData = np.log(dispData)
        if self.FFT:
            dispData = np.abs(np.fft.fft2(dispData))
        if self.FFTSHIFT:
            dispData = np.fft.fftshift(dispData)
        return dispData

    def closeEvent(self, event):
        global data_viewer_window
        reply = QtGui.QMessageBox.question(self, 'Message',
                                           "Are you sure to quit?",
                                           QtGui.QMessageBox.Yes,
                                           QtGui.QMessageBox.No)
        if reply == QtGui.QMessageBox.Yes:
            #print_with_timestamp('Bye-Bye.')
            #pg.exit()
            if self.h5obj is not None:
                self.h5obj.close()
            del data_viewer_window
            data_viewer_window = None
        else:
            event.ignore()

    def changeDatasetSlot(self, item, column):
        if isinstance(item, DatasetItem):
            datasetItem = item
            fileItem = datasetItem.parent()
        else:
            assert isinstance(item, FileItem)
            fileItem = item
            datasetItem = fileItem.child(0)
        self.loadData(fileItem.filepath,
                      datasetItem.datasetName)  # maybe 1d, 2d or 3d dataset
        self.maybeChangeCurve(name=datasetItem.datasetName)
        self.maybeChangeDisp()
        self.maybePlotProfile()

    def maybeChangeCurve(self, name=None):
        if not self.showCurvePlot or self.curve is None:
            return None
        self.curveItem.setData(self.curve)
        if name is not None:
            self.ui.curveWidget.getPlotItem().setTitle('Curve Plot -- %s' %
                                                       name)

    def maybeChangeDisp(self):
        if not self.showImage or self.imageData is None:
            return None
        dispData = self.calcDispData()
        if dispData is None:
            return None
        self.dispShape = dispData.shape
        if self.maskFlag:
            if self.mask is None:
                self.mask = np.ones(self.dispShape)
            assert self.mask.shape == self.dispShape
            dispData *= self.mask
        if self.binaryFlag:
            dispData[dispData < self.dispThreshold] = 0.
            dispData[dispData >= self.dispThreshold] = 1.
        self.dispData = dispData
        # set dispData to distItem. Note: transpose the dispData to show image with same manner in matplotlib
        self.ui.imageView.setImage(
            self.dispData.T,
            autoRange=self.imageAutoRange,
            autoLevels=self.imageAutoLevels,
            autoHistogramRange=self.imageAutoHistogramRange)
        if self.showRings:
            if len(self.ringRadiis) > 0:
                _cx = np.ones_like(self.ringRadiis) * self.center[0]
                _cy = np.ones_like(self.ringRadiis) * self.center[1]
                self.ringItem.setData(_cx,
                                      _cy,
                                      size=self.ringRadiis * 2.,
                                      symbol='o',
                                      brush=(255, 255, 255, 0),
                                      pen='r',
                                      pxMode=False)
        self.centerMarkItem.setData([self.center[0]], [self.center[1]],
                                    size=10,
                                    symbol='+',
                                    brush=(255, 255, 255, 0),
                                    pen='r',
                                    pxMode=False)
        Friedel_score = calc_Friedel_score(self.dispData,
                                           self.center,
                                           mask=self.mask,
                                           mode='relative')
        self.params.param('Data Info',
                          'Image Friedel Score').setValue(Friedel_score)

    def setLineAngleSlot(self, _, lineAngle):
        #print_with_timestamp('set line angle: %s' %str(lineAngle))
        self.lineAngle = lineAngle
        self.maybePlotProfile()

    def setLineWidthSlot(self, _, lineWidth):
        #print_with_timestamp('set line width: %s' %str(lineWidth))
        self.lineWidth = lineWidth
        self.maybePlotProfile()

    def applyImageLogSlot(self, _, imageLog):
        #print_with_timestamp('set image log: %s' %str(imageLog))
        self.imageLog = imageLog
        self.maybeChangeDisp()
        self.maybePlotProfile()

    def applyFFTSlot(self, _, FFT):
        #print_with_timestamp('set image fft: %s' %str(FFT))
        self.FFT = FFT
        self.maybeChangeDisp()
        self.maybePlotProfile()

    def applyFFTSHIFTSlot(self, _, FFTSHIFT):
        #print_with_timestamp('set image fft-shift: %s' %str(FFTSHIFT))
        self.FFTSHIFT = FFTSHIFT
        self.maybeChangeDisp()
        self.maybePlotProfile()

    def setExtremaSearchSlot(self, _, extremaSearch):
        #print_with_timestamp('set extrema search: %s' %str(extremaSearch))
        self.extremaSearch = extremaSearch
        self.maybePlotProfile()

    def setExtremaWinSizeSlot(self, _, extremaWinSize):
        #print_with_timestamp('set extrema window size: %s' %str(extremaWinSize))
        self.extremaWinSize = extremaWinSize
        self.maybePlotProfile()

    def setExtremaTypeSlot(self, _, extremaType):
        #print_with_timestamp('set extrema type: %s' %str(extremaType))
        self.extremaType = extremaType
        self.maybePlotProfile()

    def setExtremaThresholdSlot(self, _, extremaThreshold):
        #print_with_timestamp('set extrema threshold: %s' %str(extremaThreshold))
        self.extremaThreshold = extremaThreshold
        self.maybePlotProfile()

    def axisChangedSlot(self, _, axis):
        #print_with_timestamp('axis changed.')
        self.axis = axis
        self.center = self.calcCenter()
        self.setCenterInfo()
        self.maybeChangeDisp()
        self.maybePlotProfile()

    def frameIndexChangedSlot(self, _, frameIndex):
        #print_with_timestamp('frame index changed')
        self.frameIndex = frameIndex
        self.maybeChangeDisp()
        self.maybePlotProfile()

    def showProfileSlot(self, _, showProfile):
        #print_with_timestamp('show or hide radial profile changed')
        if showProfile:
            #print_with_timestamp('show profile')
            self.ui.profileWidget.show()
        else:
            self.ui.profileWidget.hide()
        self.maybePlotProfile()

    def centerXChangedSlot(self, _, centerX):
        #print_with_timestamp('center X changed')
        self.center[0] = centerX
        self.maybeChangeDisp()
        self.maybePlotProfile()

    def centerYChangedSlot(self, _, centerY):
        #print_with_timestamp('center Y changed')
        self.center[1] = centerY
        self.maybeChangeDisp()
        self.maybePlotProfile()

    def showFileMenuSlot(self, position):
        fileMenu = QtGui.QMenu()
        item = self.ui.fileList.currentItem()
        if isinstance(item, DatasetItem):
            setAsMask = fileMenu.addAction("Set As Mask")
            action = fileMenu.exec_(self.ui.fileList.mapToGlobal(position))
            if action == setAsMask:
                filepath = item.parent().filepath
                mask = load_data(filepath, item.datasetName)
                if len(mask.shape) != 2:
                    raise ValueError(
                        '%s:%s can not be used as mask. Mask data must be 2d.'
                        % (filepath, item.datasetName))
                self.mask = np.asarray(mask)
                self.params.param('Data Info', 'Mask').setValue(
                    "%s::%s" % (os.path.basename(filepath), item.datasetName))
        elif isinstance(item, FileItem):
            deleteAction = fileMenu.addAction("Delete")
            action = fileMenu.exec_(self.ui.fileList.mapToGlobal(position))
            if action == deleteAction:
                #print('deleting selected file')
                for item in self.ui.fileList.selectedItems():
                    self.ui.fileList.takeTopLevelItem(
                        self.ui.fileList.indexOfTopLevelItem(item))

    def applyMaskSlot(self, _, mask):
        #print_with_timestamp('turn on mask: %s' % str(mask))
        self.maskFlag = mask
        self.maybeChangeDisp()
        self.maybePlotProfile()

    def imageAutoRangeSlot(self, _, imageAutoRange):
        #print_with_timestamp('set image autorange: %s' % str(imageAutoRange))
        self.imageAutoRange = imageAutoRange
        self.maybeChangeDisp()

    def imageAutoLevelsSlot(self, _, imageAutoLevels):
        #print_with_timestamp('set image autolevels: %s' % str(imageAutoLevels))
        self.imageAutoLevels = imageAutoLevels
        self.maybeChangeDisp()

    def imageAutoHistogramRangeSlot(self, _, imageAutoHistogramRange):
        #print_with_timestamp('set image autohistogram: %s' % str(imageAutoHistogramRange))
        self.imageAutoHistogramRange = imageAutoHistogramRange
        self.maybeChangeDisp()

    def curvePlotAutoRangeSlot(self, _, plotAutoRange):
        #print_with_timestamp('set curve plot autorange: %s' % str(plotAutoRange))
        self.curvePlotAutoRange = plotAutoRange

    def curvePlotLogModeSlot(self, _, log):
        #print_with_timestamp('set curve plot log mode: %s' % str(log))
        if log:
            self.ui.curveWidget.setLogMode(y=True)
        else:
            self.ui.curveWidget.setLogMode(y=False)

    def profilePlotAutoRangeSlot(self, _, plotAutoRange):
        #print_with_timestamp('set profile plot autorange: %s' % str(plotAutoRange))
        if plotAutoRange:
            self.ui.profileWidget.getViewBox().enableAutoRange()
        else:
            self.ui.profileWidget.getViewBox().disableAutoRange()
        self.maybePlotProfile()

    def profilePlotLogModeSlot(self, _, log):
        #print_with_timestamp('set profile plot log mode: %s' % str(log))
        self.maybePlotProfile()
        if log:
            self.ui.profileWidget.setLogMode(y=True)
        else:
            self.ui.profileWidget.setLogMode(y=False)

    def smallDataPlotAutoRangeSlot(self, _, plotAutoRange):
        #print_with_timestamp('set small data plot autorange: %s' % str(plotAutoRange))
        if plotAutoRange:
            self.ui.smallDataWidget.getViewBox().enableAutoRange()
        else:
            self.ui.smallDataWidget.getViewBox().disableAutoRange()
        self.maybePlotProfile()

    def smallDataPlotLogModeSlot(self, _, log):
        #print_with_timestamp('set small data log mode: %s' % str(log))
        if log:
            self.ui.smallDataWidget.setLogMode(y=True)
        else:
            self.ui.smallDataWidget.setLogMode(y=False)

    def showRingsSlot(self, _, showRings):
        #print_with_timestamp('show rings: %s' %showRings)
        self.showRings = showRings
        if not self.showRings:
            self.ringItem.clear()
        self.maybeChangeDisp()

    def ringRadiiSlot(self, _, ringRadiis):
        #print_with_timestamp('set ring radiis: %s' %str(ringRadiis))
        ringRadiisStrList = ringRadiis.split()
        ringRadiis = []
        for ringRadiisStr in ringRadiisStrList:
            ringRadiis.append(float(ringRadiisStr))
        self.ringRadiis = np.asarray(ringRadiis)
        self.maybeChangeDisp()

    def setProfileTypeSlot(self, _, profileType):
        #print_with_timestamp('set profile type: %s' %str(profileType))
        self.profileType = profileType
        self.maybePlotProfile()

    def setProfileModeSlot(self, _, profileMode):
        #print_with_timestamp('set profile mode: %s' %str(profileMode))
        self.profileMode = profileMode
        self.maybePlotProfile()

    def binaryImageSlot(self, _, binaryImage):
        #print_with_timestamp('apply Binaryzation: %s' %str(binaryImage))
        self.binaryFlag = binaryImage
        self.maybeChangeDisp()
        self.maybePlotProfile()

    def setDispThresholdSlot(self, _, threshold):
        #print_with_timestamp('set disp threshold: %.1f' %threshold)
        self.dispThreshold = threshold
        self.maybeChangeDisp()
        self.maybePlotProfile()

    def setSmoothSlot(self, _, smooth):
        #print_with_timestamp('set smooth: %s' %str(smooth))
        self.smoothFlag = smooth
        self.maybeChangeDisp()
        self.maybePlotProfile()

    def setWinSizeSlot(self, _, winSize):
        winSize = winSize
        if winSize % 2 == 0:
            winSize += 1  # winSize must be odd
        #print_with_timestamp('set smooth winsize: %d' %winSize)
        self.smoothWinSize = winSize
        self.maybeChangeDisp()
        self.maybePlotProfile()

    def setPolyOrderSlot(self, _, polyOrder):
        #print_with_timestamp('set poly order: %d' %polyOrder)
        self.polyOrder = polyOrder
        self.maybeChangeDisp()
        self.maybePlotProfile()

    def setAngularRminSlot(self, _, Rmin):
        #print_with_timestamp('set angular Rmin to %.1f' %Rmin)
        self.angularRmin = Rmin
        self.maybeChangeDisp()
        self.maybePlotProfile()

    def setAngularRmaxSlot(self, _, Rmax):
        #print_with_timestamp('set angular Rmax to %.1f' %Rmax)
        self.angularRmax = Rmax
        self.maybeChangeDisp()
        self.maybePlotProfile()

    def mouseMoved(self, pos):
        if self.dispShape is None:
            return None
        mouse_point = self.ui.imageView.view.mapToView(pos)
        x, y = int(mouse_point.x()), int(mouse_point.y())
        filename = os.path.basename(str(self.filepath))
        if 0 <= x < self.dispData.shape[1] and 0 <= y < self.dispData.shape[0]:
            self.ui.statusbar.showMessage(
                "%s x:%d y:%d I:%.2E" % (filename, x, y, self.dispData[y, x]),
                5000)
        else:
            pass
Exemple #18
0
class arrayNormaliserNode(Node):
    nodeName = 'arrayNormaliser'
    sigUpdatePlot = QtCore.Signal(object)

    def __init__(self, name):
        Node.__init__(self,
                      name,
                      terminals={
                          'dataIn': {
                              'io': 'in'
                          },
                          'dataOut': {
                              'io': 'out'
                          },
                          'plotItems': {
                              'io': 'out'
                          }
                      })
        color = (220, 220, 25, 255)
        self.plotDataItem = PlotDataItem(stepMode=True,
                                         fillLevel=0,
                                         pen={
                                             'color': color,
                                             'width': 2
                                         })
        self.plotRegion = LinearRegionItem([0, 1], movable=True)
        self.plotRegion.sigRegionChangeFinished.connect(self.regionChanged)
        self.sigUpdatePlot.connect(self.updatePlot)

    def updatePlot(self, xy):
        self.plotDataItem.setData(*xy)

    def regionChanged(self):
        self.regionLimits = self.plotRegion.getRegion()
        self.update()

    def process(self, dataIn, display=True):
        if len(dataIn.shape) != 1:
            data = dataIn[:, -1]
        else:
            data = dataIn

        self.extremeLimits = np.nanmin(data), np.nanmax(data)

        # if not self.plotWidget.closed: # the plotWidget attribute is never removed but it is invalidated when the widget is closed
        y, x = np.histogram(data, bins=100)
        # self.plotWidget.clear()
        self.sigUpdatePlot.emit((x, y))
        # self.plotWidget.addItem(self.plotRegion)

        if hasattr(self, 'regionLimits'):
            mi, ma = self.regionLimits
        else:
            mi, ma = self.extremeLimits

        # if hasattr(self, 'plotRegion'):
        #     self.plotRegion.setRegion((mi, ma))

        dataOut = (data - mi) / (ma - mi)

        # print (dataOut)

        return {
            'dataOut': dataOut,
            'plotItems': [self.plotRegion, self.plotDataItem]
        }
Exemple #19
0
class ScanPlot(PlotItem):
    # scan is the scan json object
    def __init__(self,parent,scan):
        # name of independent variable
        input = scan[INPUT]
        x_label = input.get(NAME,'input')
        x_units = input.get(UNITS,'arb')
        # name of dependent variable
        output = scan[OUTPUT]
        if type(output) is dict:
            y_label = output.get(NAME,'output')
            y_units = output.get(UNITS,'arb')       
        else:
            y_label = 'output'
            y_units = 'arb'
        # scan title
        title = scan.get(
            NAME,
            '%s vs. %s' % (y_label,x_label)
        )
        labels = {
            'bottom':'%s (%s)' % (x_label,x_units),
            'left':'%s (%s)' % (y_label,y_units)
        }
        # initialize pyqtgraph plot item with title and axis labels
        PlotItem.__init__(
            self,
            title = title,
            labels = labels
        )
        # are we setting input to optimal value of scan result?
        self.optimizing = scan.get(OPTIMIZE,False)
        # if we have multiple outputs and are optimizing, which output to optimize?
        self.checking_optimize = scan.get(CHECK_OPTIMIZE,False)
        self.click_deferred = None
        self.optimize_axis = scan.get(OPTIMIZE_AXIS,0)
        # are we saving scan data to datavault?
        self.saving = scan.get(SAVE,False)
        # are we returning to input's original position after scan?
        self.returning = scan.get(RETURN,False)
        self.scan = scan
        self.__parent = parent

    def mouseClickEvent(self,event):
        if self.click_deferred is not None:
            self.click_deferred.callback(self.getViewBox().mapSceneToView(event.scenePos()).x())
            event.accept()
            self.click_deferred = None
        else:
            PlotItem.mouseClickEvent(self,event)

    # performs set up for scan
    def start(self):
        return self.set_scan_items()
    
    @inlineCallbacks
    def set_scan_items(self):
        scan = self.scan
        outputs = scan[OUTPUT]
        if type(outputs) is dict:
            outputs = [outputs]
        if self.is_saving():
            # get labrad connection
            cxn = yield connectAsync()
            # get handle to data vault
            data_vault = cxn.data_vault
            # list of folders (trunk to leaf) for dataset directory
            save_dir = scan.get(SAVE_DIR,[])
            # go to scans dir in dv
            yield data_vault.cd(data_vault_dir+save_dir,True)
            independent_variables = [
                '%s [%s]' % (
                    scan[INPUT].get(NAME,'input'),
                    scan[INPUT].get(UNITS,'arb')
                )
            ]
            dependent_variables = [
                '%s [%s]' % (
                    output.get(NAME,'output'),
                    output.get(UNITS,'arb')
                ) for output in outputs
            ]
            save_name = scan.get(SAVE_NAME,None)
            default_name = text=scan.get(NAME,'')
            if save_name is None:
                save_name, result = QtGui.QInputDialog.getText(
                    self.__parent,
                    'enter dataset name',
                    'enter title for data vault dataset',
                    text = default_name
                )
                if not result:
                    save_name = default_name
            # create new dataset                
            yield data_vault.new(
                str(save_name),
                independent_variables,
                dependent_variables
            )
            # make note of dataset creation
            yield data_vault.add_parameter(
                'time',
                get_datetime()
            )
            self.data_vault = data_vault
        # instantiate scan input object
        self.input = INPUTS[
            scan[INPUT][CLASS]
        ](
            **mangle(scan[INPUT].get(ARGS,{}))
        )
        # note initial position if we are planning to return to it after scan
        if self.is_returning():
            self._return_input = yield self.input._get_input()
        # instantiate scan output object
        self.outputs = [
            OUTPUTS[
                output[CLASS]
            ](
                **mangle(output.get(ARGS,{}))
            ) for output in outputs
        ]
        # intialize x and y values
        self.x_data = []
        self.y_datas = []
        # clear any stuff that happens to be in plot (TODO: do we ever scan the same object twice?)
        for item in self.allChildItems():
            self.removeItem(item)            
        # if optimizing or have multiple sources, \
        # add legend to distinguish different sources
        if self.is_optimizing() or len(outputs) > 1:
            self.addLegend()
        if self.is_optimizing():
            # initialize fit curve
            self.fit_curve = PlotDataItem(
                name='fit (%s)' % outputs[
                    self.optimize_axis
                ].get(
                    NAME,
                    'output %d' % (self.optimize_axis+1)
                ),
                pen={'color':'BBB','width':2}
            )
        # initialize data curves
        self.curves = [
            self.plot(
                name=output.get(NAME,'output %d' % (index + 1)),
                pen=None,
                symbolSize=5,
                symbolPen=None,
                symbolBrush=('F55','5F5','55F','FF5','5FF','F5F')[index % 6]
            ) for index, output in enumerate(outputs)
        ]

    def show_fit(self):
        self.addItem(self.fit_curve)

    # put input back to initial position
    def return_input(self):
        return self.input.set_input(self._return_input)

    # called to increment scan by one step
    @inlineCallbacks
    def step(self):
        # ask input for next x value
        x = yield self.input.get_input()
        # check if scan input is done and err if so
        if x is None:
            returnValue(False)
        y = []
        output_deferreds = [
            output.get_output() for output in self.outputs
        ]
        for deferred in output_deferreds:
            y_value = yield deferred
            y.append(y_value)
        # add new values to data arrays
        self.x_data.append(x)
        self.y_datas.append(y)        
        # update dataset with new datapoint if saving
        if self.is_saving():
            yield self.data_vault.add([x]+y)
        for curve, y_data in zip(self.curves,zip(*self.y_datas)):
            # update data curve
            curve.setData(self.x_data,y_data)
        # indicate successful step
        returnValue(True)

    def is_optimizing(self):
        return self.optimizing

    def is_saving(self):
        return self.saving

    def is_returning(self):
        return self.returning    

    # fit data to gaussian to find input value that optimizes output and set input to that value
    @inlineCallbacks
    def optimize_input(self):        
        x_arr = np.array(self.x_data)
        y_arr = np.array(zip(*self.y_datas)[self.optimize_axis])
        params = self.estimate_gaussian_parameters(
            x_arr,y_arr
        )
        try:
            params, _ = curve_fit(
                self.gaussian,
                x_arr,
                y_arr,
                params
            )
        except RuntimeError:
            pass
        x_fit = np.linspace(x_arr.min(),x_arr.max(),FIT_POINTS)            
        self.fit_curve.setData(
            x_fit,
            self.gaussian(x_fit,*params)
        )
        if self.fit_curve not in self.listDataItems():
            self.show_fit()
            input = int(np.round(params[0]))
            if self.checking_optimize:                
                result = QtGui.QMessageBox.question(
                    self.__parent,
                    'check optimize',
                    'is optimize result of %d ok?' % input,
                    QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
                )
                if result == QtGui.QMessageBox.No:
                    message_box = QtGui.QMessageBox(self.__parent)
                    click_button = message_box.addButton('click',QtGui.QMessageBox.AcceptRole)
                    enter_button = message_box.addButton('enter',QtGui.QMessageBox.RejectRole)
                    message_box.setText('specify how to enter location')
                    message_box.setInformativeText('click location on graph or enter in value?')
                    message_box.exec_()
                    if message_box.clickedButton() == click_button:
                        self.click_deferred = Deferred()
                        input = yield self.click_deferred
                        input = int(np.round(input))
                    else:
                        new_input, result = QtGui.QInputDialog.getInt(
                            self.__parent,
                            'enter location',
                            'location',
                            input
                        )
            yield self.input.set_input(input)

    @staticmethod
    def gaussian(x,mean,std,amplitude,offset):
        return amplitude * np.exp(- 1. / 2. * np.square( ( x - mean ) / std) ) + offset

    @staticmethod
    def estimate_gaussian_parameters(x,y):
        min = y.min()
        max = y.max()
        offset = min
        amplitude = max - min
        mean_index = y.argmax()
        mean = x[mean_index]
        threshold = min + (max - min) / 2
        right_estimate = None
        index = mean_index
        while True:
            if index == len(y):
                break
            if y[index] < threshold:
                right_estimate = abs(x[index] - mean) / 2.355 * 2
            index += 1
        left_estimate = None
        index = mean_index
        while True:
            if index < 0:
                break
            if y[index] < threshold:
                left_estimate = abs(x[index] - mean) / 2.355 * 2
            index -= 1
        if right_estimate is None and left_estimate is None:
            std = abs(x[len(y)/2]-x[0])
        elif right_estimate is None:
            std = left_estimate
        elif left_estimate is None:
            std = right_estimate
        else:
            std = ( left_estimate + right_estimate ) / 2.
        return (mean,std,amplitude,offset)