class MiniMap(QtGui.QWidget): """Shows the entire signal and allows the user to navigate through it. Provides an scrollable selector over the entire signal. Attributes: xmin: Selector lower limit (measured in h-axis units). xmax: Selector upper limit (measured in h-axis units). step: Selector length (measured in h-axis units). """ def __init__(self, parent, ax, record=None): super(MiniMap, self).__init__(parent) self.ax = ax self.xmin = 0.0 self.xmax = 0.0 self.step = 10.0 self.xrange = np.array([]) self.minimapFig = plt.figure() self.minimapFig.set_figheight(0.75) self.minimapFig.add_axes((0, 0, 1, 1)) self.minimapCanvas = FigureCanvas(self.minimapFig) self.minimapCanvas.setFixedHeight(64) self.minimapSelector = self.minimapFig.axes[0].axvspan(0, self.step, color='gray', alpha=0.5, animated=True) self.minimapSelection = self.minimapFig.axes[0].axvspan( 0, self.step, color='LightCoral', alpha=0.5, animated=True) self.minimapSelection.set_visible(False) self.minimapBackground = [] self.minimapSize = (self.minimapFig.bbox.width, self.minimapFig.bbox.height) self.press_selector = None self.playback_marker = None self.minimapCanvas.mpl_connect('button_press_event', self.onpress) self.minimapCanvas.mpl_connect('button_release_event', self.onrelease) self.minimapCanvas.mpl_connect('motion_notify_event', self.onmove) # Animation related attrs. self.background = None self.animated = False # Set the layout self.layout = QtGui.QVBoxLayout(self) self.layout.addWidget(self.minimapCanvas) # Animation related attributes self.parentViewer = parent # Set Markers dict self.markers = {} self.record = None if record is not None: self.set_record(record) def set_record(self, record, step): self.record = record self.step = step self.xrange = np.linspace(0, len(self.record.signal) / self.record.fs, num=len(self.record.signal), endpoint=False) self.xmin = self.xrange[0] self.xmax = self.xrange[-1] self.markers = {} ax = self.minimapFig.axes[0] ax.lines = [] formatter = FuncFormatter( lambda x, pos: str(datetime.timedelta(seconds=x))) ax.xaxis.set_major_formatter(formatter) ax.grid(True, which='both') # Set dataseries to plot xmin = self.xmin * self.record.fs xmax = self.xmax * self.record.fs pixel_width = np.ceil(self.minimapFig.get_figwidth() * self.minimapFig.get_dpi()) x_data, y_data = plotting.reduce_data(self.xrange, self.record.signal, pixel_width, xmin, xmax) # self._plot_data.set_xdata(x_data) # self._plot_data.set_ydata(y_data) ax.plot(x_data, y_data, color='black', rasterized=True) ax.set_xlim(self.xmin, self.xmax) plotting.adjust_axes_height(ax) # Set the playback marker self.playback_marker = PlayBackMarker(self.minimapFig, self) self.playback_marker.markers[0].set_animated(True) # Draw canvas self.minimapCanvas.draw() self.minimapBackground = self.minimapCanvas.copy_from_bbox( self.minimapFig.bbox) self.draw_animate() def onpress(self, event): self.press_selector = event xdata = round(self.get_xdata(event), 2) xmin = round(xdata - (self.step / 2.0), 2) xmax = round(xdata + (self.step / 2.0), 2) self.parentViewer._set_animated(True) self.set_selector_limits(xmin, xmax) def onrelease(self, event): self.press_selector = None # Finish parent animation self.parentViewer._set_animated(False) def onmove(self, event): if self.press_selector is not None: xdata = round(self.get_xdata(event), 2) xmin = round(xdata - (self.step / 2.0), 2) xmax = round(xdata + (self.step / 2.0), 2) self.set_selector_limits(xmin, xmax) def get_xdata(self, event): inv = self.minimapFig.axes[0].transData.inverted() xdata, _ = inv.transform((event.x, event.y)) return xdata def set_selector_limits(self, xmin, xmax): step = xmax - xmin if step >= self.xmax - self.xmin: xleft = self.xmin xright = self.xmax if xmin < self.xmin: xleft = self.xmin xright = self.step elif xmax > self.xmax: xleft = self.xmax - step xright = self.xmax else: xleft = xmin xright = xmax if (xleft, xright) != (self.minimapSelector.xy[1, 0], self.minimapSelector.xy[2, 0]): self.step = step self.minimapSelector.xy[:2, 0] = xleft self.minimapSelector.xy[2:4, 0] = xright self.ax.set_xlim(xleft, xright) self.draw_animate() else: self.parentViewer.draw() def get_selector_limits(self): return self.minimapSelector.xy[0, 0], self.minimapSelector.xy[2, 0] def draw(self): self.draw_animate() def draw_animate(self): size = self.minimapFig.bbox.width, self.minimapFig.bbox.height if size != self.minimapSize: self.minimapSize = size self.minimapCanvas.draw() self.minimapBackground = self.minimapCanvas.copy_from_bbox( self.minimapFig.bbox) self.minimapCanvas.restore_region(self.minimapBackground) self.minimapFig.draw_artist(self.minimapSelection) self.minimapFig.draw_artist(self.minimapSelector) self.minimapFig.draw_artist(self.playback_marker.markers[0]) for marker in self.markers.values(): self.minimapFig.draw_artist(marker) self.minimapCanvas.blit(self.minimapFig.bbox) def set_visible(self, value): self.minimapCanvas.setVisible(value) def get_visible(self): return self.minimapCanvas.isVisible() def set_selection_limits(self, xleft, xright): self.minimapSelection.xy[:2, 0] = xleft self.minimapSelection.xy[2:4, 0] = xright self.draw_animate() def set_selection_visible(self, value): self.minimapSelection.set_visible(value) self.draw_animate() def create_marker(self, key, position, **kwargs): if self.xmin <= position <= self.xmax: marker = self.minimapFig.axes[0].axvline(position, animated=True) self.markers[key] = marker self.markers[key].set(**kwargs) def set_marker_position(self, key, value): marker = self.markers.get(key) if marker is not None: if self.xmin <= value <= self.xmax: marker.set_xdata(value) def set_marker(self, key, **kwargs): marker = self.markers.get(key) if marker is not None: kwargs.pop( "animated", None ) # marker's animated property must be always true to be drawn properly marker.set(**kwargs) def delete_marker(self, key): marker = self.markers.get(key) if marker is not None: self.minimapFig.axes[0].lines.remove(marker) self.markers.pop(key)
class MainGUI(QWidget): # signal_DMDmask is cictionary with laser specification as key and binary mask as content. signal_DMDmask = pyqtSignal(dict) signal_DMDcontour = pyqtSignal(list) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) os.chdir('./') # Set directory to current folder. self.setFont(QFont("Arial")) # self.setMinimumSize(900, 1020) self.setWindowTitle("Cell Selection") self.layout = QGridLayout(self) self.roi_list_freehandl_added = [] self.selected_ML_Index = [] self.selected_cells_infor_dict = {} self.mask_color_multiplier = [1, 1, 0] # ============================================================================= # Container for image display # ============================================================================= graphContainer = StylishQT.roundQGroupBox() graphContainerLayout = QGridLayout() self.Imgviewtabs = QTabWidget() MLmaskviewBox = QWidget() MLmaskviewBoxLayout = QGridLayout() self.Matdisplay_Figure = Figure() self.Matdisplay_Canvas = FigureCanvas(self.Matdisplay_Figure) self.Matdisplay_Canvas.setFixedWidth(500) self.Matdisplay_Canvas.setFixedHeight(500) self.Matdisplay_Canvas.mpl_connect('button_press_event', self._onclick) self.Matdisplay_toolbar = NavigationToolbar(self.Matdisplay_Canvas, self) MLmaskviewBoxLayout.addWidget(self.Matdisplay_toolbar, 0, 0) MLmaskviewBoxLayout.addWidget(self.Matdisplay_Canvas, 1, 0) MLmaskviewBox.setLayout(MLmaskviewBoxLayout) self.Imgviewtabs.addTab(MLmaskviewBox, "MaskRCNN") # ============================================================================= # Mask editing tab # ============================================================================= MLmaskEditBox = QWidget() MLmaskEditBoxLayout = QGridLayout() self.Mask_edit_view = DrawingWidget(self) self.Mask_edit_view.enable_drawing(False) # Disable drawing first # self.Mask_edit_view = pg.ImageView() # self.Mask_edit_view.getView().setLimits(xMin = 0, xMax = 2048, yMin = 0, yMax = 2048, minXRange = 2048, minYRange = 2048, maxXRange = 2048, maxYRange = 2048) self.Mask_edit_viewItem = self.Mask_edit_view.getImageItem() # self.ROIitem = pg.PolyLineROI([[0,0], [80,0], [80,80], [0,80]], closed=True) self.Mask_edit_view_getView = self.Mask_edit_view.getView() # self.Mask_edit_view_getView.addItem(self.ROIitem) self.Mask_edit_view.ui.roiBtn.hide() self.Mask_edit_view.ui.menuBtn.hide() self.Mask_edit_view.ui.normGroup.hide() self.Mask_edit_view.ui.roiPlot.hide() MLmaskEditBoxLayout.addWidget(self.Mask_edit_view, 0, 0) MLmaskEditBox.setLayout(MLmaskEditBoxLayout) self.Imgviewtabs.addTab(MLmaskEditBox, "Mask edit") graphContainerLayout.addWidget(self.Imgviewtabs, 0, 0) graphContainer.setLayout(graphContainerLayout) # ============================================================================= # Operation container # ============================================================================= operationContainer = StylishQT.roundQGroupBox() operationContainerLayout = QGridLayout() self.init_ML_button = QPushButton('Initialize ML', self) operationContainerLayout.addWidget(self.init_ML_button, 0, 0) self.init_ML_button.clicked.connect(self.init_ML) #---------------------Load image from file----------------------------- self.textbox_loadimg = QLineEdit(self) operationContainerLayout.addWidget(self.textbox_loadimg, 1, 0) self.button_import_img_browse = QPushButton('Browse', self) operationContainerLayout.addWidget(self.button_import_img_browse, 1, 1) self.button_import_img_browse.clicked.connect(self.get_img_file_tif) self.run_ML_button = QPushButton('Analysis', self) operationContainerLayout.addWidget(self.run_ML_button, 2, 0) self.run_ML_button.clicked.connect(self.run_ML_onImg_and_display) self.generate_MLmask_button = QPushButton('Mask', self) operationContainerLayout.addWidget(self.generate_MLmask_button, 2, 1) self.generate_MLmask_button.clicked.connect(self.generate_MLmask) self.update_MLmask_button = QPushButton('Update mask', self) operationContainerLayout.addWidget(self.update_MLmask_button, 3, 0) self.update_MLmask_button.clicked.connect(self.update_mask) self.enable_modify_MLmask_button = QPushButton('Enable free-hand', self) self.enable_modify_MLmask_button.setCheckable(True) operationContainerLayout.addWidget(self.enable_modify_MLmask_button, 4, 0) self.enable_modify_MLmask_button.clicked.connect(self.enable_free_hand) # self.modify_MLmask_button = QPushButton('Add patch', self) # operationContainerLayout.addWidget(self.modify_MLmask_button, 4, 1) # self.modify_MLmask_button.clicked.connect(self.addedROIitem_to_Mask) self.clear_roi_button = QPushButton('Clear ROIs', self) operationContainerLayout.addWidget(self.clear_roi_button, 5, 0) self.clear_roi_button.clicked.connect(self.clear_edit_roi) # self.maskLaserComboBox = QComboBox() # self.maskLaserComboBox.addItems(['640', '532', '488']) # operationContainerLayout.addWidget(self.maskLaserComboBox, 6, 0) # # self.generate_transformed_mask_button = QPushButton('Transform mask', self) # operationContainerLayout.addWidget(self.generate_transformed_mask_button, 6, 1) # self.generate_transformed_mask_button.clicked.connect(self.generate_transformed_mask) self.emit_transformed_mask_button = QPushButton('Emit mask', self) operationContainerLayout.addWidget(self.emit_transformed_mask_button, 7, 1) self.emit_transformed_mask_button.clicked.connect( self.emit_mask_contour) operationContainer.setLayout(operationContainerLayout) # ============================================================================= # Mask para container # ============================================================================= MaskparaContainer = StylishQT.roundQGroupBox() MaskparaContainerContainerLayout = QGridLayout() #---------------------------------------------------------------------- self.fillContourButton = QCheckBox() self.invertMaskButton = QCheckBox() self.thicknessSpinBox = QSpinBox() self.thicknessSpinBox.setRange(1, 25) MaskparaContainerContainerLayout.addWidget(QLabel('Fill contour:'), 0, 0) MaskparaContainerContainerLayout.addWidget(self.fillContourButton, 0, 1) MaskparaContainerContainerLayout.addWidget(QLabel('Invert mask:'), 1, 0) MaskparaContainerContainerLayout.addWidget(self.invertMaskButton, 1, 1) MaskparaContainerContainerLayout.addWidget(QLabel('Thickness:'), 2, 0) MaskparaContainerContainerLayout.addWidget(self.thicknessSpinBox, 2, 1) MaskparaContainer.setLayout(MaskparaContainerContainerLayout) # ============================================================================= # Device operation container # ============================================================================= deviceOperationContainer = StylishQT.roundQGroupBox() deviceOperationContainerLayout = QGridLayout() #---------------------------------------------------------------------- self.CamExposureBox = QDoubleSpinBox(self) self.CamExposureBox.setDecimals(6) self.CamExposureBox.setMinimum(0) self.CamExposureBox.setMaximum(100) self.CamExposureBox.setValue(0.001501) self.CamExposureBox.setSingleStep(0.001) deviceOperationContainerLayout.addWidget(self.CamExposureBox, 0, 1) deviceOperationContainerLayout.addWidget(QLabel("Exposure time:"), 0, 0) cam_snap_button = QPushButton('Cam snap', self) deviceOperationContainerLayout.addWidget(cam_snap_button, 0, 2) cam_snap_button.clicked.connect(self.cam_snap) cam_snap_button = QPushButton('Cam snap', self) deviceOperationContainerLayout.addWidget(cam_snap_button, 0, 2) cam_snap_button.clicked.connect(self.cam_snap) deviceOperationContainer.setLayout(deviceOperationContainerLayout) self.layout.addWidget(graphContainer, 0, 0, 3, 1) self.layout.addWidget(operationContainer, 0, 1) self.layout.addWidget(MaskparaContainer, 1, 1) self.layout.addWidget(deviceOperationContainer, 2, 1) self.setLayout(self.layout) #%% # ============================================================================= # MaskRCNN detection part # ============================================================================= # @run_in_thread def init_ML(self): # Initialize the detector instance and load the model. self.ProcessML = ProcessImageML() def get_img_file_tif(self): self.img_tif_filePath, _ = QtWidgets.QFileDialog.getOpenFileName( self, 'Single File', 'M:/tnw/ist/do/projects/Neurophotonics/Brinkslab/Data', "(*.tif)") self.textbox_loadimg.setText(self.img_tif_filePath) if self.img_tif_filePath != None: self.Rawimage = imread(self.img_tif_filePath) self.MLtargetedImg_raw = self.Rawimage.copy() self.MLtargetedImg = self.convert_for_MaskRCNN( self.MLtargetedImg_raw) self.show_raw_image(self.MLtargetedImg) self.addedROIitemMask = np.zeros( (self.MLtargetedImg.shape[0], self.MLtargetedImg.shape[1])) self.MLmask = np.zeros( (self.MLtargetedImg.shape[0], self.MLtargetedImg.shape[1])) def show_raw_image(self, image): # display a single image try: self.Matdisplay_Figure.clear() except: pass ax1 = self.Matdisplay_Figure.add_subplot(111) ax1.set_xticks([]) ax1.set_yticks([]) ax1.imshow(image) self.Matdisplay_Figure.tight_layout() self.Matdisplay_Canvas.draw() RGB_image = gray2rgb(image) self.Mask_edit_viewItem.setImage(RGB_image) def convert_for_MaskRCNN(self, input_img): """Convert the image size and bit-depth to make it suitable for MaskRCNN detection.""" if input_img.shape[0] > 1024 or input_img.shape[1] > 1024: resized_img = resize(input_img, [1024, 1024], preserve_range=True).astype(input_img.dtype) minval = np.min(resized_img) maxval = np.max(resized_img) return ((resized_img - minval) / (maxval - minval) * 255).astype( np.uint8) def run_ML_onImg_and_display(self): """Run MaskRCNN on input image""" self.Matdisplay_Figure.clear() ax1 = self.Matdisplay_Figure.add_subplot(111) # Depends on show_mask or not, the returned figure will be input raw image with mask or not. self.MLresults, self.Matdisplay_Figure_axis, self.unmasked_fig = self.ProcessML.DetectionOnImage( self.MLtargetedImg, axis=ax1, show_mask=False, show_bbox=False) self.Mask = self.MLresults['masks'] self.Label = self.MLresults['class_ids'] self.Score = self.MLresults['scores'] self.Bbox = self.MLresults['rois'] self.SelectedCellIndex = 0 self.NumCells = int(len(self.Label)) self.selected_ML_Index = [] self.selected_cells_infor_dict = {} self.Matdisplay_Figure_axis.imshow(self.unmasked_fig.astype(np.uint8)) self.Matdisplay_Figure.tight_layout() self.Matdisplay_Canvas.draw() #%% # ============================================================================= # Configure click event to add clicked cell mask # ============================================================================= def _onclick(self, event): """Highlights the cell selected in the figure by the user when clicked on""" if self.NumCells > 0: ShapeMask = np.shape(self.Mask) # get coorinates at selected location in image coordinates if event.xdata == None or event.ydata == None: return xcoor = min(max(int(event.xdata), 0), ShapeMask[1]) ycoor = min(max(int(event.ydata), 0), ShapeMask[0]) # search for the mask coresponding to the selected cell for EachCell in range(self.NumCells): if self.Mask[ycoor, xcoor, EachCell]: self.SelectedCellIndex = EachCell break # highlight selected cell if self.SelectedCellIndex not in self.selected_ML_Index: # Get the selected cell's contour coordinates and mask patch self.contour_verts, self.Cell_patch = self.get_cell_polygon( self.Mask[:, :, self.SelectedCellIndex]) self.Matdisplay_Figure_axis.add_patch(self.Cell_patch) self.Matdisplay_Canvas.draw() self.selected_ML_Index.append(self.SelectedCellIndex) self.selected_cells_infor_dict['cell{}_verts'.format( str(self.SelectedCellIndex))] = self.contour_verts else: # If click on the same cell self.Cell_patch.remove() self.Matdisplay_Canvas.draw() self.selected_ML_Index.remove(self.SelectedCellIndex) self.selected_cells_infor_dict.pop('cell{}_verts'.format( str(self.SelectedCellIndex))) def get_cell_polygon(self, mask): # Mask Polygon # Pad to ensure proper polygons for masks that touch image edges. padded_mask = np.zeros((mask.shape[0] + 2, mask.shape[1] + 2), dtype=np.uint8) padded_mask[1:-1, 1:-1] = mask contours = find_contours(padded_mask, 0.5) for verts in contours: # Subtract the padding and flip (y, x) to (x, y) verts = np.fliplr(verts) - 1 contour_polygon = mpatches.Polygon( verts, facecolor=self.random_colors(1)[0]) return contours, contour_polygon def random_colors(self, N, bright=True): """ Generate random colors. To get visually distinct colors, generate them in HSV space then convert to RGB. """ brightness = 1.0 if bright else 0.7 hsv = [(i / N, 1, brightness) for i in range(N)] colors = list(map(lambda c: colorsys.hsv_to_rgb(*c), hsv)) random.shuffle(colors) return colors #%% # ============================================================================= # For mask generation # ============================================================================= def generate_MLmask(self): """ Generate binary mask with all selected cells""" self.MLmask = np.zeros( (self.MLtargetedImg.shape[0], self.MLtargetedImg.shape[1])) if len(self.selected_ML_Index) > 0: for selected_index in self.selected_ML_Index: self.MLmask = np.add(self.MLmask, self.Mask[:, :, selected_index]) self.intergrate_into_final_mask() self.add_rois_of_selected() else: self.intergrate_into_final_mask() # self.Mask_edit_viewItem.setImage(gray2rgb(self.MLtargetedImg)) def add_rois_of_selected(self): """ Using find_contours to get list of contour coordinates in the binary mask, and then generate polygon rois based on these coordinates. """ for selected_index in self.selected_ML_Index: contours = self.selected_cells_infor_dict['cell{}_verts'.format( str(selected_index))] # contours = find_contours(self.Mask[:,:,selected_index], 0.5) # Find iso-valued contours in a 2D array for a given level value. for n, contour in enumerate(contours): contour_coord_array = contours[n] #Swap columns contour_coord_array[:, 0], contour_coord_array[:, 1] = contour_coord_array[:, 1], contour_coord_array[:, 0].copy( ) #Down sample the coordinates otherwise it will be too dense. contour_coord_array_del = np.delete( contour_coord_array, np.arange(2, contour_coord_array.shape[0] - 3, 2), 0) self.selected_cells_infor_dict['cell{}_ROIitem'.format(str(selected_index))] = \ pg.PolyLineROI(positions=contour_coord_array_del, closed=True) self.Mask_edit_view.getView().addItem( self.selected_cells_infor_dict['cell{}_ROIitem'.format( str(selected_index))]) def update_mask(self): """ Regenerate the masks for MaskRCNN and free-hand added (in case they are changed), and show in imageview. !!!ISSUE: getLocalHandlePositions: moving handles changes the position read out, dragging roi as a whole doesn't. """ # Binary mask from ML detection if len(self.selected_ML_Index) > 0: # Delete items in dictionary that are not roi items roi_dict = self.selected_cells_infor_dict.copy() del_key_list = [] for key in roi_dict: print(key) if 'ROIitem' not in key: del_key_list.append(key) for key in del_key_list: del roi_dict[key] self.MLmask = ProcessImage.ROIitem2Mask( roi_dict, mask_resolution=(self.MLtargetedImg.shape[0], self.MLtargetedImg.shape[1])) # Binary mask of added rois self.addedROIitemMask = ProcessImage.ROIitem2Mask( self.roi_list_freehandl_added, mask_resolution=(self.MLtargetedImg.shape[0], self.MLtargetedImg.shape[1])) self.intergrate_into_final_mask() # if type(self.roi_list_freehandl_added) is list: # for ROIitem in self.roi_list_freehandl_added: # # ROIitem.sigHoverEvent.connect(lambda: self.show_roi_detail(ROIitem)) # # plt.figure() # plt.imshow(self.addedROIitemMask) # plt.show() # ============================================================================= # For free-hand rois # ============================================================================= def enable_free_hand(self): if self.enable_modify_MLmask_button.isChecked(): self.Mask_edit_view.enable_drawing(True) else: self.Mask_edit_view.enable_drawing(False) def add_freehand_roi(self, roi): # For drawwidget self.roi_list_freehandl_added.append(roi) def clear_edit_roi(self): """ Clean up all the free-hand rois. """ for roi in self.roi_list_freehandl_added: self.Mask_edit_view.getView().removeItem(roi) self.roi_list_freehandl_added = [] if len(self.selected_cells_infor_dict) > 0: # Remove all selected masks for roiItemkey in self.selected_cells_infor_dict: if 'ROIitem' in roiItemkey: self.Mask_edit_view.getView().removeItem( self.selected_cells_infor_dict[roiItemkey]) self.selected_cells_infor_dict = {} self.MLmask = np.zeros( (self.MLtargetedImg.shape[0], self.MLtargetedImg.shape[1])) self.intergrate_into_final_mask() def intergrate_into_final_mask(self): # Binary mask of added rois self.addedROIitemMask = ProcessImage.ROIitem2Mask( self.roi_list_freehandl_added, mask_resolution=(self.MLtargetedImg.shape[0], self.MLtargetedImg.shape[1])) #Display the RGB mask, ML mask plus free-hand added. self.Mask_edit_viewItem.setImage(gray2rgb(self.addedROIitemMask) * self.mask_color_multiplier + \ gray2rgb(self.MLmask) * self.mask_color_multiplier + gray2rgb(self.MLtargetedImg)) self.final_mask = self.MLmask + self.addedROIitemMask # In case the input image is 2048*2048, and it is resized to fit in MaskRCNN, need to convert back to original size for DMD tranformation. if self.final_mask.shape[0] != self.Rawimage.shape[ 0] or self.final_mask.shape[1] != self.Rawimage.shape[1]: self.final_mask = resize( self.final_mask, [self.Rawimage.shape[0], self.Rawimage.shape[1]], preserve_range=True).astype(self.final_mask.dtype) # self.final_mask = np.where(self.final_mask <= 1, self.final_mask, int(1)) plt.figure() plt.imshow(self.final_mask) plt.show() # ============================================================================= # For DMD transformation and mask generation # ============================================================================= def generate_transformed_mask(self): self.read_transformations_from_file() # self.transform_to_DMD_mask(laser = self.maskLaserComboBox.currentText(), dict_transformations = self.dict_transformations) target_laser = self.maskLaserComboBox.currentText() self.final_DMD_mask = self.finalmask_to_DMD_mask( laser=target_laser, dict_transformations=self.dict_transformations) plt.figure() plt.imshow(self.final_DMD_mask) plt.show() def emit_mask_contour(self): """Use find_contours to get a list of (n,2)-ndarrays consisting of n (row, column) coordinates along the contour, and then feed the list of signal:[list_of_rois, flag_fill_contour, contour_thickness, flag_invert_mode] to the receive_mask_coordinates function in DMDWidget. """ contours = find_contours(self.final_mask, 0.5) sig = [ contours, self.fillContourButton.isChecked(), self.thicknessSpinBox.value(), self.invertMaskButton.isChecked() ] self.signal_DMDcontour.emit(sig) def emit_mask(self): target_laser = self.maskLaserComboBox.currentText() final_DMD_mask_dict = {} final_DMD_mask_dict['camera-dmd-' + target_laser] = self.final_DMD_mask self.signal_DMDmask.emit(final_DMD_mask_dict) def read_transformations_from_file(self): try: with open( r'M:\tnw\ist\do\projects\Neurophotonics\Brinkslab\People\Xin Meng\Code\Python_test\DMDManager\Registration\transformation.txt', 'r') as json_file: self.dict_transformations = json.load(json_file) except: print( 'No transformation could be loaded from previous registration run.' ) return # def transform_to_DMD_mask(self, laser, dict_transformations, flag_fill_contour = True, contour_thickness = 1, flag_invert_mode = False, mask_resolution = (1024, 768)): # """ # Get roi vertices from all roi items and perform the transformation, and then create the mask for DMD. # """ # # #list of roi vertices each being (n,2) numpy array for added rois # if len(self.roi_list_freehandl_added) > 0: # self.addedROIitem_vertices = ProcessImage.ROIitem2Vertices(self.roi_list_freehandl_added) # #addedROIitem_vertices needs to be seperated to be inidividual (n,2) np.array # self.ROIitems_mask_transformed = ProcessImage.vertices_to_DMD_mask(self.addedROIitem_vertices, laser, dict_transformations, flag_fill_contour = True, contour_thickness = 1,\ # flag_invert_mode = False, mask_resolution = (1024, 768)) # # #Dictionary with (n,2) numpy array for clicked cells # if len(self.selected_cells_infor_dict) > 0: # #Convert dictionary to np.array # for roiItemkey in self.selected_cells_infor_dict: # #Each one is 'contours' from find_contour # if '_verts' in roiItemkey: # self.selected_cells_infor_dict[roiItemkey] # # self.MLitems_mask_transformed = ProcessImage.vertices_to_DMD_mask(self.selected_cells_infor_dict, laser, dict_transformations, flag_fill_contour = True, contour_thickness = 1,\ # flag_invert_mode = False, mask_resolution = (1024, 768)) # # if len(self.roi_list_freehandl_added) > 0: # self.final_DMD_mask = self.ROIitems_mask_transformed + self.MLitems_mask_transformed # self.final_DMD_mask[self.final_DMD_mask>1] = 1 # else: # self.final_DMD_mask = self.MLitems_mask_transformed # # return self.final_DMD_mask def finalmask_to_DMD_mask(self, laser, dict_transformations, flag_fill_contour=True, contour_thickness=1, flag_invert_mode=False, mask_resolution=(1024, 768)): """ Same goal as transform_to_DMD_mask, with input being the final binary mask and using find_contour to get all vertices and perform transformation, and then coordinates to mask. """ self.final_DMD_mask = ProcessImage.binarymask_to_DMD_mask(self.final_mask, laser, dict_transformations, flag_fill_contour = True, \ contour_thickness = 1, flag_invert_mode = False, mask_resolution = (1024, 768)) return self.final_DMD_mask def closeEvent(self, event): QtWidgets.QApplication.quit() event.accept() # def apply_mask(self, image, mask, color, alpha=0.5): # """Apply the given mask to the image. # """ # for c in range(3): # image[:, :, c] = np.where(mask == 1, # image[:, :, c] * # (1 - alpha) + alpha * color[c] * 255, # image[:, :, c]) # return image #%% # @run_in_thread def cam_snap(self): """Get a image from camera""" self.cam = CamActuator() self.cam.initializeCamera() exposure_time = self.CamExposureBox.value() self.Rawimage = self.cam.SnapImage(exposure_time) self.cam.Exit() print('Snap finished') self.MLtargetedImg_raw = self.Rawimage.copy() self.MLtargetedImg = self.convert_for_MaskRCNN(self.MLtargetedImg_raw) self.show_raw_image(self.MLtargetedImg) self.addedROIitemMask = np.zeros( (self.MLtargetedImg.shape[0], self.MLtargetedImg.shape[1])) self.MLmask = np.zeros( (self.MLtargetedImg.shape[0], self.MLtargetedImg.shape[1]))
class Fitter_Control_Widget(_gui.QWidget): def __init__(self, parent_graph, **kwargs): """ This widget is a control pannel for the fitter """ _gui.QWidget.__init__(self) _uic.loadUi(_os.path.join(_os.path.dirname(__file__),'Fitter.ui'), self) self.graph = parent_graph self.kwargs = kwargs self.fitTraces = dict() self._fitted_name_association = dict() self.prepareLatex() self.generateCtrls() #Build a quick function to remove an item by name from the signal list def removeItemByName(name): for i in range(self.signalSelect.count()): if self.signalSelect.itemText(i)==name: self.signalSelect.removeItem(i) self.signalSelect.removeItemByName = removeItemByName self.graph.traceAdded[str].connect(lambda x: self.signalSelect.addItem(x)) self.graph.traceRemoved[str].connect(lambda x: self.signalSelect.removeItemByName(x)) def prepareLatex(self): # Get window background color bg = self.palette().window().color() cl = (bg.redF(), bg.greenF(), bg.blueF()) #Get figure and make it the same color as background self.fig = _plt.figure() self.latexHolder = FigureCanvas(self.fig) self.latexHolder.setFixedHeight(60) self.matplotlib_container.addWidget(self.latexHolder) #self.fig = self.latexMPLholder.figure self.fig.set_edgecolor(cl) self.fig.set_facecolor(cl) def generateCtrls(self): #Fill in the signal select input items = list(self.graph) self.signalSelect.addItems(items) self.signalSelect.setCurrentIndex(len(items)-1) #Link fit buttons self.fitBtn.clicked.connect(self.fitData) self.guessBtn.clicked.connect(self.guessP) #Fill the combobox for fit function self.fitFct = fit.getAllFitFct() self.fitFctSelect.addItems(list(self.fitFct.keys())) self.fitFctSelect.currentIndexChanged.connect(self.generateFitTable) self.fitFctSelect.setCurrentIndex(list(self.fitFct.keys()).index('Gaussian')) #Begin/Stop continuous timer self.timer = _core.QTimer(self) self.timer.timeout.connect(self.fitData) self.contFitActive = False self.start_stop_btn.clicked.connect(self.toogleContFit) #Fill in the combobox for error method self.errorMethodSelect.addItems(fit.Generic_Fct.allowedErrorMethods) self.errorMethodSelect.setCurrentIndex(fit.Generic_Fct.allowedErrorMethods.index('subtract')) #Link the output btns self.calcOutBtn.clicked.connect(lambda: self.generateOutputTable()) self.exportOutputAllBtn.clicked.connect(lambda: self.exportOutputToClipboard(All=True)) self.exportOutputValBtn.clicked.connect(lambda: self.exportOutputToClipboard(All=False)) #Dynamically generate the fit variables table self.generateFitTable() def generateFitTable(self): """ Delete the current table and regenerate a new one based on the current fonction variables. """ #Fetch fct name and variables fctName = str(self.fitFctSelect.currentText()) fctVars = self.fitFct[fctName].getParamList() #Update Latex self.generateLatex() #Set the size of the table self.fitVariableTable.setRowCount(len(fctVars)) #Fill in the table self.fitVarInputs = dict() ri = 0 for var in fctVars: #Set variable name in collumn 0 self.fitVariableTable.setCellWidget(ri,0,_gui.QLabel(str(var))) #Set variable value in collumn 1 varValue = _gui.QDoubleSpinBox() varValue.setDecimals(16) varValue.setMaximum(1000000000000000) varValue.setMinimum(-1000000000000000) varValue.setValue(1.0) self.fitVariableTable.setCellWidget(ri,2,varValue) #Set the variable constant checkbox in collumn 2 varConst = _gui.QCheckBox() self.fitVariableTable.setCellWidget(ri,1,varConst) #Remember the widgets self.fitVarInputs[var] = dict() self.fitVarInputs[var]['value'] = varValue self.fitVarInputs[var]['const'] = varConst #Go to next row ri += 1 return def generateOutputTable(self, p=None): """ Delete the current table and regenerate a new one based on the current fonction variables. """ #Fetch fct name and variables fctName = str(self.fitFctSelect.currentText()) if p == None: #Read p p = dict() for var in list(self.fitVarInputs.keys()): p[var] = self.fitVarInputs[var]['value'].value() #Calculate output outputVal = self.fitFct[fctName].calcOutput(p) if outputVal == None: #No Output variables defined self.outputTable.setRowCount(0) return else: self.outputTable.setRowCount(len(outputVal)) #Fill in the table ri = 0 for val in list(outputVal.keys()): #Set variable name in collumn 0 self.outputTable.setCellWidget(ri,0,_gui.QLabel(str(val))) #Set variable value in collumn 1 outputLabel = _gui.QLabel(str(outputVal[val])) outputLabel.setTextInteractionFlags(_core.Qt.TextSelectableByMouse) self.outputTable.setCellWidget(ri,1,outputLabel) #Go to next row ri += 1 return #------------------------------------------------------------------------------ # Fit parameters and value #------------------------------------------------------------------------------ def fitData(self): """ Get all the variables and perform the desired fit """ #Grab the variables and constants fctVars = dict() fctConst = dict() for var in list(self.fitVarInputs.keys()): if self.fitVarInputs[var]['const'].isChecked(): fctConst[var] = self.fitVarInputs[var]['value'].value() else: fctVars[var] = self.fitVarInputs[var]['value'].value() #Get the name of the function fctName = str(self.fitFctSelect.currentText()) #Get the error method errorMethod = str(self.errorMethodSelect.currentText()) #Get the trace name trc =str(self.signalSelect.currentText()) #Fit the data xData, yData = self.graph.getRegionData(trc, transformed = self.transformed_Check.isChecked()) if len(fctVars) != 0: fitData, p = self.fitFct[fctName].performFit(fctVars, _np.array([xData, yData]), const=fctConst, errorMethod=errorMethod) else: p = fctConst #Gets the full fit data x = self.graph[trc].getData(transformed = self.transformed_Check.isChecked())[0] y = self.fitFct[fctName].evaluateFct(p, x) #Fill the tables self.setTableValue(p) self.generateOutputTable(p) #If Add the fit data if not present and update it if present if not trc in self._fitted_name_association: trace = self.graph.addTrace(trc+' Fit', **self.kwargs) self._fitted_name_association[trc] = fit_trc_name = trace.name self.fitTraces[fit_trc_name] = trace else: fit_trc_name = self._fitted_name_association[trc] self.fitTraces[fit_trc_name].setData(x,y) #Change transform if necessary if self.transformed_Check.isChecked(): def f(x,y): return x,y self.fitTraces[fit_trc_name].setTransform(f, transform_name = 'NoTransform') else: self.fitTraces[fit_trc_name].setTransform(self.graph[trc].transform, self.graph[trc].transform_name) def setTableValue(self, p): """ Set all variables in the table to their associated dictionnary value """ for var in list(p.keys()): if var in self.fitVarInputs: self.fitVarInputs[var]['value'].setValue(p[var]) def generateLatex(self): """ Generate and show the latex equation """ # Clear figure self.fig.clf() #Fetch fct name and variables fctName = str(self.fitFctSelect.currentText()) #Update Latex latex = self.fitFct[fctName].getLatex() # Set figure title self.fig.suptitle(latex, y = 0.5, x = 0.5, horizontalalignment='center', verticalalignment='center', size = 18) self.latexHolder.draw() #self.latexMPLholder.draw() def guessP(self): """ Guess and set the parameters """ #Fetch fct name and variables fctName = str(self.fitFctSelect.currentText()) #Data xData, yData = self.graph.getRegionData(self.signalSelect.currentText(), transformed = self.transformed_Check.isChecked()) #Guess the parametters p = self.fitFct[fctName].guessP(xData, yData) if p != None: #Set the parametters self.setTableValue(p) else: print("No Guessing algorithm defined for this function!") #------------------------------------------------------------------------------ # Other HELPER methods #------------------------------------------------------------------------------ def exportOutputToClipboard(self, All=True): app = _core.QCoreApplication.instance() exportStr = '' for i in range(self.outputTable.rowCount()): if All: exportStr += str(self.outputTable.cellWidget(i, 0).text()) exportStr += '\t' exportStr += str(self.outputTable.cellWidget(i, 1).text()) exportStr += '\n' app.clipboard().setText(exportStr) def toogleContFit(self): if self.contFitActive: self.timer.stop() self.start_stop_btn.setText("Start") else: self.timer.start(self.contFitTime.value()*1000.) self.start_stop_btn.setText("Stop") self.contFitActive = not self.contFitActive def _closeEvent(self, event): self.timer.stop() for trc in self.fitTraces: if trc in self.graph: self.graph.removeTrace(trc) self.parent().requestSelfDestroy.emit() def loadSettings(self, settingsObj = None, **kwargs): print('Loading Fitter...') if type(settingsObj) != _core.QSettings: print("No QSetting object was provided") else: self.restoreGeometry(settingsObj.value('Geometry')) lastFunctionUsed = str(settingsObj.value('FunctionName')) self.fitFctSelect.setCurrentIndex(list(self.fitFct.keys()).index(lastFunctionUsed)) return def saveSettings(self, settingsObj = None, **kwargs): print('Saving Fitter...') if type(settingsObj) != _core.QSettings: print("No QSetting object was provided") else: settingsObj.setValue('Geometry', self.saveGeometry()) settingsObj.setValue('FunctionName', str(self.fitFctSelect.currentText())) return
class MiniMap(QtGui.QWidget): """Shows the entire signal and allows the user to navigate through it. Provides an scrollable selector over the entire signal. Attributes: xmin: Selector lower limit (measured in h-axis units). xmax: Selector upper limit (measured in h-axis units). step: Selector length (measured in h-axis units). """ def __init__(self, parent, ax, record=None): super(MiniMap, self).__init__(parent) self.ax = ax self.xmin = 0.0 self.xmax = 0.0 self.step = 10.0 self.xrange = np.array([]) self.minimapFig = plt.figure() self.minimapFig.set_figheight(0.75) self.minimapFig.add_axes((0, 0, 1, 1)) self.minimapCanvas = FigureCanvas(self.minimapFig) self.minimapCanvas.setFixedHeight(64) self.minimapSelector = self.minimapFig.axes[0].axvspan(0, self.step, color='gray', alpha=0.5, animated=True) self.minimapSelection = self.minimapFig.axes[0].axvspan(0, self.step, color='LightCoral', alpha = 0.5, animated=True) self.minimapSelection.set_visible(False) self.minimapBackground = [] self.minimapSize = (self.minimapFig.bbox.width, self.minimapFig.bbox.height) self.press_selector = None self.playback_marker = None self.minimapCanvas.mpl_connect('button_press_event', self.onpress) self.minimapCanvas.mpl_connect('button_release_event', self.onrelease) self.minimapCanvas.mpl_connect('motion_notify_event', self.onmove) # Animation related attrs. self.background = None self.animated = False # Set the layout self.layout = QtGui.QVBoxLayout(self) self.layout.addWidget(self.minimapCanvas) # Animation related attributes self.parentViewer = parent # Set Markers dict self.markers = {} self.record = None if record is not None: self.set_record(record) def set_record(self, record, step): self.record = record self.step = step self.xrange = np.linspace(0, len(self.record.signal) / self.record.fs, num=len(self.record.signal), endpoint=False) self.xmin = self.xrange[0] self.xmax = self.xrange[-1] self.markers = {} ax = self.minimapFig.axes[0] ax.lines = [] formatter = FuncFormatter(lambda x, pos: str(datetime.timedelta(seconds=x))) ax.xaxis.set_major_formatter(formatter) ax.grid(True, which='both') # Set dataseries to plot xmin = self.xmin * self.record.fs xmax = self.xmax * self.record.fs pixel_width = np.ceil(self.minimapFig.get_figwidth() * self.minimapFig.get_dpi()) x_data, y_data = plotting.reduce_data(self.xrange, self.record.signal, pixel_width, xmin, xmax) # self._plot_data.set_xdata(x_data) # self._plot_data.set_ydata(y_data) ax.plot(x_data, y_data, color='black', rasterized=True) ax.set_xlim(self.xmin, self.xmax) plotting.adjust_axes_height(ax) # Set the playback marker self.playback_marker = PlayBackMarker(self.minimapFig, self) self.playback_marker.markers[0].set_animated(True) # Draw canvas self.minimapCanvas.draw() self.minimapBackground = self.minimapCanvas.copy_from_bbox(self.minimapFig.bbox) self.draw_animate() def onpress(self, event): self.press_selector = event xdata = round(self.get_xdata(event), 2) xmin = round(xdata - (self.step / 2.0), 2) xmax = round(xdata + (self.step / 2.0), 2) self.parentViewer._set_animated(True) self.set_selector_limits(xmin, xmax) def onrelease(self, event): self.press_selector = None # Finish parent animation self.parentViewer._set_animated(False) def onmove(self, event): if self.press_selector is not None: xdata = round(self.get_xdata(event), 2) xmin = round(xdata - (self.step / 2.0), 2) xmax = round(xdata + (self.step / 2.0), 2) self.set_selector_limits(xmin, xmax) def get_xdata(self, event): inv = self.minimapFig.axes[0].transData.inverted() xdata, _ = inv.transform((event.x, event.y)) return xdata def set_selector_limits(self, xmin, xmax): step = xmax - xmin if step >= self.xmax - self.xmin: xleft = self.xmin xright = self.xmax if xmin < self.xmin: xleft = self.xmin xright = self.step elif xmax > self.xmax: xleft = self.xmax - step xright = self.xmax else: xleft = xmin xright = xmax if (xleft, xright) != (self.minimapSelector.xy[1, 0], self.minimapSelector.xy[2, 0]): self.step = step self.minimapSelector.xy[:2, 0] = xleft self.minimapSelector.xy[2:4, 0] = xright self.ax.set_xlim(xleft, xright) self.draw_animate() else: self.parentViewer.draw() def get_selector_limits(self): return self.minimapSelector.xy[0, 0], self.minimapSelector.xy[2, 0] def draw(self): self.draw_animate() def draw_animate(self): size = self.minimapFig.bbox.width, self.minimapFig.bbox.height if size != self.minimapSize: self.minimapSize = size self.minimapCanvas.draw() self.minimapBackground = self.minimapCanvas.copy_from_bbox(self.minimapFig.bbox) self.minimapCanvas.restore_region(self.minimapBackground) self.minimapFig.draw_artist(self.minimapSelection) self.minimapFig.draw_artist(self.minimapSelector) self.minimapFig.draw_artist(self.playback_marker.markers[0]) for marker in self.markers.values(): self.minimapFig.draw_artist(marker) self.minimapCanvas.blit(self.minimapFig.bbox) def set_visible(self, value): self.minimapCanvas.setVisible(value) def get_visible(self): return self.minimapCanvas.isVisible() def set_selection_limits(self, xleft, xright): self.minimapSelection.xy[:2, 0] = xleft self.minimapSelection.xy[2:4, 0] = xright self.draw_animate() def set_selection_visible(self, value): self.minimapSelection.set_visible(value) self.draw_animate() def create_marker(self, key, position, **kwargs): if self.xmin <= position <= self.xmax: marker = self.minimapFig.axes[0].axvline(position, animated=True) self.markers[key] = marker self.markers[key].set(**kwargs) def set_marker_position(self, key, value): marker = self.markers.get(key) if marker is not None: if self.xmin <= value <= self.xmax: marker.set_xdata(value) def set_marker(self, key, **kwargs): marker = self.markers.get(key) if marker is not None: kwargs.pop("animated", None) # marker's animated property must be always true to be drawn properly marker.set(**kwargs) def delete_marker(self, key): marker = self.markers.get(key) if marker is not None: self.minimapFig.axes[0].lines.remove(marker) self.markers.pop(key)