class waterfallWidget(QtGui.QWidget): """Widget for plotting a waterfall plot. """ def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.layout = QtGui.QHBoxLayout(self) self.viewport = GraphicsLayoutWidget() self.view = self.viewport.addViewBox(colspan=3, rowspan=3, lockAspect=False, enableMenu=True) self.img = pg.ImageItem() self.view.addItem(self.img) # Get the colormap colormap = cm.get_cmap("jet") colormap._init() lut = (colormap._lut * 255).view( np.ndarray ) # Convert matplotlib colormap from 0-1 to 0 -255 for Qt # Apply the colormap self.img.setLookupTable(lut) self.h = self.viewport.addViewBox(enableMenu=False, colspan=3) self.hist = pg.HistogramLUTItem(image=self.img, fillHistogram=False) self.hist.setImageItem(self.img) self.h.addItem(self.hist) self.imv = pg.ImageView(view=self.view, imageItem=self.img) self.layout.addWidget(self.imv) self.setLayout(self.layout)
class waterfallWidget(QtGui.QWidget): """Widget for plotting a waterfall plot. """ def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.layout = QtGui.QHBoxLayout(self) self.viewport = GraphicsLayoutWidget() self.view = self.viewport.addViewBox(colspan=3, rowspan=3, lockAspect=False, enableMenu=True) self.img = pg.ImageItem() self.view.addItem(self.img) self.h = self.viewport.addViewBox(enableMenu=False, colspan=3) self.hist = pg.HistogramLUTItem(image=self.img, fillHistogram=False) self.hist.setImageItem(self.img) self.h.addItem(self.hist) self.imv = pg.ImageView(view=self.view, imageItem=self.img) self.layout.addWidget(self.imv) self.setLayout(self.layout)
class viewerWidget(QtGui.QWidget): """Widget for holding the GUI elements of the viewer. """ def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.layout = QtGui.QVBoxLayout(self) self.viewport = GraphicsLayoutWidget() self.view = self.viewport.addViewBox(enableMenu=True) self.img = pg.ImageItem() self.view.addItem(self.img) self.buttons = QtGui.QHBoxLayout() self.startButton = QtGui.QPushButton('Start') self.stopButton = QtGui.QPushButton('Stop') self.buttons.addWidget(self.startButton) self.buttons.addWidget(self.stopButton) self.setLayout(self.layout) self.layout.addWidget(self.viewport) self.layout.addLayout(self.buttons)
def create_sliders(self, width, height): """Creates the sliders, label for the slider, text with value of slider and the percentage sign.""" font = QtGui.QFont() x_pos = [int(width * x) for x in [0.05, 0.3, 0.55]] y_pos = [int(height * y) for y in [0.24, 0.33, 0.80]] wid = [int(width * x) for x in [0.15, 0.05, 0.04]] hei = [int(height * y) for y in [0.09, 0.44, 0.09]] i = 0 for mode in self.mode: font.setPixelSize(int(height * 0.07)) exec(f"self.{mode}_power_title = QtWidgets.QLabel(self)") exec(f"self.{mode}_power_title.setFont(font)") exec( f"self.{mode}_power_title.setAlignment(QtCore.Qt.AlignCenter)") exec( f"self.{mode}_power_title.setText('{self.mode_nl[self.mode.index(mode)]}')" ) exec( f"self.{mode}_power_title.setGeometry(QtCore.QRect({x_pos[i]}, {y_pos[0]}," f"{wid[0]}, {hei[0]}))") font.setPixelSize(int(height * 0.03)) exec(f"self.{mode}_power_value = QtWidgets.QLCDNumber(self)") exec( f"self.{mode}_power_value.setGeometry({x_pos[i]}, {y_pos[2]}, {wid[0]}, {hei[2]})" ) exec(f"self.{mode}_power_value.setFont(font)") exec(f"self.{mode}_power_value.setDigitCount(3)") exec(f"self.{mode}_power_value.display(0)") exec(f"self.{mode}_power_slider = QtWidgets.QSlider(self)") exec(f"self.{mode}_power_slider.setMaximum(100)") exec( f"self.{mode}_power_slider.setOrientation(QtCore.Qt.Vertical)") exec( f"self.{mode}_power_slider.setGeometry({x_pos[i]}, {y_pos[1]}," f"{wid[0]}, {hei[1]})") exec( f"self.{mode}_power_slider.valueChanged.connect(self.{mode}_power_value.display)" ) exec( f"self.{mode}_power_slider.sliderReleased.connect(self.update_data_manager)" ) font.setPixelSize(int(height * 0.07)) exec(f"self.{mode}_power_symb = QtWidgets.QLabel(self)") exec( f"self.{mode}_power_symb.setGeometry({x_pos[i] + int(width * 0.125)}, {y_pos[2]}," f"{wid[2]}, {hei[2]})") exec(f"self.{mode}_power_symb.setText('%')") exec(f"self.{mode}_power_symb.setFont(font)") i += 1 font.setPixelSize(int(height * 0.07)) self.opslag_title = QtWidgets.QLabel(self) self.opslag_title.setFont(font) self.opslag_title.setAlignment(QtCore.Qt.AlignCenter) self.opslag_title.setText('Opslaan') fac_scale = 0.2 self.opslag_title.setGeometry( QtCore.QRect(int(width * 0.8 - hei[0] * fac_scale / 2), y_pos[0], wid[0] * (1 + fac_scale), hei[0])) self.opslag_title.setStyleSheet( "QLabel { color : rgba(21, 255, 0, 255); }") self.bijspring_title = QtWidgets.QLabel(self) self.bijspring_title.setFont(font) self.bijspring_title.setAlignment(QtCore.Qt.AlignCenter) self.bijspring_title.setText('Verbruiken') self.bijspring_title.setGeometry( QtCore.QRect(int(width * 0.8 - hei[0] * fac_scale / 2), y_pos[2], wid[0] * (1 + fac_scale), hei[0])) self.bijspring_title.setStyleSheet("QLabel {color : red; }") font.setPixelSize(int(height * 0.03)) overhead3 = GraphicsLayoutWidget(self) overhead3.setGeometry( QtCore.QRect(int(width * 0.85), int(y_pos[1] + +0.49 * hei[1]), int(width * 0.05), int(0.02 * hei[1]))) overhead3.setBackground('w') self.h2_slide = QtWidgets.QSlider(self) self.h2_slide.setRange(-100, 100) self.h2_slide.setOrientation(QtCore.Qt.Vertical) self.h2_slide.setGeometry(int(width * 0.8), y_pos[1], wid[0], hei[1]) self.h2_slide.setStyleSheet( """QSlider {background-color: rgba(255, 255, 255, 0)} QSlider::handle:vertical { background: black; margin: 0 -80px; border: 1px solid; height: 10px; } QSlider::groove:vertical { background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 green, stop:1 red); width: 10px; margin: 0 -5px; } """) overhead = GraphicsLayoutWidget(self) overhead.setGeometry( QtCore.QRect(int(width * 0.8), y_pos[1], wid[0], hei[1])) overhead.setBackground(None) overhead2 = overhead.addViewBox(enableMouse=False) overhead2.setBackgroundColor(None) overhead2.setAutoPan(False, False) self.data_manager.h2_slide = self.h2_slide
class monitorMainWidget(QtGui.QWidget): """Widget for holding the images generated by the camera. """ def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) # General layout of the widget to hold an image and a histogram self.layout = QtGui.QHBoxLayout(self) # Settings for the image self.viewport = GraphicsLayoutWidget() self.view = self.viewport.addViewBox(lockAspect=False, enableMenu=True) self.autoScale = QtGui.QAction("Auto Range", self.view.menu) self.autoScale.triggered.connect(self.doAutoScale) self.view.menu.addAction(self.autoScale) self.img = pg.ImageItem() self.view.addItem(self.img) self.imv = pg.ImageView(view=self.view, imageItem=self.img) # Add everything to the widget self.layout.addWidget(self.imv) self.setLayout(self.layout) self.showCrosshair = False self.showCrossCut = False def setup_overlay(self): # useful if one want to plot the recovered trajectory in the camera viewport self.img2 = pg.ImageItem() # To overlay another image if needed. self.img2.setOpacity(0.4) self.img2.setZValue(1000) self.view.addItem(self.img2) def setup_cross_hair(self, max_size): """Sets up a cross hair.""" self.crosshair = [] self.crosshair.append( pg.InfiniteLine(angle=0, movable=False, pen={ 'color': 124, 'width': 4 })) self.crosshair.append( pg.InfiniteLine(angle=90, movable=False, pen={ 'color': 124, 'width': 4 })) self.crosshair[0].setBounds((1, max_size[1] - 1)) self.crosshair[1].setBounds((1, max_size[0] - 1)) def setup_cross_cut(self, max_size): """Set ups the horizontal line for the cross cut.""" self.crossCut = pg.InfiniteLine(angle=0, movable=False, pen={ 'color': 'g', 'width': 2 }) self.crossCut.setBounds((1, max_size)) def setup_roi_lines(self, max_size): """Sets up the ROI lines surrounding the image. :param list max_size: List containing the maximum size of the image to avoid ROIs bigger than the CCD.""" self.hline1 = pg.InfiniteLine(angle=0, movable=True, hoverPen={ 'color': "FF0", 'width': 4 }) self.hline2 = pg.InfiniteLine(angle=0, movable=True, hoverPen={ 'color': "FF0", 'width': 4 }) self.vline1 = pg.InfiniteLine(angle=90, movable=True, hoverPen={ 'color': "FF0", 'width': 4 }) self.vline2 = pg.InfiniteLine(angle=90, movable=True, hoverPen={ 'color': "FF0", 'width': 4 }) self.vline2.setValue(max_size[0] - 1) self.hline2.setValue(max_size[1] - 1) self.hline1.setBounds((1, max_size[1] - 1)) self.hline2.setBounds((1, max_size[1] - 1)) self.vline1.setBounds((1, max_size[0] - 1)) self.vline2.setBounds((1, max_size[0] - 1)) self.view.addItem(self.hline1) self.view.addItem(self.hline2) self.view.addItem(self.vline1) self.view.addItem(self.vline2) def setup_mouse_tracking(self): self.imv.setMouseTracking(True) self.imv.getImageItem().scene().sigMouseMoved.connect(self.mouseMoved) self.imv.getImageItem().scene().contextMenu = None def keyPressEvent(self, key): """Triggered when there is a key press with some modifier. Shift+C: Removes the cross hair from the screen Ctrl+C: Emits a specialTask signal Ctrl+V: Emits a stopSpecialTask signal These last two events have to be handeled in the mainWindow that implemented this widget.""" modifiers = QtGui.QApplication.keyboardModifiers() if modifiers == QtCore.Qt.ShiftModifier: if key.key() == 67: # For letter C of 'Clear if self.showCrosshair: for c in self.crosshair: self.view.removeItem(c) self.showCrosshair = False if self.showCrossCut: self.view.removeItem(self.crossCut) self.showCrossCut = False elif modifiers == QtCore.Qt.ControlModifier: if key.key() == 67: # For letter C of 'Clear self.emit(QtCore.SIGNAL('specialTask')) if key.key() == 86: # For letter V self.emit(QtCore.SIGNAL('stopSpecialTask')) def mouseMoved(self, arg): """Updates the position of the cross hair. The mouse has to be moved while pressing down the Ctrl button.""" # arg = evt.pos() modifiers = QtGui.QApplication.keyboardModifiers() if modifiers == QtCore.Qt.ControlModifier: if not self.showCrosshair: for c in self.crosshair: self.view.addItem(c) self.showCrosshair = True self.crosshair[1].setValue(int(self.img.mapFromScene(arg).x())) self.crosshair[0].setValue(int(self.img.mapFromScene(arg).y())) elif modifiers == QtCore.Qt.AltModifier: if not self.showCrossCut: self.view.addItem(self.crossCut) self.showCrossCut = True self.crossCut.setValue(int(self.img.mapFromScene(arg).y())) def doAutoScale(self): h, y = self.img.getHistogram() self.imv.setLevels(min(h), max(h)) # self.img.HistogramLUTItem.setLevels(min(h),max(h)) def drawTargetPointer(self, image, location): """gets an image and draws a square around the target location""" w = 5 x0 = np.int(location[0]) y0 = np.int(location[1]) newimage = image / 2 for x in range(w): newimage[x0 + x, y0 - w + x, 1] = 3000 newimage[x0 - x, y0 - w + x, 2] = 6000 newimage[x0 + x, y0 + w - x, 2] = 6000 newimage[x0 - x, y0 + w - x, 1] = 3000 return newimage
def initGUI(self): self.plot = PlotWidget() self.sensorplot = PlotWidget() self.schemeplot = PlotWidget() self.scheme_plot = UTILS_QT.myplot(self.schemeplot,xlabel = ['time', 's'], ylabel =['',''],logmode=False) self.psp = UTILS_QT.pulses_scheme_plot(self.scheme_plot) date_axis = TimeAxisItem(orientation='bottom') #date_axis = pg.graphicsItems.DateAxisItem.DateAxisItem(orientation = 'bottom') self.sensorplot = PlotWidget(axisItems = {'bottom': date_axis}) win = GraphicsLayoutWidget() win2 = PlotWidget() self.view = win.addViewBox(border = 'w', invertY = True) self.view.setAspectLocked(True) self.img = ImageItem() self.plotaxes = win2.getPlotItem() #self.view.addItem(self.img) #self.view.addItem(self.plotaxes) #self.view. data = np.random.normal(size=(1, 600, 600), loc=1024, scale=64).astype(np.uint16) self.img.setImage(data[0]) self.plotaxes.getViewBox().addItem(self.img) # colormap pos = np.array([0., 1., 0.5, 0.25, 0.75]) #pos2 = np.array([1.0,0.75,0.5,0.25,0.]) pos2 = np.array([0.,0.25,0.5,0.75,1.0]) color2 = np.array([[255,242,15,255], [245,124,15,255],[170,69,16,255],[91,50,0,255],[0,0,0,255]],dtype=np.ubyte) color = np.array([[0,255,255,255], [255,255,0,255], [0,0,0,255], (0, 0, 255, 255), (255, 0, 0, 255)], dtype=np.ubyte) cmap = pg.ColorMap(pos2, color2) lut = cmap.getLookupTable(0.0, 1.0, 256) self.img.setLookupTable(lut) #self.img.setLevels([-50,1]) self.tw = QtGui.QTabWidget() self.tw.addTab(win2,'ESR data') self.tw.addTab(self.sensorplot,'B field') self.tw.addTab(self.schemeplot,'Scheme') layout = QtGui.QVBoxLayout() layout.addWidget(self.plot) #layout.addWidget(win2) layout.addWidget(self.tw) self.setLayout(layout) self.p1 = self.plot.getPlotItem() self.p2 = self.plot.getPlotItem() self.ps = self.sensorplot.getPlotItem() #self.p1.addLegend() self.p1data = self.p1.plot([0],pen = 'r') self.p2data = self.p1.plot([0],pen = 'g') self.psdata = self.ps.plot([],pen = 'w') self.ps.setLabel('left','Magnetic field', 'uT') self.vLine5 = pg.InfiniteLine(angle=90, movable=True) self.vLine6 = pg.InfiniteLine(angle=90, movable=True) self.plotaxes.addItem(self.vLine5, ignoreBounds=True) self.plotaxes.addItem(self.vLine6, ignoreBounds=True)
class PlotWindow(QWidget): img_dict = pyqtSignal(object) def __init__(self): super(PlotWindow, self).__init__() self.layout = QHBoxLayout(self) pg.setConfigOptions(imageAxisOrder='row-major') self.viewport = GraphicsLayoutWidget() self.video_view = self.viewport.addViewBox() self.video = pg.ImageItem() # self.video_view.clicked.connect(self.btn_state) self.video_view.addItem(self.video) self.video_view.setMouseEnabled(x=False, y=False)#make it can not move self.img_label = 'no image' self.viewport.setToolTip(str(self.img_label)) self.setLayout(self.layout) self.layout.addWidget(self.viewport) # self.push_btn = QPushButton("sent", self) # self.push_btn.clicked.connect(self.btn_state) # self.save_btn = QPushButton("save", self) # self.save_btn.clicked.connect(self.save_image) # self.horizontalLayout = QVBoxLayout() # self.horizontalLayout.addWidget(self.push_btn) # self.horizontalLayout.addWidget(self.save_btn) # self.horizontalLayout.addWidget(self.img_label) # self.layout.addLayout(self.horizontalLayout) screen = QtGui.QDesktopWidget().screenGeometry() # print(screen) self.setFixedSize(screen.width() * 15 / 100, screen.width() * (9/16)*(14 / 100)) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.btn_state() def mouseDoubleClickEvent(self, event): if event.button() == Qt.LeftButton: self.save_image() # def enterEvent(self, event): # pass # # def leaveEvent(self, event): # pass def btn_state(self): if self.video.image is None: print("have no image in window") # from MainWindow import TestMainWindow # TestMainWindow.path.setTitle(str('have no image in window')) return # img_analyse_setting.roi.setChecked(False) img_dict = {'img_data': np.array(self.video.image), 'img_name': self.img_label} settings.imgData["Img_data"] = img_dict['img_data'] self.img_dict.emit(img_dict) def save_image(self): # try: if self.video.image is None: print("have no image in window") return fpath = IOHelper.get_config_setting('DATA_PATH') fpath = Path(fpath) dir_path = fpath.joinpath(str(datetime.datetime.now())[2:].split('.')[0].replace(' ', '').replace(':', '_')) if settings.m_path != []: dir_path = settings.m_path # print("save images to {}".format(dir_path)) if not dir_path.exists(): dir_path.mkdir() img_data = np.array(self.video.image) # load image name by path img_name2 = (self.img_label)[0:20].replace(' ', '~').replace(':', '').replace('-', '') img_name = str(img_name2) img_data = img_data[::-1] # img_data = Image.fromarray(img_data) # img_data.save(r"{}\{}.png".format(dir_path, img_name)) import numpy numpy.savetxt(r"{}\{}.data".format(dir_path, img_name), img_data, fmt='%.2e', delimiter=' ', newline='\n', header='', footer='', comments=' ', encoding=None) print("save images to {}".format(dir_path)) # print("images have saved.") # except OSError: # print('Image cannot be saved.') #############################################################3 def img_plot(self, img_dict): # print(img_dict['img_data'].ndim) self.video.setImage(img_dict['img_data']) self.img_label = img_dict['img_name'] self.viewport.setToolTip(str(self.img_label)) def clear_win(self): self.video.clear() self.img_label = 'no image' self.viewport.setToolTip(str(self.img_label))
class ImageTester(QtWidgets.QWidget): """Graphical interface for auditing image comparison tests. """ def __init__(self): self.lastKey = None QtWidgets.QWidget.__init__(self) self.resize(1200, 800) #self.showFullScreen() self.layout = QtWidgets.QGridLayout() self.setLayout(self.layout) self.view = GraphicsLayoutWidget() self.layout.addWidget(self.view, 0, 0, 1, 2) self.label = QtWidgets.QLabel() self.layout.addWidget(self.label, 1, 0, 1, 2) self.label.setWordWrap(True) font = QtGui.QFont("monospace", 14, QtGui.QFont.Weight.Bold) self.label.setFont(font) self.passBtn = QtWidgets.QPushButton('Pass') self.failBtn = QtWidgets.QPushButton('Fail') self.layout.addWidget(self.passBtn, 2, 0) self.layout.addWidget(self.failBtn, 2, 1) self.passBtn.clicked.connect(self.passTest) self.failBtn.clicked.connect(self.failTest) self.views = (self.view.addViewBox(row=0, col=0), self.view.addViewBox(row=0, col=1), self.view.addViewBox(row=0, col=2)) labelText = ['test output', 'standard', 'diff'] for i, v in enumerate(self.views): v.setAspectLocked(1) v.invertY() v.image = ImageItem(axisOrder='row-major') v.image.setAutoDownsample(True) v.addItem(v.image) v.label = TextItem(labelText[i]) v.setBackgroundColor(0.5) self.views[1].setXLink(self.views[0]) self.views[1].setYLink(self.views[0]) self.views[2].setXLink(self.views[0]) self.views[2].setYLink(self.views[0]) def test(self, im1, im2, message): """Ask the user to decide whether an image test passes or fails. This method displays the test image, reference image, and the difference between the two. It then blocks until the user selects the test output by clicking a pass/fail button or typing p/f. If the user fails the test, then an exception is raised. """ self.show() if im2 is None: message += '\nImage1: %s %s Image2: [no standard]' % (im1.shape, im1.dtype) im2 = np.zeros((1, 1, 3), dtype=np.ubyte) else: message += '\nImage1: %s %s Image2: %s %s' % ( im1.shape, im1.dtype, im2.shape, im2.dtype) self.label.setText(message) self.views[0].image.setImage(im1) self.views[1].image.setImage(im2) diff = makeDiffImage(im1, im2) self.views[2].image.setImage(diff) self.views[0].autoRange() while True: QtWidgets.QApplication.processEvents() lastKey = self.lastKey self.lastKey = None if lastKey in ('f', 'esc') or not self.isVisible(): raise Exception("User rejected test result.") elif lastKey == 'p': break time.sleep(0.03) for v in self.views: v.image.setImage(np.zeros((1, 1, 3), dtype=np.ubyte)) def keyPressEvent(self, event): if event.key() == QtCore.Qt.Key.Key_Escape: self.lastKey = 'esc' else: self.lastKey = str(event.text()).lower() def passTest(self): self.lastKey = 'p' def failTest(self): self.lastKey = 'f'
class PlotWindow(QWidget): img_dict = pyqtSignal(object) myserial = 5 def __init__(self): super(PlotWindow, self).__init__() self.layout = QHBoxLayout(self) pg.setConfigOptions(imageAxisOrder='row-major') self.viewport = GraphicsLayoutWidget() self.video_view = self.viewport.addViewBox() self.video = pg.ImageItem() self.video_view.addItem(self.video) self.video_view.setMouseEnabled(x=False, y=False) #make it can not move self.setLayout(self.layout) self.layout.addWidget(self.viewport) self.img_label = QLabel() # self.horizontalLayout = QVBoxLayout() # self.horizontalLayout.addWidget(self.img_label) # self.layout.addLayout(self.horizontalLayout) screen = QtGui.QDesktopWidget().screenGeometry() self.setFixedSize(screen.width() * 15 / 100, screen.height() * 14.5 / 100) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: try: fpath = IOHelper.get_config_setting('DATA_PATH') img_fpath = QFileDialog.getOpenFileName( self, "Open File", fpath) # name path strimg_fpath = str(img_fpath) img_file = strimg_fpath[2:len(strimg_fpath) - 19] img_path = Path(img_file) file = open(img_path) linescontent = file.readlines( ) # Read the file as a behavior unit rows = len(linescontent) # get the numbers fo line lines = len(linescontent[0].strip().split(' ')) img_data = np.zeros((rows, lines)) # Initialization matrix row = 0 for line in linescontent: line = line.strip().split(' ') img_data[row, :] = line[:] row += 1 file.close() img_data = img_data[::-1] img_name = img_path.stem img = {'img_name': img_name, 'img_data': img_data} settings.absimgDatas[self.myserial] = img_data self.img_plot(img) except TypeError: return except PermissionError: return def update_console(self, stri): MAX_LINES = 50 stri = str(stri) new_text = self.prompt_dock.console_text() + '\n' + stri line_list = new_text.splitlines() N_lines = min(MAX_LINES, len(line_list)) # limit output lines new_text = '\n'.join(line_list[-N_lines:]) self.prompt_dock.console_text(new_text) self.prompt_dock.automatic_scroll() def save_image(self): try: if self.video.image is None: print("have no image in window") return fpath = IOHelper.get_config_setting('DATA_PATH') fpath = Path(fpath) dir_path = fpath.joinpath( str(datetime.datetime.now()).split('.')[0].replace( ' ', '-').replace(':', '_')) # print("save images to {}".format(dir_path)) if not dir_path.exists(): dir_path.mkdir() img_data = np.array(self.video.image) # load image name by path img_name1 = settings.widget_params["Analyse Data Setting"][ "Prefix"] img_name2 = (self.img_label.text())[0:20].replace( ' ', '~').replace(':', '').replace('-', '') img_name = str(img_name1) + str(img_name2) img_data = img_data[::-1] img_data = Image.fromarray(img_data) img_data.save(r"{}\{}.png".format(dir_path, img_name)) print("save images to {}".format(dir_path)) # print("images have saved.") except OSError: print('Only new version files can be saved.') def img_plot(self, img_dict): self.video.setImage(img_dict['img_data']) self.img_label.setText(img_dict['img_name']) def clear_win(self): self.video.clear() self.img_label.setText('')
class CameraViewerWidget(QWidget): """Widget for holding the images generated by the camera. """ specialTask = pyqtSignal() stopSpecialTask = pyqtSignal() def __init__(self, parent=None): super().__init__(parent=parent) # General layout of the widget to hold an image and a histogram self.layout = QHBoxLayout(self) # Settings for the image self.viewport = GraphicsLayoutWidget() self.view = self.viewport.addViewBox(lockAspect=False, enableMenu=True) self.autoScale = QAction("Auto Range", self.view.menu) self.autoScale.triggered.connect(self.do_auto_scale) self.view.menu.addAction(self.autoScale) self.img = pg.ImageItem() self.marker = pg.PlotDataItem(pen=None) self.marker.setBrush(255, 0, 0, 255) self.view.addItem(self.img) self.view.addItem(self.marker) self.imv = pg.ImageView(view=self.view, imageItem=self.img) # Add everything to the widget self.layout.addWidget(self.imv) self.setLayout(self.layout) self.showCrosshair = False self.showCrossCut = False self.rois = [] self.radius_circle = 10 # The radius to draw around particles self.corner_roi = [0, 0] # Initial ROI corner for the camera self.first_image = True def setup_roi_lines(self, max_size): """Sets up the ROI lines surrounding the image. :param list max_size: List containing the maximum size of the image to avoid ROIs bigger than the CCD.""" self.hline1 = pg.InfiniteLine(angle=0, movable=True, hoverPen={ 'color': "FF0", 'width': 4 }) self.hline2 = pg.InfiniteLine(angle=0, movable=True, hoverPen={ 'color': "FF0", 'width': 4 }) self.vline1 = pg.InfiniteLine(angle=90, movable=True, hoverPen={ 'color': "FF0", 'width': 4 }) self.vline2 = pg.InfiniteLine(angle=90, movable=True, hoverPen={ 'color': "FF0", 'width': 4 }) self.hline1.setValue(0) self.vline1.setValue(0) self.vline2.setValue(max_size[0]) self.hline2.setValue(max_size[1]) self.hline1.setBounds((0, max_size[1])) self.hline2.setBounds((0, max_size[1])) self.vline1.setBounds((0, max_size[0])) self.vline2.setBounds((0, max_size[0])) self.view.addItem(self.hline1) self.view.addItem(self.hline2) self.view.addItem(self.vline1) self.view.addItem(self.vline2) self.corner_roi[0] = 0 self.corner_roi[1] = 0 def get_roi_values(self): """ Get's the ROI values in camera-space. It keeps track of the top left corner in order to update the values before returning. :return: Position of the corners of the ROI region assuming 0-indexed cameras. """ y1 = round(self.hline1.value()) y2 = round(self.hline2.value()) x1 = round(self.vline1.value()) x2 = round(self.vline2.value()) X = np.sort((x1, x2)) Y = np.sort((y1, y2)) # Updates to the real values in camera space X += self.corner_roi[0] Y += self.corner_roi[1] X[1] -= 1 Y[1] -= 1 return X, Y def set_roi_lines(self, X, Y): self.corner_roi = [X[0], Y[0]] self.hline1.setValue(0) self.vline1.setValue(0) x2 = X[1] - X[0] + 1 y2 = Y[1] - Y[0] + 1 self.hline2.setValue(y2) # To the last pixel self.vline2.setValue(x2) # To the last pixel def setup_mouse_tracking(self): self.imv.setMouseTracking(True) self.imv.getImageItem().scene().sigMouseMoved.connect(self.mouseMoved) self.imv.getImageItem().scene().contextMenu = None def keyPressEvent(self, key): """Triggered when there is a key press with some modifier. Shift+C: Removes the cross hair from the screen Ctrl+C: Emits a specialTask signal Ctrl+V: Emits a stopSpecialTask signal These last two events have to be handeled in the mainWindow that implemented this widget.""" modifiers = QApplication.keyboardModifiers() if modifiers == Qt.ShiftModifier: if key.key() == 67: # For letter C of 'Clear if self.showCrosshair: for c in self.crosshair: self.view.removeItem(c) self.showCrosshair = False if self.showCrossCut: self.view.removeItem(self.crossCut) self.showCrossCut = False elif modifiers == Qt.ControlModifier: if key.key() == 67: # For letter C of 'Clear self.specialTask.emit() if key.key() == 86: # For letter V self.stopSpecialTask.emit() def mouseMoved(self, arg): """Updates the position of the cross hair. The mouse has to be moved while pressing down the Ctrl button.""" # arg = evt.pos() modifiers = QApplication.keyboardModifiers() if modifiers == Qt.ControlModifier: if not self.showCrosshair: for c in self.crosshair: self.view.addItem(c) self.showCrosshair = True self.crosshair[1].setValue(int(self.img.mapFromScene(arg).x())) self.crosshair[0].setValue(int(self.img.mapFromScene(arg).y())) elif modifiers == Qt.AltModifier: if not self.showCrossCut: self.view.addItem(self.crossCut) self.showCrossCut = True self.crossCut.setValue(int(self.img.mapFromScene(arg).y())) def do_auto_scale(self): h, y = self.img.getHistogram() self.imv.setLevels(min(h), max(h)) def draw_target_pointer(self, locations): """gets an image and draws a circle around the target locations. :param DataFrame locations: DataFrame generated by trackpy's locate method. It only requires columns `x` and `y` with coordinates. """ if locations is None: return locations = locations[['y', 'x']].values brush = pg.mkBrush(color=(255, 0, 0)) self.marker.setData(locations[:, 0], locations[:, 1], symbol='x', symbolBrush=brush) def update_image(self, image): self.img.setImage(image.astype(int), autoLevels=False, autoRange=False, autoHistogramRange=False) if self.first_image: self.do_auto_scale() self.first_image = False def setup_cross_hair(self, max_size): """Sets up a cross hair.""" self.crosshair = [] self.crosshair.append( pg.InfiniteLine(angle=0, movable=False, pen={ 'color': 124, 'width': 4 })) self.crosshair.append( pg.InfiniteLine(angle=90, movable=False, pen={ 'color': 124, 'width': 4 })) self.crosshair[0].setBounds((1, max_size[1] - 1)) self.crosshair[1].setBounds((1, max_size[0] - 1)) def setup_cross_cut(self, max_size): """Set ups the horizontal line for the cross cut.""" self.crossCut = pg.InfiniteLine(angle=0, movable=False, pen={ 'color': 'g', 'width': 2 }) self.crossCut.setBounds((1, max_size))
class MainWindow(QtWidgets.QMainWindow): def __init__(self, host, port, path, parent=None): super().__init__(parent) self.path = path self.maze_files = sorted( fname.relative_to(self.path) for fname in self.path.glob('**/*.txt') if fname.is_file() ) self.setWindowTitle('Micromouse maze simulator') self.resize(800, 600) self.history = [] self.status = QStatusBar() self.setStatusBar(self.status) self.search = QLineEdit() self.search.textChanged.connect(self.filter_mazes) self.files = QListWidget() self.files.currentItemChanged.connect(self.list_value_changed) self.graphics = GraphicsLayoutWidget() viewbox = self.graphics.addViewBox() viewbox.setAspectLocked() self.maze = MazeItem() viewbox.addItem(self.maze) self.slider = QSlider(QtCore.Qt.Horizontal) self.slider.setSingleStep(1) self.slider.setPageStep(10) self.slider.setTickPosition(QSlider.TicksAbove) self.slider.valueChanged.connect(self.slider_value_changed) self.reset() files_layout = QVBoxLayout() files_layout.setContentsMargins(0, 0, 0, 0) files_layout.addWidget(self.search) files_layout.addWidget(self.files) files_widget = QWidget() files_widget.setLayout(files_layout) graphics_layout = QVBoxLayout() graphics_layout.setContentsMargins(0, 0, 0, 0) graphics_layout.addWidget(self.graphics) graphics_layout.addWidget(self.slider) graphics_widget = QWidget() graphics_widget.setLayout(graphics_layout) central_splitter = QSplitter() central_splitter.addWidget(files_widget) central_splitter.addWidget(graphics_widget) main_layout = QVBoxLayout() main_layout.addWidget(central_splitter) main_layout.setContentsMargins(4, 4, 4, 4) main_widget = QWidget() main_widget.setLayout(main_layout) self.setCentralWidget(main_widget) self.filter_mazes('') self.context = zmq.Context() self.reply = self.context.socket(zmq.PUSH) self.reply.connect('inproc://reply') self.thread = QtCore.QThread() self.zeromq_listener = ZMQListener(self.context, host=host, port=port) self.zeromq_listener.moveToThread(self.thread) self.thread.started.connect(self.zeromq_listener.loop) self.zeromq_listener.message.connect(self.signal_received) QtCore.QTimer.singleShot(0, self.thread.start) def filter_mazes(self, text): keywords = text.lower().split(' ') self.files.clear() for fname in self.maze_files: for key in keywords: if key not in str(fname).lower(): break else: self.files.addItem(str(fname)) self.files.setCurrentRow(0) def list_value_changed(self, after, before): if not after: return self.set_maze(after.text()) def set_maze(self, fname): template_file = Path(fname) template = load_maze(self.path / template_file) self.maze.reset(template) self.reset() def reset(self): self.history = [] self.slider.setValue(-1) self.slider.setRange(-1, -1) self.status.showMessage('Ready') def slider_update(self): self.slider.setTickInterval(len(self.history) / 10) self.slider.setRange(0, len(self.history) - 1) self.status_set_slider(self.slider.value()) def slider_value_changed(self, value): self.status_set_slider(value) if not len(self.history): return state = self.history[value] position = state[:3] discovery = state[3:] self.maze.update_position(position) self.maze.update_discovery(discovery) def status_set_slider(self, value): self.status.showMessage('{}/{}'.format(value, len(self.history) - 1)) def signal_received(self, message): if message.startswith(b'W'): walls = self.maze.read_position_walls(message.lstrip(b'W')) self.reply.send(struct.pack('3B', *walls)) return if message.startswith(b'S'): message = message.lstrip(b'S') self.history.append(message) self.reply.send(b'ok') self.slider_update() return if message == b'reset': self.reset() self.reply.send(b'ok') return if message == b'ping': self.reply.send(b'pong') return raise ValueError('Unknown message received! "{}"'.format(message)) def closeEvent(self, event): self.zeromq_listener.running = False self.thread.quit() self.thread.wait()
class PlotWindow(QWidget): img_dict = pyqtSignal(object) def __init__(self): super(PlotWindow, self).__init__() self.layout = QVBoxLayout(self) pg.setConfigOptions(imageAxisOrder='row-major') self.viewport = GraphicsLayoutWidget() self.video_view = self.viewport.addViewBox() self.video = pg.ImageItem() self.video_view.addItem(self.video) self.setLayout(self.layout) self.layout.addWidget(self.viewport) self.img_label = QLabel() self.push_btn = QPushButton("sent to main window", self) self.push_btn.clicked.connect(self.btn_state) self.save_btn = QPushButton("save", self) self.save_btn.clicked.connect(self.save_image) self.horizontalLayout = QHBoxLayout() self.horizontalLayout.addWidget(self.push_btn) self.horizontalLayout.addWidget(self.save_btn) self.horizontalLayout.addWidget(self.img_label) self.layout.addLayout(self.horizontalLayout) def btn_state(self): if self.video.image is None: print("have no image in window") return img_dict = { 'img_data': np.array(self.video.image), 'img_name': self.img_label.text() } self.img_dict.emit(img_dict) def save_image(self): if self.video.image is None: print("have no image in window") return fpath = IOHelper.get_config_setting('DATA_PATH') fpath = Path(fpath) dir_path = fpath.joinpath( str(datetime.datetime.now()).split('.')[0].replace(' ', '-').replace( ':', '_')) print("save images to {}".format(dir_path)) if not dir_path.exists(): dir_path.mkdir() img_data = np.array(self.video.image) # load image name by path img_name = (self.img_label.text()).split('.')[0].replace( ' ', '-').replace(':', '_') img_data = Image.fromarray(img_data) img_data.save(r"{}\{}.png".format(dir_path, img_name)) print("images have saved.") def img_plot(self, img_dict): self.video.setImage(img_dict['img_data']) self.img_label.setText(img_dict['img_name']) def clear_win(self): self.video.clear() self.img_label.setText('')
class MainUI(object): def __init__(self, MainWindow, data_path): MainWindow.setWindowTitle('Tracking demo') MainWindow.resize(1000, 700) # data path self.gt_path = data_path self.path = data_path # Dataset Selection self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.groupBox = QtWidgets.QGroupBox(self.centralwidget) self.groupBox.setGeometry(QtCore.QRect(10, 10, 150, 200)) self.groupBox.setObjectName("groupBox") self.groupBox_legend = QtWidgets.QGroupBox(self.centralwidget) self.groupBox_legend.setGeometry(QtCore.QRect(10, 220, 200, 300)) self.groupBox_legend.setObjectName("groupBox_legend") # self.legend_label = QtWidgets.QLabel() self.legend_label = QtWidgets.QLabel(self.groupBox_legend) self.legend_label.setGeometry(QtCore.QRect(10, 20, 95, 20)) self.legend_label.setText("Ground Trut ----") self.legend_label.setStyleSheet( " color: rgba(255, 0, 0); font-size: 10pt; font-weight: 300;") self.legend_label2 = QtWidgets.QLabel(self.groupBox_legend) self.legend_label2.setText("Tracking Result ----") self.legend_label2.setStyleSheet( " color: rgba(0,255, 0); font-size: 10pt; font-weight: 300;") self.radBtn_1 = QtWidgets.QRadioButton(self.groupBox) self.radBtn_1.setGeometry(QtCore.QRect(10, 20, 95, 20)) self.radBtn_1.setObjectName("panda_radBtn") self.radBtn_1.toggled.connect(self.setDataset) self.radBtn_2 = QtWidgets.QRadioButton(self.groupBox) self.radBtn_2.setGeometry(QtCore.QRect(10, 40, 95, 20)) self.radBtn_2.setObjectName("tiger_radBtn") self.radBtn_2.toggled.connect(self.setDataset) self.radBtn_3 = QtWidgets.QRadioButton(self.groupBox) self.radBtn_3.setGeometry(QtCore.QRect(10, 60, 95, 20)) self.radBtn_3.setObjectName("panda_radBtn") self.radBtn_3.toggled.connect(self.setDataset) self.radBtn_4 = QtWidgets.QRadioButton(self.groupBox) self.radBtn_4.setGeometry(QtCore.QRect(10, 80, 95, 20)) self.radBtn_4.setObjectName("tiger_radBtn") self.radBtn_4.toggled.connect(self.setDataset) self.radBtn_5 = QtWidgets.QRadioButton(self.groupBox) self.radBtn_5.setGeometry(QtCore.QRect(10, 100, 95, 20)) self.radBtn_5.setObjectName("panda_radBtn") self.radBtn_5.toggled.connect(self.setDataset) self.model_pulldown = QtGui.QComboBox(self.groupBox) self.model_pulldown.setGeometry(QtCore.QRect(15, 125, 93, 28)) self.model_pulldown.setObjectName("model_pulldown") # self.model_pulldown.addItem("Select") self.model_pulldown.addItem("KCF") self.model_pulldown.addItem("MDNet") self.model_pulldown.addItem("SiamFC") self.img_root = self.path["KCF"] self.model_pulldown.activated.connect(self.setModel) self.display_Btn = QtWidgets.QPushButton(self.groupBox) self.display_Btn.setGeometry(QtCore.QRect(15, 155, 93, 28)) self.display_Btn.setObjectName("display_Btn") # Set up GraphicsLayoutWidget for images self.graphicsWindow = GraphicsLayoutWidget(self.centralwidget, border=True) self.graphicsWindow.setGeometry(QtCore.QRect(140, 10, 850, 600)) self.graphicsWindow.setObjectName("graphicsWindow") MainWindow.setCentralWidget(self.centralwidget) self.score_box = self.graphicsWindow.addViewBox(0, 0, colspan=100) self.ref_box = self.graphicsWindow.addViewBox(0, 100, colspan=50) self.score_box.invertY( True) # Images usually have their Y-axis pointing downward self.ref_box.invertY(True) self.score_box.setAspectLocked(True) self.ref_box.setAspectLocked(True) # image stuff self.score_map = pg.ImageItem(axisOrder='row-major') self.groundtruth_img = pg.ImageItem(axisOrder='row-major') self.ref_img = pg.ImageItem(axisOrder='row-major') # Set Image placeholders self.score_map.setImage(np.zeros((300, 230, 3))) self.groundtruth_img.setImage(np.zeros((300, 230, 3))) self.ref_img.setImage(np.zeros((300, 230, 3))) self.score_box.addItem(self.score_map) self.ref_box.addItem(self.ref_img) # laybels # Add the Labels to the images font = QtGui.QFont() font.setPointSize(4) # parameter for text display param_dict = {'color': (255, 255, 255), 'anchor': (0, 1)} label_score = pg.TextItem(text='Tracking Result', **param_dict) label_gt = pg.TextItem(text='Tracking Error', **param_dict) label_ref = pg.TextItem(text='Reference Image', **param_dict) font.setPointSize(16) label_score.setFont(font) label_gt.setFont(font) label_ref.setFont(font) label_score.setParentItem(self.score_map) label_gt.setParentItem(self.groundtruth_img) label_ref.setParentItem(self.ref_img) self.score_box.addItem(label_score) self.ref_box.addItem(label_ref) # display buttons self.display_Btn.clicked.connect(self.addImages) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) self.i = 0 self.error = np.zeros((1)) self.updateTime = ptime.time() self.fps = 0 # display error plot self.error_plot = self.graphicsWindow.addPlot(3, 0, colspan=200) self.error_data = np.zeros((3, )) self.curve1 = self.error_plot.plot(self.error_data) MainWindow.show() def exit(self): sys.exit() def setDataset(self): if self.radBtn_1.isChecked(): self.img_disp_path = "bolt1/" self.template_path = "bolt.jpg" self.temp_img = self.load_temp() self.error_plot.removeItem(self.curve1) self.error_data = self.read_error() if self.radBtn_2.isChecked(): self.img_disp_path = "bolt2/" self.template_path = "bolt2.jpg" self.temp_img = self.load_temp() self.error_plot.removeItem(self.curve1) self.error_data = self.read_error() if self.radBtn_3.isChecked(): self.img_disp_path = "football/" self.template_path = "football.jpg" self.temp_img = self.load_temp() self.error_plot.removeItem(self.curve1) self.error_data = self.read_error() if self.radBtn_4.isChecked(): self.img_disp_path = "football1/" self.template_path = "football1.jpg" self.temp_img = self.load_temp() self.error_plot.removeItem(self.curve1) self.error_data = self.read_error() if self.radBtn_5.isChecked(): self.img_disp_path = "mountainbike/" self.template_path = "mountainbike.jpg" self.temp_img = self.load_temp() self.error_plot.removeItem(self.curve1) self.error_data = self.read_error() def setModel(self): if self.model_pulldown.currentText() == "KCF": self.error_plot.removeItem(self.curve1) self.img_root = self.path["KCF"] if self.model_pulldown.currentText() == "MDNet": self.error_plot.removeItem(self.curve1) self.img_root = self.path["MDNet"] if self.model_pulldown.currentText() == "SiamFC": self.error_plot.removeItem(self.curve1) self.img_root = self.path["SiamFC"] def addImages(self, MainWindow): self.i = 0 self.data_set = self.load_data() self.error = np.zeros((1)) self.curve1 = self.error_plot.plot(np.zeros((1, ))) self.error_plot.removeItem(self.curve1) self.curve1 = self.error_plot.plot(np.zeros((1, ))) self.updateData() def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) self.groupBox.setTitle(_translate("MainWindow", "Select Dataset")) self.radBtn_1.setText(_translate("MainWindow", "Bolt1")) self.radBtn_2.setText(_translate("MainWindow", "Bolt2")) self.radBtn_3.setText(_translate("MainWindow", "football")) self.radBtn_4.setText(_translate("MainWindow", "football1")) self.radBtn_5.setText(_translate("MainWindow", "mountainbike")) self.display_Btn.setText(_translate("MainWindow", "Display!")) def load_temp(self): imagePath = os.path.join(self.img_root + self.template_path) temp = cv2.imread(imagePath) temp = cv2.cvtColor(temp, cv2.COLOR_BGR2RGB) return temp def load_data(self): n_files = len(os.listdir(self.img_root + self.img_disp_path)) - 3 image_set = [] for i in range(1, n_files): imagePath = os.path.join(self.img_root + self.img_disp_path + "%08d.jpg" % i) frame = cv2.imread(imagePath) # 1 for colored imaged frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) image_set.append(frame) return image_set def read_error(self): lineList = [] filePath = os.path.join(self.img_root + self.img_disp_path + "error.txt") with open(filePath, 'r') as file: for line in file: lines = [float(number) for number in line.strip().split()] lineList.append(lines[0]) return np.array(lineList) def updateData(self): self.score_map.setImage(self.data_set[self.i]) self.ref_img.setImage(self.temp_img) self.i = (self.i + 1) % len(self.data_set) now = ptime.time() fps2 = 1.0 / (now - self.updateTime) self.updateTime = now self.fps = self.fps * 0.9 + fps2 * 0.1 time.sleep(0.01) QtCore.QTimer.singleShot(100, self.updateData) self.curve1.setData(self.error_data[0:self.i])
class CameraViewerWidget(DataViewWidget): """ The Camera Viewer Widget is a wrapper around PyQtGraph ImageView. It adds some common methods for getting extra mouse interactions, such as performing an auto-range through right-clicking, it allows to drag and drop horizontal and vertical lines to define a ROI, and it allows to draw on top of the image. The core idea is to make these options explicit, in order to systematize them in one place. Signals ------- clicked_on_image: Emits [float, float] with the coordinates where the mouse was clicked on the image. Does not distinguish between left/right clicks. Any further processing must be done downstream. Attributes ---------- layout: QHBoxLayout, in case extra elements must be added viewport: GraphicsLayoutWidget view: ViewBox img: ImageItem imv: ImageView auto_levels: Whether to actualize the levels of the image every time they are refreshed """ clicked_on_image = pyqtSignal([float, float]) def __init__(self, parent=None): super().__init__(parent=parent) # Settings for the image self.viewport = GraphicsLayoutWidget() self.view = self.viewport.addViewBox(lockAspect=False, enableMenu=True) self.img = pg.ImageItem() self.view.addItem(self.img) self.imv = pg.ImageView(view=self.view, imageItem=self.img) self.scene().sigMouseClicked.connect(self.mouse_clicked) # Add everything to the widget layout = self.get_layout() layout.addWidget(self.imv) self.show_roi_lines = False # ROI lines are shown or not self.show_cross_hair = False # Cross hair is shown or not self.show_cross_cut = False # Cross cut is shown or not self.cross_hair_setup = False # Cross hair was setup or not self.cross_cut_setup = False # Cross cut was setup or not self.rois = [] self.corner_roi = [0, 0] # Initial ROI corner for the camera self.first_image = True self.logger = get_logger() self.last_image = None self.add_actions_to_menu() self.setup_mouse_tracking() def scene(self): """ Shortcut to getting the image scene""" return self.img.scene() def update_image(self, image, auto_range=False, auto_histogram_range=False): """ Updates the image being displayed with some sensitive defaults, which can be over written if needed. """ auto_levels = self.auto_levels_action.isChecked() self.logger.debug(f'Updating image with auto_levels: {auto_levels}') if image is not None: self.imv.setImage(image, autoLevels=auto_levels, autoRange=auto_range, autoHistogramRange=auto_histogram_range) if self.first_image: self.do_auto_range() self.first_image = False self.last_image = image else: self.logger.debug(f'No new image to update') def setup_roi_lines(self, max_size=None): """Sets up the ROI lines surrounding the image. :param list max_size: List containing the maximum size of the image to avoid ROIs bigger than the CCD.""" if self.last_image is None: return self.logger.info('Setting up ROI lines') if not isinstance(max_size, list): max_size = self.last_image.shape self.hline1 = pg.InfiniteLine(angle=0, movable=True, hoverPen={ 'color': "FF0", 'width': 4 }) self.hline2 = pg.InfiniteLine(angle=0, movable=True, hoverPen={ 'color': "FF0", 'width': 4 }) self.vline1 = pg.InfiniteLine(angle=90, movable=True, hoverPen={ 'color': "FF0", 'width': 4 }) self.vline2 = pg.InfiniteLine(angle=90, movable=True, hoverPen={ 'color': "FF0", 'width': 4 }) self.hline1.setValue(0) self.vline1.setValue(0) self.vline2.setValue(max_size[0]) self.hline2.setValue(max_size[1]) self.hline1.setBounds((0, max_size[1])) self.hline2.setBounds((0, max_size[1])) self.vline1.setBounds((0, max_size[0])) self.vline2.setBounds((0, max_size[0])) self.view.addItem(self.hline1) self.view.addItem(self.hline2) self.view.addItem(self.vline1) self.view.addItem(self.vline2) self.corner_roi[0] = 0 self.corner_roi[1] = 0 def get_roi_values(self): """ Get's the ROI values in camera-space. It keeps track of the top left corner in order to update the values before returning. :return: Position of the corners of the ROI region assuming 0-indexed cameras. """ y1 = round(self.hline1.value()) y2 = round(self.hline2.value()) x1 = round(self.vline1.value()) x2 = round(self.vline2.value()) width = np.abs(x1 - x2) height = np.abs(y1 - y2) x = np.min((x1, x2)) + self.corner_roi[0] y = np.min((y1, y2)) + self.corner_roi[1] return (x, width), (y, height) def set_roi_lines(self, X, Y): self.corner_roi = [X[0], Y[0]] self.hline1.setValue(0) self.vline1.setValue(0) self.hline2.setValue(Y[1]) # To the last pixel self.vline2.setValue(X[1]) # To the last pixel def setup_mouse_tracking(self): self.imv.setMouseTracking(True) self.imv.getImageItem().scene().sigMouseMoved.connect(self.mouseMoved) self.imv.getImageItem().scene().contextMenu = None def keyPressEvent(self, key): """Triggered when there is a key press with some modifier. Shift+C: Removes the cross hair from the screen These last two events have to be handeled in the mainWindow that implemented this widget.""" modifiers = QApplication.keyboardModifiers() if modifiers == Qt.ShiftModifier: if key.key() == 67: # For letter C of 'Clear if self.show_cross_hair: for c in self.crosshair: self.view.removeItem(c) self.show_cross_hair = False if self.show_cross_cut: self.view.removeItem(self.crossCut) self.show_cross_cut = False def mouseMoved(self, arg): """Updates the position of the cross hair. The mouse has to be moved while pressing down the Ctrl button.""" # arg = evt.pos() modifiers = QApplication.keyboardModifiers() if modifiers == Qt.ControlModifier: if self.cross_hair_setup: if not self.show_cross_hair: for c in self.crosshair: self.view.addItem(c) self.show_cross_hair = True self.crosshair[1].setValue(int(self.img.mapFromScene(arg).x())) self.crosshair[0].setValue(int(self.img.mapFromScene(arg).y())) elif modifiers == Qt.AltModifier: self.logger.debug('Moving mouse while pressing Alt') if self.cross_cut_setup: if not self.show_cross_cut: self.view.addItem(self.crossCut) self.show_cross_cut = True self.crossCut.setValue(int(self.img.mapFromScene(arg).y())) def do_auto_range(self): """ Sets the levels of the image based on the maximum and minimum. This is useful when auto-levels are off (the default behavior), and one needs to quickly adapt the histogram. """ h, y = self.img.getHistogram() self.imv.setLevels(min(h), max(h)) def draw_target_pointer(self, locations): """gets an image and draws a circle around the target locations. :param DataFrame locations: DataFrame generated by trackpy's locate method. It only requires columns `x` and `y` with coordinates. """ if locations is None: return locations = locations[['y', 'x']].values brush = pg.mkBrush(color=(255, 0, 0)) self.marker.setData(locations[:, 0], locations[:, 1], symbol='x', symbolBrush=brush) def setup_cross_hair(self, max_size): """Sets up a cross hair.""" self.show_cross_hair = self.setup_cross_hair_action.isChecked() if self.last_image is None: return self.logger.info('Setting up Cross Hair lines') if not isinstance(max_size, list): max_size = self.last_image.shape self.cross_hair_setup = True self.crosshair = [] self.crosshair.append( pg.InfiniteLine(angle=0, movable=False, pen={ 'color': 124, 'width': 4 })) self.crosshair.append( pg.InfiniteLine(angle=90, movable=False, pen={ 'color': 124, 'width': 4 })) self.crosshair[0].setBounds((1, max_size[1] - 1)) self.crosshair[1].setBounds((1, max_size[0] - 1)) def setup_cross_cut(self, max_size): """Set ups the horizontal line for the cross cut.""" self.show_cross_cut = self.setup_cross_cut_action.isChecked() if self.last_image is None: return self.logger.info('Setting up horizontal cross cut line') if not isinstance(max_size, list): max_size = self.last_image.shape[1] self.cross_cut_setup = True self.crossCut = pg.InfiniteLine(angle=0, movable=False, pen={ 'color': 'g', 'width': 2 }) self.crossCut.setBounds((1, max_size)) def mouse_clicked(self, evnt): modifiers = evnt.modifiers() if modifiers == Qt.ControlModifier: self.clicked_on_image.emit( self.img.mapFromScene(evnt.pos()).x(), self.img.mapFromScene(evnt.pos()).y()) def add_actions_to_menu(self): """ Adds actions to the contextual menu. If you want to have control on which actions appear, consider subclassing this widget and overriding this method. """ self.auto_range_action = QAction("Auto Range", self.view.menu) self.auto_range_action.triggered.connect(self.do_auto_range) self.setup_roi_lines_action = QAction("Setup ROI", self.view.menu) self.setup_roi_lines_action.triggered.connect(self.setup_roi_lines) self.setup_cross_cut_action = QAction("Setup cross cut", self.view.menu, checkable=True) self.setup_cross_cut_action.triggered.connect(self.setup_cross_cut) self.setup_cross_hair_action = QAction("Setup cross hair", self.view.menu, checkable=True) self.setup_cross_hair_action.triggered.connect(self.setup_cross_hair) self.auto_levels_action = QAction('Auto Levels', self.view.menu, checkable=True) self.view.menu.addAction(self.auto_range_action) self.view.menu.addAction(self.auto_levels_action) self.view.menu.addAction(self.setup_roi_lines_action) self.view.menu.addAction(self.setup_cross_hair_action) self.view.menu.addAction(self.setup_cross_cut_action) @classmethod def connect_to_camera(cls, camera, refresh_time=50, parent=None): """ Instantiate the viewer using connect_to_camera in order to get some functionality out of the box. It will create a timer to automatically update the image """ instance = cls(parent=parent) instance.timer = QTimer() instance.timer.timeout.connect( lambda: instance.update_image(camera.temp_image)) instance.timer.start(refresh_time) return instance