class Prototype(QWidget): def __init__(self, parent=None): super(Prototype, self).__init__(parent) # ================================================================================ # Window Setup # ================================================================================ self.setWindowTitle("Prototype Demo") self.setWindowFlags(Qt.WindowCloseButtonHint | Qt.WindowMinimizeButtonHint) # ================================================================================ # Variable Setup # ================================================================================ self.nissl_filename = config.NISSL_DEFAULT_FILE # ================================================================================ # UI Layout Preparation # ================================================================================ layout_main = QVBoxLayout() # ***** Vertical Layout (Main) # ***** Top Layout (Input Layout + Region Layout) layout_main.addLayout(self.get_layout_top()) # Set the main layout self.setLayout(layout_main) # ================================================================================ # Routines after UI Preparation # ================================================================================ self.refresh_image() # Set default image self.thread_match = MatchingThread(self) self.thread_match.startProgress.connect(self.on_thread_match_start) self.thread_match.updateProgress.connect(self.on_thread_match_update) self.thread_match.endProgress.connect(self.on_thread_match_end) ################################################################################## # UI Layout/Widget Routines ################################################################################## def get_layout_top(self): layout = QHBoxLayout() layout.addStretch(1) layout.addWidget(self.get_widget_input()) layout.addWidget(self.get_widget_region()) return layout def get_widget_input(self): canvas_box = QGroupBox("Input Image") layout = QVBoxLayout() # *** Button (Open File) self.btn_open = QPushButton("Open Nissl file") self.btn_open.clicked.connect(self.on_click_btn_open) layout.addWidget(self.btn_open) # *** Canvas (Input Image) self.canvas_input = Graph(self, width=1, height=8, dpi=500) self.canvas_input.corners_callback = self.on_canvas_input_corners_update layout.addWidget(self.canvas_input) canvas_box.setLayout(layout) return canvas_box def get_widget_region(self): canvas_box = QGroupBox("Selected Region") layout = QVBoxLayout() # ================================================================================ # ==================== Horizontal Layout (Matches Button + Add Entire Image Button) match_layout = QHBoxLayout() # *** Button (Find Best Match) self.btn_find_match = QPushButton("Find best matches") self.btn_find_match.clicked.connect(self.on_click_btn_find_match) self.btn_find_match.setEnabled(False) match_layout.addWidget(self.btn_find_match) self.btn_add_image = QPushButton("Add entire image") self.btn_add_image.clicked.connect(self.on_click_btn_add_image) match_layout.addWidget(self.btn_add_image) layout.addLayout(match_layout) # ==================== End Horizontal Layout (Match Layout) # ================================================================================ progress_layout = QHBoxLayout() # *** ProgressBar (Matching Completion) self.progressbar_match = QProgressBar() self.progressbar_match.setValue(0) progress_layout.addWidget(self.progressbar_match) layout.addLayout(progress_layout) # *** Label (Matching Status) self.label_match_status = QLabel( "Select a region you want to find a match for") layout.addWidget(self.label_match_status) # *** Canvas (Region Crops) self.canvas_region = Graph(self, width=5, height=5, dpi=100) self.canvas_region.is_interactive = False layout.addWidget(self.canvas_region) # *** Label (Ratio Test Distance) self.label_slider_ratio_test = QLabel("") layout.addWidget(self.label_slider_ratio_test) # *** Slider (Ratio Test Distance) self.slider_ratio_test = QSlider(Qt.Horizontal) self.slider_ratio_test.valueChanged.connect( self.on_slider_change_ratio_test) self.slider_ratio_test.setMinimum(0) self.slider_ratio_test.setMaximum(100) self.slider_ratio_test.setValue(int(config.DISTANCE_RATIO * 100)) self.slider_ratio_test.setTickPosition(QSlider.NoTicks) self.slider_ratio_test.setTickInterval(1) self.slider_ratio_test.setEnabled(False) layout.addWidget(self.slider_ratio_test) if (config.UI_ANGLE): # *** Label (Angle) self.label_angle = QLabel("Angle: 0") layout.addWidget(self.label_angle) # *** Slider (Angle) self.slider_angle = QSlider(Qt.Horizontal) self.slider_angle.valueChanged.connect(self.on_slider_change_angle) self.slider_angle.setMinimum(0) self.slider_angle.setMaximum(360) self.slider_angle.setTickPosition(QSlider.NoTicks) self.slider_angle.setTickInterval(1) self.slider_angle.setEnabled(False) layout.addWidget(self.slider_angle) if (config.UI_WARP): # *** Label (Warp Points) self.label_warp_points = QLabel("") layout.addWidget(self.label_warp_points) # *** Slider (Warp Points) self.slider_warp_points = QSlider(Qt.Horizontal) self.slider_warp_points.valueChanged.connect( self.on_slider_change_warp_points) self.slider_warp_points.setMinimum(3) self.slider_warp_points.setMaximum(50) self.slider_warp_points.setTickPosition(QSlider.NoTicks) self.slider_warp_points.setTickInterval(1) self.slider_warp_points.setValue(5) self.slider_warp_points.setEnabled(False) layout.addWidget(self.slider_warp_points) # *** Label (Warp Disp Min) self.label_warp_disp_min = QLabel("Min Displacement: ") layout.addWidget(self.label_warp_disp_min) # *** Label (Warp Disp Max) self.label_warp_disp_max = QLabel("Max Displacement: ") layout.addWidget(self.label_warp_disp_max) from qrangeslider import QRangeSlider self.slider_warp_disp = QRangeSlider() self.slider_warp_disp.setMin(0) self.slider_warp_disp.setMax(50) self.slider_warp_disp.setRange(1, 5) self.slider_warp_disp.setEnabled(False) layout.addWidget(self.slider_warp_disp) self.btn_warp = QPushButton("Warp") self.btn_warp.clicked.connect(self.on_click_btn_warp) self.btn_warp.setEnabled(False) layout.addWidget(self.btn_warp) self.btn_reset = QPushButton("Reset") self.btn_reset.clicked.connect(self.on_click_btn_reset) self.btn_reset.setEnabled(False) layout.addWidget(self.btn_reset) canvas_box.setLayout(layout) return canvas_box ################################################################################## # Slider Events ################################################################################## def on_slider_change_ratio_test(self): new_ratio = float(self.slider_ratio_test.value() / 100.0) config.DISTANCE_RATIO = new_ratio self.label_slider_ratio_test.setText("Distance Ratio Test: " + str(config.DISTANCE_RATIO)) def on_slider_change_angle(self): angle = self.slider_angle.value() self.label_angle.setText("Angle: " + str(angle)) if self.canvas_region.im is not None: import scipy.misc im = scipy.misc.imrotate(self.region, angle) self.canvas_region.imshow(im) def on_slider_change_warp_points(self): points = self.slider_warp_points.value() self.label_warp_points.setText("Warp Points: " + str(points)) def on_slider_change_warp_min(self): pass def on_slider_change_warp_max(self): pass ################################################################################## # Class Functions ################################################################################## def refresh_image(self): im = util.im_read(self.nissl_filename) self.canvas_input.imshow(im) def set_im_region(self, im_region): logger.info("Image Region Shape: {0}", im_region.shape) w, h, c = im_region.shape # Reduce the size of images to a reasonable range #import scipy.misc as misc #reduction_percent = int(config.RESIZE_WIDTH/w * 100) #im_region = misc.imresize(im_region, reduction_percent) #logger.info("Resized region to {0}", im_region.shape) # Check if we should save the region for testing if config.UI_SAVE_REGION: util.im_write('region.jpg', im_region) # Save the original selection for actual matching self.region = im_region # Check if the user would like to see the region keypoints if config.UI_SHOW_KP: kp, des = sift.extract_sift(im_region) im_region = cv2.drawKeypoints( im_region, kp, None, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) self.canvas_region.imshow(im_region) self.canvas_input.clear_corners() self.btn_find_match.setEnabled(True) self.slider_ratio_test.setEnabled(True) if config.UI_WARP: self.btn_warp.setEnabled(True) self.btn_reset.setEnabled(True) self.slider_warp_points.setEnabled(True) self.slider_warp_disp.setEnabled(True) if config.UI_ANGLE: self.slider_angle.setEnabled(True) ################################################################################## # Canvas Events ################################################################################## def on_canvas_input_corners_update(self): count = len(self.canvas_input.corners) if count == 4: x, y = [], [] for corner in self.canvas_input.corners: # Numpy slicing uses integers x.append(corner[0].astype(np.uint64)) y.append(corner[1].astype(np.uint64)) x1, x2, y1, y2 = min(x), max(x), min(y), max(y) im_region = self.canvas_input.im[y1:y2, x1:x2].copy() self.set_im_region(im_region) # Redraw corner scatterplot return True ################################################################################## # Button Events ################################################################################## def on_click_btn_find_match(self): self.thread_match.set_im(self.region) self.thread_match.start() def on_click_btn_add_image(self): self.set_im_region(self.canvas_input.im.copy()) def on_click_btn_warp(self): min_disp = self.slider_warp_disp.getRange()[0] max_disp = self.slider_warp_disp.getRange()[1] im_warp = warping.warp(self.canvas_region.im, 5, min_disp, max_disp) self.canvas_region.imshow(im_warp) def on_click_btn_reset(self): self.set_im_region(self.region) def on_click_btn_open(self): new_filename, extra = QFileDialog.getOpenFileName( self, 'Open file', config.NISSL_DIR, "Image files (*.jpg *.png)") if new_filename: self.nissl_filename = new_filename self.refresh_image() ################################################################################## # Thread Events ################################################################################## def on_thread_match_start(self, total): self.progressbar_match.setMaximum(total) self.canvas_input.is_interactive = False self.btn_open.setEnabled(False) self.btn_find_match.setEnabled(False) self.btn_add_image.setEnabled(False) self.slider_ratio_test.setEnabled(False) if config.UI_ANGLE: self.slider_angle.setEnabled(False) # Timing timing.stopwatch() def on_thread_match_update(self, index): self.progressbar_match.setValue(index) self.label_match_status.setText("Matching with plate " + str(index)) def on_thread_match_end(self, matches): timing.stopwatch("Matching Time: ") self.canvas_input.is_interactive = True self.btn_open.setEnabled(True) self.btn_find_match.setEnabled(True) self.btn_add_image.setEnabled(True) self.slider_ratio_test.setEnabled(True) if config.UI_ANGLE: self.slider_angle.setEnabled(True) if len(matches) <= 0: self.label_match_status.setText("Didn't find a good match.") else: self.label_match_status.setText("Found " + str(len(matches)) + " possible matches") results_diag = ResultsDialog(self.nissl_filename, matches, self) results_diag.show()
class BinaryPercentileTransformation(Transformation): def __init__(self): super(BinaryPercentileTransformation).__init__() self.col_ids = [] self.name = "Binary (Percentile)" def add_widgets(self, layout, owner, controller): suffix_label = QLabel("Column Suffix", owner) self.suffix_box = QLineEdit(owner) transform_cols_label = QLabel("Columns to transform", owner) self.transformation_cols_box = ui_objects.ColumnList(owner, controller) perc_label = QLabel("Select quantile range to flag (inclusive)", owner) self.perc_slider = QRangeSlider(owner) layout.addWidget(suffix_label) layout.addWidget(self.suffix_box) layout.addWidget(transform_cols_label) layout.addWidget(self.transformation_cols_box) layout.addWidget(perc_label) layout.addWidget(self.perc_slider) def add_summary_widgets(self, box, controller): box.transformation = self box.tran_label.setText("Binary (Percentile)") box.lower_perc_label = QLabel("Lower Percentile:", box) box.lower_perc_label.setFont(controller.NORMAL_FONT) box.lower_perc = QLabel(str(self.lower_perc), box) box.lower_perc.setFont(controller.SMALL_FONT) box.upper_perc_label = QLabel("Upper Percentile:", box) box.upper_perc_label.setFont(controller.NORMAL_FONT) box.upper_perc = QLabel(str(self.upper_perc), box) box.upper_perc.setFont(controller.SMALL_FONT) box.transformation_cols_label = QLabel("Columns transformed:", box) box.transformation_cols_label.setFont(controller.NORMAL_FONT) box.transformation_cols = QListWidget(box) box.transformation_cols.addItems(self.col_ids) box.content_layout.addWidget(box.lower_perc_label) box.content_layout.addWidget(box.lower_perc) box.content_layout.addWidget(box.upper_perc_label) box.content_layout.addWidget(box.upper_perc) box.content_layout.addWidget(box.transformation_cols_label) box.content_layout.addWidget(box.transformation_cols) box.setFixedHeight(500) box.setFixedWidth(500) def validate_inputs(self, owner): self.suffix = self.suffix_box.text() self.transform_cols = [ x.text() for x in self.transformation_cols_box.selectedItems() ] self.lower_perc = self.perc_slider.getRange()[0] self.upper_perc = self.perc_slider.getRange()[1] if self.suffix == "": QMessageBox.about( owner, "Warning", "Please provide a valid suffix for the resulting column(s).") return False if len(self.transform_cols) == 0: QMessageBox.about( owner, "Warning", "Please select at least one column to transform.") return False return True def create_transformation(self, df): for col in self.transform_cols: col_name = self.generate_column_name(df, col + "_" + self.suffix) self.col_ids.append(col_name) numeric_col = pd.to_numeric(df[col], errors="coerce") lower_q = np.nanquantile(numeric_col, self.lower_perc / 100) upper_q = np.nanquantile(numeric_col, self.upper_perc / 100) new_col = np.where( np.logical_and(numeric_col >= lower_q, numeric_col <= upper_q), 1, 0) new_col = np.where(new_col == np.nan, np.nan, new_col) df[col_name] = new_col return df
class RangesliderZmq(QtGui.QWidget): def __init__(self): #app = QtGui.QApplication(sys.argv) super().__init__() self.gesture_dict = GestureDict() self.port = 5556 logging.basicConfig(level=logging.DEBUG, format='%(message)s') self.initUI() #sys.exit(app.exec_()) def initUI(self): port = self.port self.create_socket(port) range_label = QtGui.QLabel('events') self.range_events = QRangeSlider() self.range_events.show() self.range_events.setFixedWidth(300) self.range_events.setFixedHeight(36) self.range_events.setMin(0) self.range_events.setMax(4) self.range_events.setRange(0, 1) self.range_events.startValueChanged.connect( lambda: self.keep_slider_min(self.range_events)) hbox_events = QtGui.QHBoxLayout() hbox_events.addWidget(range_label) hbox_events.addWidget(self.range_events) self.textbox = QtGui.QLineEdit() self.update_btn = QtGui.QPushButton("update") self.update_btn.clicked.connect(lambda: self.button_click(port)) self.update_btn.setFixedWidth(100) hbox = QtGui.QHBoxLayout() hbox.addWidget(self.update_btn) magnitude_label = QtGui.QLabel('magnitude in g/10') self.range_magnitude = QRangeSlider() self.range_magnitude.show() self.range_magnitude.setFixedWidth(300) self.range_magnitude.setFixedHeight(36) self.range_magnitude.setMin(20) self.range_magnitude.setMax(80) self.range_magnitude.setRange(20, 30) hbox_magnitude = QtGui.QHBoxLayout() hbox_magnitude.addWidget(magnitude_label) hbox_magnitude.addWidget(self.range_magnitude) self.filter_length = QRangeSlider() self.filter_length.show() self.filter_length.setFixedWidth(300) self.filter_length.setFixedHeight(36) self.filter_length.setMin(0) self.filter_length.setMax(250) self.filter_length.setRange(0, 100) self.filter_length.startValueChanged.connect( lambda: self.keep_slider_min(self.filter_length)) filter_length_label = QtGui.QLabel('filter length in samples') hbox_length = QtGui.QHBoxLayout() hbox_length.addWidget(filter_length_label) hbox_length.addWidget(self.filter_length) self.message_label = QtGui.QLabel("messages will be here") self.exit_btn = QtGui.QPushButton('exit') self.exit_btn.clicked.connect( lambda: self.exit_click(self.socket, self.context, port)) vbox = QtGui.QVBoxLayout() vbox.addStretch(1) vbox.addLayout(hbox_events) vbox.addLayout(hbox_magnitude) vbox.addLayout(hbox_length) vbox.addWidget(self.message_label) vbox.addWidget(self.update_btn) vbox.addLayout(hbox) vbox.addWidget(self.exit_btn) self.setLayout(vbox) self.setGeometry(300, 300, 300, 150) self.setWindowTitle('rangesliders') self.show() @QtCore.pyqtSlot() def button_click(self, port): ''' handle button click event ''' try: self.update_gesture_dict() message = self.gesture_dict.make_json() logging.info('rangeslider sending message {}'.format(message)) self.socket.send_json(message, flags=zmq.NOBLOCK) except zmq.error.Again as e: logging.info('no receiver for the message: {}'.format(e)) # restart the socket if nothing to receive the message # if receiver closed, the socket needs to be restarted self.close_socket() self.create_socket(port) def close_socket(self): ''' close the socket and context ''' self.socket.close() self.context.term() def create_socket(self, port): ''' create a socket using pyzmq with PAIR context ''' self.context = zmq.Context() self.socket = self.context.socket(zmq.PAIR) self.socket.setsockopt(zmq.LINGER, 0) self.socket.bind("tcp://*:%s" % port) stream_pair = zmqstream.ZMQStream(self.socket) stream_pair.on_recv(self.process_message) def exit_click(self, socket, context, port): ''' handle exit button click ''' socket.close() context.term() sys.exit() def filter_length_change(self): ''' filter length slider has changed ''' length = self.filter_length.value() self.set_message_label('filter_length: {}'.format(length)) def keep_slider_min(self, slider): ''' keep the slider length minimum as one ''' try: slider.setStart(0) except RuntimeError as e: pass self.set_message_label('cannot change this') def process_message(self, msg): time = datetime.now() time = time.strftime('%H:%M:%S') text = ('{}: {}'.format(time, msg)) self.message_label.setText(text) def set_message_label(self, text): self.message_label.setText(text) def timer_timeout(self): ''' handle the QTimer timeout ''' try: msg = self.socket.recv(flags=zmq.NOBLOCK).decode() self.process_message(msg) except zmq.error.Again as e: return def update_gesture_dict(self): ''' get status of all the rangesliders into GestureDict object ''' magnitude_min, magnitude_max = self.range_magnitude.getRange() filter_length = self.filter_length.end() events = self.range_events.end() self.gesture_dict.update_dict(magnitude_min=magnitude_min, \ magnitude_max=magnitude_max/10, events=events, \ filter_length=filter_length)