def __init__(self, nifti_path, parent): self._nifti_path = nifti_path self._workspace_parent = parent try: self._nifti = nib.load(self._nifti_path) except FileNotFoundError: print('Error in path %s' % nifti_path) return self._array_data = self._nifti.get_data() # Nifti file does not show stable shape, sometimes as (num,x,y) and at times as (x,y,num) or (x,num,y) self._array_data = utils.shape_nifti_2_segtool(self._array_data) # normalize images self.frames = (self._array_data.astype(np.float64) / np.max(self._array_data)) * 255 # TODO: perform in separate thread self._contrasted_frames = np.zeros((10, self.frames.shape[0], self.frames.shape[1], self.frames.shape[2])) for i in range(1, 11): self._contrasted_frames[i-1] = contrast_change(i, self.frames) # index of current frame displayed self.frame_displayed_index = 0 # numpy array of frames with segmentation as binary images self._segmentation_array = None # perform segmentation algorithm in separate thread so that gui is not frozen self._segmentation_thread = Thread(target=self._workspace_parent.perform_segmentation) self._segmentation_thread.setDaemon(True) self.image_label = ImageLabel(self.frames, self._contrasted_frames, self._workspace_parent) # text representing scan's status: in Queue \ Processing \ Ready \ etc. self.status = '' # class which performs the segmentation process self._segment_worker = BrainSegment(self.frames) # what is currently displayed: USER MARKS / SEGMENTATION / CONVEX / BRAIN HALVES / CSF self.display_state = '' # volume calculated of different parts of the scan self._full_brain_volume = 0 self._brain_halves_volume = [0, 0] self._csf_volume = 0
def __init__(self, parent = None): """ Initialize DocumentWidget. """ QtGui.QWidget.__init__(self, parent) self.parent = parent self.Document = None self.CurrentPage = None self.Image = None self.highlights = None self.num_pages = 0 self.page = 0 self.offset = 0 self.ImgLabel = ImageLabel(self) self.ImgLabel.setAlignment(QtCore.Qt.AlignCenter) self.ImgPixmap = QtGui.QPixmap() self.scale = 1
class ScanFile: def __init__(self, nifti_path, parent): self._nifti_path = nifti_path self._workspace_parent = parent try: self._nifti = nib.load(self._nifti_path) except FileNotFoundError: print('Error in path %s' % nifti_path) return self._array_data = self._nifti.get_data() # Nifti file does not show stable shape, sometimes as (num,x,y) and at times as (x,y,num) or (x,num,y) self._array_data = utils.shape_nifti_2_segtool(self._array_data) # normalize images self.frames = (self._array_data.astype(np.float64) / np.max(self._array_data)) * 255 # TODO: perform in separate thread self._contrasted_frames = np.zeros((10, self.frames.shape[0], self.frames.shape[1], self.frames.shape[2])) for i in range(1, 11): self._contrasted_frames[i-1] = contrast_change(i, self.frames) # index of current frame displayed self.frame_displayed_index = 0 # numpy array of frames with segmentation as binary images self._segmentation_array = None # perform segmentation algorithm in separate thread so that gui is not frozen self._segmentation_thread = Thread(target=self._workspace_parent.perform_segmentation) self._segmentation_thread.setDaemon(True) self.image_label = ImageLabel(self.frames, self._contrasted_frames, self._workspace_parent) # text representing scan's status: in Queue \ Processing \ Ready \ etc. self.status = '' # class which performs the segmentation process self._segment_worker = BrainSegment(self.frames) # what is currently displayed: USER MARKS / SEGMENTATION / CONVEX / BRAIN HALVES / CSF self.display_state = '' # volume calculated of different parts of the scan self._full_brain_volume = 0 self._brain_halves_volume = [0, 0] self._csf_volume = 0 def save_numpy_polygon(self, dest_dir): ''' Save each marked point as a numpy array. All frames will be saved in a folder for this scan. :param dest_dir: folder to place sub-folder inside. ''' polygon_numpy = self.image_label.points_to_image() self.save_numpy_files_handler(polygon_numpy, dest_dir) def save_numpy_slices(self, dest_dir): ''' Save each frame as a numpy array. All frames will be saved in a folder for this scan. :param dest_dir: folder to place sub-folder inside. ''' self.save_numpy_files_handler(self.frames, dest_dir) def save_numpy_files_handler(self, array_data, dest_dir): base_nifti_name = self._nifti_path[self._nifti_path.rfind('\\')+1:] base_nifti_name = base_nifti_name.rstrip('.gz').rstrip('.nii') a = self._nifti_path[:self._nifti_path.rfind('\\')] b = a[a.rfind('\\')+1:] extra = b[:b.rfind('_')] filename = os.path.join(dest_dir, base_nifti_name) + '_' + extra + '_frame' + '1' tmp_filename = filename suffix = 0 while os.path.exists(tmp_filename + '.npz'): tmp_filename = filename + '_' + str(suffix) suffix += 1 suffix = ('_' + tmp_filename[tmp_filename.rfind('_')+1:]) if tmp_filename != filename else '' for i in range(1, array_data.shape[0] - 1): filename = os.path.join(dest_dir, base_nifti_name) + '_' + extra + '_frame' + str(i) + suffix # if os.path.exists(filename + '.npz'): # warning('{} was not saved, already exists.'.format(base_nifti_name)) # break np.savez(filename, array_data[i-1], array_data[i], array_data[i+1]) print("Saved file at {}".format(filename)) def load_image_label(self): self.image_label.activate_image() def run_segmentation(self): # do not run if status is not initial, cannot start a thread more than once # TODO: solve for a case one wants to remark and reperform segmentation if self.status == '': self.status = PROCESSING self._segmentation_thread.start() def perform_segmentation(self): # collect points marked by user all_points = self.image_label.shapes.all_points() # do not attempt segmentation if user did not mark enough frames frames_marked = 0 for items_list in all_points.values(): if items_list: frames_marked += 1 if frames_marked < 3: return None seeds = [] for frame_idx, frame_points in all_points.items(): if frame_points: for pos in frame_points: translated_pos = self.image_label.label_to_image_pos(pos) seeds.append((frame_idx, translated_pos.y(), translated_pos.x())) segmentation_array = self._segment_worker.segmentation_3d(self.frames, seeds) if segmentation_array is None: self.status = '' else: # store marks aside before setting segmentation self.image_label.shapes.store_marks() segmentation_array *= 255 self.set_segmentation(segmentation_array) return segmentation_array def volume(self, segmentation_array=None): ''' Calculate volume of segmentation, based on the voxel spacing of the nifti file. :param segmentation_array [numpy.ndarray] to calculate volume :return: [float] volume in mm. ''' if segmentation_array is None: segmentation_array = self.image_label.points_to_image() pixel_dims = self._nifti._header['pixdim'] if len(pixel_dims) == 8: num_pixels = np.sum(segmentation_array) return (num_pixels * pixel_dims[1] * pixel_dims[2] * pixel_dims[3]) / 1000 else: print('Error in calculating volume from Nifti Header.') return 0 def show_brain_halves(self): if self._segment_worker: if self.display_state == SEGMENTATION: # TODO maybe wasteful and unnecessary to calculate this each time? think about it self._segmentation_array = self.image_label.points_to_image() try: left_half, right_half = self._segment_worker.separate_to_two_brains(self._segmentation_array) self.image_label.set_brain_halves(left_half, right_half) left_brain, right_brain = self.image_label.points_to_image(True) left_volume, right_volume = self.volume(left_brain), self.volume(right_brain) self._workspace_parent.set_brain_halves_volume(left_volume, right_volume) self._brain_halves_volume = [left_volume, right_volume] self.display_state = HALVES except Exception as ex: print('error in separate_to_two_brains', ex) def show_segmentation(self): '''Show current segmentation over image label.''' if self.display_state == MARKS: return if self.display_state == SEGMENTATION: self._segmentation_array = self.image_label.points_to_image() if self.status == SEGMENTED: self.display_state = SEGMENTATION self.image_label.set_segmentation(self._segmentation_array) def show_convex(self): ''' Calculate convex for given segmentation of brain, and display it. ''' # save aside the given segmentation as the most updated one if self._segment_worker: if self.display_state == SEGMENTATION: self._segmentation_array = self.image_label.points_to_image() convex = self._segment_worker.flood_fill_hull(self._segmentation_array) self.image_label.set_segmentation(convex) self.display_state = CONVEX def show_csf(self): ''' Display segmentation of brain CSF only. ''' if self._segment_worker: if self.display_state == SEGMENTATION: self._segmentation_array = self.image_label.points_to_image() try: # todo need to send copy so that _segmentation_array is not changed? csf = self._segment_worker.get_csf_seg(self._segmentation_array) except Exception as ex: print("CSF computation error", ex) else: self.image_label.set_segmentation(csf) self._csf_volume = self.volume(csf) self._workspace_parent.set_csf_volume(self._csf_volume) self.display_state = CSF def show_quantization_segmentation(self, level): ''' Get segmentation after applying quantization stage with different quantum values, and display it. :param level: [int] the quantum value to be used, in proportion. ''' if self.display_state == SEGMENTATION: updated_seg = self.image_label.points_to_image() segmentation_array = self._segment_worker.get_quant_segment(level, updated_seg) self.set_segmentation(segmentation_array) def set_segmentation(self, segmentation_array): self.display_state = SEGMENTATION self.status = SEGMENTED self._segmentation_array = segmentation_array self.image_label.set_segmentation(segmentation_array) self.image_label.set_image(self.image_label.frames[self.image_label.frame_displayed_index]) self._full_brain_volume = self.volume() self._workspace_parent.set_brain_volume(self._full_brain_volume) def __str__(self): return self._nifti_path.split('/')[-1].split('.')[0] @property def brain_volume(self): return self._full_brain_volume
def init_label_frame(self): self.label_frame = ImageLabel('video') self.label_frame.setAlignment(Qt.AlignCenter) self.label_frame.setStyleSheet('border: 1px solid black') self.vbox_layout.addWidget(self.label_frame, 10)
class VideoWidget(QWidget): frame_updated = pyqtSignal(int) tube_annotated = pyqtSignal(dict) annotation_loaded = pyqtSignal(list) export_progress_updated = pyqtSignal(int) def __init__(self, parent=None, with_filename=True, with_slider=True, cache_capacity=500, max_fps=0): super(VideoWidget, self).__init__(parent) self.with_filename = with_filename self.with_slider = with_slider self.video = Video(cache_capacity=cache_capacity, max_fps=max_fps) self.annotation = Annotation() self.tube_id = 0 self.tracker = None self.sim_thr = 0.9 self.init_ui() self.installEventFilter(self) if self.with_slider: self.slider.sliderReleased.connect(self.on_slider_released) self.label_frame.bbox_added.connect(self.set_tracker) self.label_frame.bbox_deleted.connect(self.del_tracker) self.video.frame_updated.connect(self.update_frame) self.video.export_progress_updated.connect(self.update_export_progress) def init_ui(self): self.vbox_layout = QVBoxLayout() if self.with_filename: self.init_label_filename() self.init_label_frame() if self.with_slider: self.init_slider() self.setLayout(self.vbox_layout) # self.setFocusPolicy(Qt.StrongFocus) def init_label_filename(self): self.label_filename = QLabel('filename') self.label_filename.setAlignment(Qt.AlignCenter) self.vbox_layout.addWidget(self.label_filename, 1) def init_label_frame(self): self.label_frame = ImageLabel('video') self.label_frame.setAlignment(Qt.AlignCenter) self.label_frame.setStyleSheet('border: 1px solid black') self.vbox_layout.addWidget(self.label_frame, 10) def init_slider(self): self.slider = QSlider(Qt.Horizontal) self.slider.setRange(0, 1000) self.slider.setTickInterval(1) self.slider.setValue(0) self.slider.setEnabled(False) self.vbox_layout.addWidget(self.slider, 1) def eventFilter(self, object, event): if event.type() == QEvent.KeyPress: key = event.key() if key == Qt.Key_D: self.frame_forward() return True elif key == Qt.Key_A: self.frame_backward() return True elif key == Qt.Key_S: self.last_keyframe = self.cursor() self.new_tube() return True elif key == Qt.Key_Left: if self.status() == VideoStatus.play_backward: self.pause() elif self.video.status != VideoStatus.not_loaded: self.play_backward() return True elif key == Qt.Key_Right: if self.status() == VideoStatus.play_forward: self.pause() elif self.status() != VideoStatus.not_loaded: self.play_forward() return True elif key == Qt.Key_Space: self.pause() return True return False def frame_forward(self): if self.tracker is not None: ori_hist = color_hist(self.tracker.init_region, 16) if self.cursor() >= self.annotation.tube_end(self.tube_id): self.last_keyframe = self.cursor() cnt = 0 while cnt < 10: frame = self.video.frame_forward() self.update_frame(frame) if (self.tracker is None or self.cursor() < self.annotation.tube_end(self.tube_id)): break bbox = self.tracker.bbox hist = color_hist( frame.raw_img[bbox.left:bbox.right, bbox.top:bbox.bottom], 16) if compare_hist(hist, ori_hist) < self.sim_thr: break cnt += 1 def frame_backward(self): frame = self.video.frame_backward() self.update_frame(frame) def play_forward(self): self.video.play_forward() def play_backward(self): self.video.play_backward() def pause(self): self.video.pause() def jump_to_frame(self, frame_id): self.clear_tracker() frame = self.video.jump_to_frame(frame_id) self.update_frame(frame) def status(self): return self.video.status def cursor(self): return self.video.cursor def frame_cnt(self): return self.video.frame_cnt def current_frame(self): return self.video.current_frame() def new_tube(self): label = self.label_frame.bbox_label if label is not None: self.tube_id = self.annotation.next_tube_id self.annotation.add_tube(label, self.cursor()) self.label_frame.flash_reticle() self.label_frame.toggle_reticle(True) self.label_frame.is_new_tube = True def clear_tracker(self): self.tracker = None def reset_tube_id(self): self.tube_id = 0 def track(self, frame): frame_rect = BoundingBox(None, 0, 0, 0, self.video.width, self.video.height) bbox, score = self.tracker.update(frame) bbox = bbox.intersected(frame_rect) return bbox def adjust_track_bboxes(self, bbox): self.annotation.interpolate(self.tube_id, bbox, self.last_keyframe, self.cursor()) @pyqtSlot(VideoFrame) def update_frame(self, frame): # get bounding boxes of current tube and other tubes bboxes = dict() if (self.tracker is not None and self.video.is_forward() and self.cursor() > self.annotation.tube_end(self.tube_id)): bboxes['current_tube'] = self.track(frame) self.annotation.set_bbox(self.tube_id, self.cursor(), bboxes['current_tube']) else: bboxes['current_tube'] = self.annotation.get_bbox( self.tube_id, self.cursor()) bboxes['other_tubes'] = self.annotation.get_bboxes( self.cursor(), self.tube_id) # show the frame and corresponding bounding boxes self.label_frame.display(frame) self.label_frame.update_bboxes(bboxes) # update slider position if self.with_slider: self.slider.setValue( int(self.slider.maximum() * frame.id / self.frame_cnt())) # emit the frame id to the main window to update status bar self.frame_updated.emit(frame.id) @pyqtSlot(BoundingBox) def set_tracker(self, bbox): if self.tracker is not None and self.cursor() > self.last_keyframe + 1: self.adjust_track_bboxes(bbox) self.tracker = Tracker() self.tracker.start_track(self.current_frame(), bbox) self.annotation.set_bbox(self.tube_id, self.cursor(), bbox) @pyqtSlot() def del_tracker(self): self.clear_tracker() self.annotation.del_later_bboxes(self.tube_id, self.cursor()) self.annotation.save() tube_info = self.annotation.tube( self.tube_id).to_dict(with_bboxes=False) self.tube_annotated.emit(tube_info) self.reset_tube_id() @pyqtSlot() def on_slider_released(self): progress = self.slider.value() / self.slider.maximum() frame_id = max(int(round(self.frame_cnt() * progress)), 1) self.jump_to_frame(frame_id) @pyqtSlot(int) def jump_to_tube(self, tube_id): self.tube_id = tube_id self.jump_to_frame(self.annotation.tube_start(tube_id)) @pyqtSlot(int) def del_tube(self, tube_id): if self.tube_id == tube_id: self.tube_id = 0 self.annotation.del_tube(tube_id) self.annotation.save() @pyqtSlot(int, str) def change_tube_label(self, tube_id, label): self.annotation.tubes[tube_id].label = label self.annotation.save() @pyqtSlot(str) def update_bbox_label(self, label): self.label_frame.update_bbox_label(label) @pyqtSlot() def open_file(self): self.filename, _ = QFileDialog.getOpenFileName( self, 'Load video', '/home/kchen/data/youtube/selected/', 'Videos (*.mp4 *.avi *.mkv *.flv *.m4v)') if not self.filename: return if self.with_filename: self.label_filename.setText(os.path.basename(self.filename)) if self.with_slider: self.slider.setEnabled(True) self.video.load(self.filename) self.annotation.load(self.filename + '.annotation') self.annotation_loaded.emit(self.annotation.get_brief_info()) self.jump_to_frame(1) @pyqtSlot() def export_video(self): filename, _ = QFileDialog.getSaveFileName(self, 'Export video', './', 'Videos (*.avi)') t = threading.Thread(target=self.video.export, kwargs=dict(out_file=filename, annotation=self.annotation)) t.daemon = True t.start() @pyqtSlot(int) def update_export_progress(self, progress): self.export_progress_updated.emit(progress) @pyqtSlot() def save_annotation(self): self.annotation.save()
class VideoWidget(QWidget): frame_updated = pyqtSignal(int) tube_annotated = pyqtSignal(dict) annotation_loaded = pyqtSignal(list) export_progress_updated = pyqtSignal(int) def __init__(self, parent=None, with_filename=True, with_slider=True, cache_capacity=500, max_fps=0): super(VideoWidget, self).__init__(parent) self.with_filename = with_filename self.with_slider = with_slider self.video = Video(cache_capacity=cache_capacity, max_fps=max_fps) self.annotation = Annotation() self.tube_id = 0 self.tracker = None self.sim_thr = 0.9 self.init_ui() self.installEventFilter(self) if self.with_slider: self.slider.sliderReleased.connect(self.on_slider_released) self.label_frame.bbox_added.connect(self.set_tracker) self.label_frame.bbox_deleted.connect(self.del_tracker) self.video.frame_updated.connect(self.update_frame) self.video.export_progress_updated.connect(self.update_export_progress) def init_ui(self): self.vbox_layout = QVBoxLayout() if self.with_filename: self.init_label_filename() self.init_label_frame() if self.with_slider: self.init_slider() self.setLayout(self.vbox_layout) # self.setFocusPolicy(Qt.StrongFocus) def init_label_filename(self): self.label_filename = QLabel('filename') self.label_filename.setAlignment(Qt.AlignCenter) self.vbox_layout.addWidget(self.label_filename, 1) def init_label_frame(self): self.label_frame = ImageLabel('video') self.label_frame.setAlignment(Qt.AlignCenter) self.label_frame.setStyleSheet('border: 1px solid black') self.vbox_layout.addWidget(self.label_frame, 10) def init_slider(self): self.slider = QSlider(Qt.Horizontal) self.slider.setRange(0, 1000) self.slider.setTickInterval(1) self.slider.setValue(0) self.slider.setEnabled(False) self.vbox_layout.addWidget(self.slider, 1) def eventFilter(self, object, event): if event.type() == QEvent.KeyPress: key = event.key() if key == Qt.Key_D: self.frame_forward() return True elif key == Qt.Key_A: self.frame_backward() return True elif key == Qt.Key_S: self.last_keyframe = self.cursor() self.new_tube() return True elif key == Qt.Key_Left: if self.status() == VideoStatus.play_backward: self.pause() elif self.video.status != VideoStatus.not_loaded: self.play_backward() return True elif key == Qt.Key_Right: if self.status() == VideoStatus.play_forward: self.pause() elif self.status() != VideoStatus.not_loaded: self.play_forward() return True elif key == Qt.Key_Space: self.pause() return True return False def frame_forward(self): if self.tracker is not None: ori_hist = color_hist(self.tracker.init_region, 16) if self.cursor() >= self.annotation.tube_end(self.tube_id): self.last_keyframe = self.cursor() cnt = 0 while cnt < 10: frame = self.video.frame_forward() self.update_frame(frame) if (self.tracker is None or self.cursor() < self.annotation.tube_end(self.tube_id)): break bbox = self.tracker.bbox hist = color_hist( frame.raw_img[bbox.left: bbox.right, bbox.top: bbox.bottom], 16) if compare_hist(hist, ori_hist) < self.sim_thr: break cnt += 1 def frame_backward(self): frame = self.video.frame_backward() self.update_frame(frame) def play_forward(self): self.video.play_forward() def play_backward(self): self.video.play_backward() def pause(self): self.video.pause() def jump_to_frame(self, frame_id): self.clear_tracker() frame = self.video.jump_to_frame(frame_id) self.update_frame(frame) def status(self): return self.video.status def cursor(self): return self.video.cursor def frame_cnt(self): return self.video.frame_cnt def current_frame(self): return self.video.current_frame() def new_tube(self): label = self.label_frame.bbox_label if label is not None: self.tube_id = self.annotation.next_tube_id self.annotation.add_tube(label, self.cursor()) self.label_frame.flash_reticle() self.label_frame.toggle_reticle(True) self.label_frame.is_new_tube = True def clear_tracker(self): self.tracker = None def reset_tube_id(self): self.tube_id = 0 def track(self, frame): frame_rect = BoundingBox(None, 0, 0, 0, self.video.width, self.video.height) bbox, score = self.tracker.update(frame) bbox = bbox.intersected(frame_rect) return bbox def adjust_track_bboxes(self, bbox): self.annotation.interpolate(self.tube_id, bbox, self.last_keyframe, self.cursor()) @pyqtSlot(VideoFrame) def update_frame(self, frame): # get bounding boxes of current tube and other tubes bboxes = dict() if (self.tracker is not None and self.video.is_forward() and self.cursor() > self.annotation.tube_end(self.tube_id)): bboxes['current_tube'] = self.track(frame) self.annotation.set_bbox(self.tube_id, self.cursor(), bboxes['current_tube']) else: bboxes['current_tube'] = self.annotation.get_bbox( self.tube_id, self.cursor()) bboxes['other_tubes'] = self.annotation.get_bboxes( self.cursor(), self.tube_id) # show the frame and corresponding bounding boxes self.label_frame.display(frame) self.label_frame.update_bboxes(bboxes) # update slider position if self.with_slider: self.slider.setValue( int(self.slider.maximum() * frame.id / self.frame_cnt())) # emit the frame id to the main window to update status bar self.frame_updated.emit(frame.id) @pyqtSlot(BoundingBox) def set_tracker(self, bbox): if self.tracker is not None and self.cursor() > self.last_keyframe + 1: self.adjust_track_bboxes(bbox) self.tracker = Tracker() self.tracker.start_track(self.current_frame(), bbox) self.annotation.set_bbox(self.tube_id, self.cursor(), bbox) @pyqtSlot() def del_tracker(self): self.clear_tracker() self.annotation.del_later_bboxes(self.tube_id, self.cursor()) self.annotation.save() tube_info = self.annotation.tube(self.tube_id).to_dict(with_bboxes=False) self.tube_annotated.emit(tube_info) self.reset_tube_id() @pyqtSlot() def on_slider_released(self): progress = self.slider.value() / self.slider.maximum() frame_id = max(int(round(self.frame_cnt() * progress)), 1) self.jump_to_frame(frame_id) @pyqtSlot(int) def jump_to_tube(self, tube_id): self.tube_id = tube_id self.jump_to_frame(self.annotation.tube_start(tube_id)) @pyqtSlot(int) def del_tube(self, tube_id): if self.tube_id == tube_id: self.tube_id = 0 self.annotation.del_tube(tube_id) self.annotation.save() @pyqtSlot(int, str) def change_tube_label(self, tube_id, label): self.annotation.tubes[tube_id].label = label self.annotation.save() @pyqtSlot(str) def update_bbox_label(self, label): self.label_frame.update_bbox_label(label) @pyqtSlot() def open_file(self): self.filename, _ = QFileDialog.getOpenFileName( self, 'Load video', '/home/kchen/data/youtube/selected/', 'Videos (*.mp4 *.avi *.mkv *.flv *.m4v)') if not self.filename: return if self.with_filename: self.label_filename.setText(os.path.basename(self.filename)) if self.with_slider: self.slider.setEnabled(True) self.video.load(self.filename) self.annotation.load(self.filename + '.annotation') self.annotation_loaded.emit(self.annotation.get_brief_info()) self.jump_to_frame(1) @pyqtSlot() def export_video(self): filename, _ = QFileDialog.getSaveFileName( self, 'Export video', './', 'Videos (*.avi)') t = threading.Thread(target=self.video.export, kwargs=dict(out_file=filename, annotation=self.annotation)) t.daemon = True t.start() @pyqtSlot(int) def update_export_progress(self, progress): self.export_progress_updated.emit(progress) @pyqtSlot() def save_annotation(self): self.annotation.save()
def __init__(self, args): super().__init__() self.annotation_path = args.label_file self.scale_factor = args.scale self.annotaion_dict = dict() self.img_list = list() self.img_name = list() img_list = os.listdir(args.img_dir) for i in range(len(img_list)): if img_list[i].endswith('jpg') or img_list[i].endswith('png'): img_path = os.path.join(args.img_dir, img_list[i]) self.img_list.append(img_path) self.img_name.append(img_list[i]) self.img_num = len(self.img_list) self.img_id = 0 self.class_id = 1 if self.img_num == 0: return self._read_img() self.region_img_view = ImageLabel(self) self.region_img_view.setPixmap( QPixmap(cvimg_to_qtimg(self.current_img_resize))) self.region_img_view.setMouseTracking(True) self.region_img_view.update_img(self.current_img_resize, self.img_name[self.img_id]) self.button_width = 400 * self.scale_factor self.button_height = 60 * self.scale_factor self.pushButton1 = QPushButton(self) self.pushButton1.setText("Next Img") self.pushButton1.clicked.connect(self._down_func) self.pushButton2 = QPushButton(self) self.pushButton2.setText("Last Img") self.pushButton2.clicked.connect(self._up_func) self.pushButton3 = QPushButton(self) self.pushButton3.setText("Apply") self.pushButton3.clicked.connect(self._apply_func) self.pushButton4 = QPushButton(self) self.pushButton4.setText("Clean") self.pushButton4.clicked.connect(self._clean_func) self.pushButton5 = QPushButton(self) self.pushButton5.setText("Output") self.pushButton5.clicked.connect(self._output_func) if args.mode == "multi": self.class_text_height = 30 * self.scale_factor self.class_text_width = 100 * self.scale_factor self.class_text_label = QLabel(self) self.class_text_label.setAlignment(Qt.AlignCenter) self.class_text_label.setText('Class ID:') self.setup_class_id = QLineEdit(self) self.setup_class_id.setFocus() self.setup_class_id.setFocusPolicy(Qt.ClickFocus) self.setup_class_id.installEventFilter(self) self.setup_class_id.editingFinished.connect( self._setup_class_id_func) else: self.setup_class_id = QLabel(self) self.setup_class_id.setFocus() self.setup_class_id.setFocusPolicy(Qt.ClickFocus) self.setup_class_id.installEventFilter(self) self.setWindowTitle('Simple 2D Object Annotator') self._init_window()
class Window(QWidget): def __init__(self, args): super().__init__() self.annotation_path = args.label_file self.scale_factor = args.scale self.annotaion_dict = dict() self.img_list = list() self.img_name = list() img_list = os.listdir(args.img_dir) for i in range(len(img_list)): if img_list[i].endswith('jpg') or img_list[i].endswith('png'): img_path = os.path.join(args.img_dir, img_list[i]) self.img_list.append(img_path) self.img_name.append(img_list[i]) self.img_num = len(self.img_list) self.img_id = 0 self.class_id = 1 if self.img_num == 0: return self._read_img() self.region_img_view = ImageLabel(self) self.region_img_view.setPixmap( QPixmap(cvimg_to_qtimg(self.current_img_resize))) self.region_img_view.setMouseTracking(True) self.region_img_view.update_img(self.current_img_resize, self.img_name[self.img_id]) self.button_width = 400 * self.scale_factor self.button_height = 60 * self.scale_factor self.pushButton1 = QPushButton(self) self.pushButton1.setText("Next Img") self.pushButton1.clicked.connect(self._down_func) self.pushButton2 = QPushButton(self) self.pushButton2.setText("Last Img") self.pushButton2.clicked.connect(self._up_func) self.pushButton3 = QPushButton(self) self.pushButton3.setText("Apply") self.pushButton3.clicked.connect(self._apply_func) self.pushButton4 = QPushButton(self) self.pushButton4.setText("Clean") self.pushButton4.clicked.connect(self._clean_func) self.pushButton5 = QPushButton(self) self.pushButton5.setText("Output") self.pushButton5.clicked.connect(self._output_func) if args.mode == "multi": self.class_text_height = 30 * self.scale_factor self.class_text_width = 100 * self.scale_factor self.class_text_label = QLabel(self) self.class_text_label.setAlignment(Qt.AlignCenter) self.class_text_label.setText('Class ID:') self.setup_class_id = QLineEdit(self) self.setup_class_id.setFocus() self.setup_class_id.setFocusPolicy(Qt.ClickFocus) self.setup_class_id.installEventFilter(self) self.setup_class_id.editingFinished.connect( self._setup_class_id_func) else: self.setup_class_id = QLabel(self) self.setup_class_id.setFocus() self.setup_class_id.setFocusPolicy(Qt.ClickFocus) self.setup_class_id.installEventFilter(self) self.setWindowTitle('Simple 2D Object Annotator') self._init_window() def eventFilter(self, source, event): if (event.type() == QEvent.KeyPress and source is self.setup_class_id): if event.key() == Qt.Key_Up: self.pushButton2.click() if event.key() == Qt.Key_Down: self.pushButton1.click() return super(Window, self).eventFilter(source, event) def _setup_class_id_func(self): setup_class_id_str = self.setup_class_id.text() if setup_class_id_str != '': try: self.class_id = int(setup_class_id_str) self.region_img_view.update_class(self.class_id) except Exception as e: print(e) def _init_window(self): window_width = self.img_width + self.button_width + 20 if args.mode == "multi": other_height = 5 * (self.button_height + 10) + \ self.class_text_height + 10 self.setup_class_id.setGeometry( QRect(self.img_width + 20 + self.class_text_width, 10 + (10 + self.button_height) * 5, self.class_text_width, self.class_text_height)) self.class_text_label.setGeometry( QRect(self.img_width + 10, 10 + (10 + self.button_height) * 5, self.class_text_width, self.class_text_height)) else: other_height = 5 * (self.button_height + 10) window_height = max(self.img_height, other_height + 10) self.setGeometry(100, 100, window_width, window_height) self.region_img_view.setGeometry( QRect(0, 0, self.img_width, self.img_height)) self.pushButton1.setGeometry( QRect(self.img_width + 10, 10, self.button_width, self.button_height)) self.pushButton2.setGeometry( QRect(self.img_width + 10, 10 + (10 + self.button_height), self.button_width, self.button_height)) self.pushButton3.setGeometry( QRect(self.img_width + 10, 10 + (10 + self.button_height) * 2, self.button_width, self.button_height)) self.pushButton4.setGeometry( QRect(self.img_width + 10, 10 + (10 + self.button_height) * 3, self.button_width, self.button_height)) self.pushButton5.setGeometry( QRect(self.img_width + 10, 10 + (10 + self.button_height) * 4, self.button_width, self.button_height)) def _read_img(self): img_original = cv2.imread(self.img_list[self.img_id]) image_resize = cv2.resize(img_original, None, fx=self.scale_factor, fy=self.scale_factor) height, width, _ = image_resize.shape self.current_img_resize = image_resize self.current_img = img_original self.img_height = height self.img_width = width def _reshape_bbox(self, bbox_list): bbox_resize_list = list() for item in bbox_list: x = int(item['bbox'][0] / self.scale_factor) y = int(item['bbox'][1] / self.scale_factor) w = int(item['bbox'][2] / self.scale_factor) h = int(item['bbox'][3] / self.scale_factor) bbox_dict = {'bbox': [x, y, w, h], 'class': item['class']} bbox_resize_list.append(bbox_dict) return bbox_resize_list def _down_func(self): self.img_id += 1 if self.img_id == self.img_num: self.img_id = self.img_num - 1 self._read_img() self._init_window() self.region_img_view.update_img(self.current_img_resize, self.img_name[self.img_id]) self.region_img_view.setPixmap( QPixmap(cvimg_to_qtimg(self.current_img_resize))) self.region_img_view.update_class(self.class_id) self.setup_class_id.setFocus() def _up_func(self): self.img_id -= 1 if self.img_id == -1: self.img_id = 0 self._read_img() self._init_window() self.region_img_view.update_img(self.current_img_resize, self.img_name[self.img_id]) self.region_img_view.setPixmap( QPixmap(cvimg_to_qtimg(self.current_img_resize))) self.region_img_view.update_class(self.class_id) self.setup_class_id.setFocus() def _apply_func(self): image_local = self.current_img_resize.copy() for item in self.region_img_view.bbox_list: bbox = item['bbox'] class_id = item['class'] class_id_str = "class: " + str(class_id) cv2.rectangle(image_local, (bbox[0], bbox[1]), (bbox[0] + bbox[2], bbox[1] + bbox[3]), (0, 255, 0), 2) cv2.putText(image_local, class_id_str, (bbox[0], bbox[1] + 15), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0, 255, 0), 1) self.region_img_view.setPixmap(QPixmap(cvimg_to_qtimg(image_local))) bbox_resize_list = self._reshape_bbox(self.region_img_view.bbox_list) self.annotaion_dict[self.img_name[self.img_id]] = bbox_resize_list self.setup_class_id.setFocus() def _clean_func(self): self.region_img_view.clean_img() self.region_img_view.setPixmap( QPixmap(cvimg_to_qtimg(self.current_img_resize))) self.annotaion_dict[self.img_name[self.img_id]] = [] self.setup_class_id.setFocus() def _output_func(self): json_str = json.dumps(self.annotaion_dict, indent=1) with open(self.annotation_path, 'w') as file: file.write(json_str) self.setup_class_id.setFocus()
class DocumentWidget(QtGui.QWidget): """ DocumentWidget is the main component of MainWindow. It displays the PDF. """ def __init__(self, parent = None): """ Initialize DocumentWidget. """ QtGui.QWidget.__init__(self, parent) self.parent = parent self.Document = None self.CurrentPage = None self.Image = None self.highlights = None self.num_pages = 0 self.page = 0 self.offset = 0 self.ImgLabel = ImageLabel(self) self.ImgLabel.setAlignment(QtCore.Qt.AlignCenter) self.ImgPixmap = QtGui.QPixmap() self.scale = 1 def set_scale(self, event): """ Sets the scale with which the document will be redered. """ self.scale = event if self.Document is not None: self.update_image() def load_document(self, document): """ Load the document. Sets current page to the first page and the scale to 1. """ self.Document = popplerqt4.Poppler.Document.load(document) self.Document.setRenderHint(self.Document.Antialiasing) self.Document.setRenderHint(self.Document.TextAntialiasing) self.Document.setRenderHint(self.Document.TextHinting) self.CurrentPage = self.Document.page(self.page) self.num_pages = self.Document.numPages() self.set_scale(1) def change_page(self, event): """ Changes the page. """ if self.Document is not None: self.page = ((event - self.offset -1) % self.num_pages) #print 'Page %d.' % self.page self.CurrentPage = self.Document.page(self.page) self.update_image() self.parent.ensureVisible(0, 0) def fit_to_width_or_height(self, event): """ Changes the scale in such a way that it fits the width or the height of the window. width: event = 1 height: event = 2 """ if self.Document is not None: page_size = self.CurrentPage.pageSizeF() if event == 1: # 18 is window border, 4 is shadow width = self.parent.rect().width() - 18 - 6 self.scale = 72.0*width/(DPI_X * page_size.width()) else: # 2 is window border, 4 is shadow height = self.parent.rect().height() - 2 - 6 self.scale = 72.0*height/(DPI_Y * page_size.height()) self.update_image() def update_image(self): """ Updates Image and ImgLabel. """ if self.Document is not None: self.Image = self.CurrentPage.renderToImage(DPI_X*self.scale, DPI_Y*self.scale) self.ImgLabel.setPixmap(self.ImgPixmap.fromImage(self.Image)) background = QtGui.QImage(QtCore.QSize(self.Image.width() + 6, self.Image.height() + 6), self.Image.format()) painter = QtGui.QPainter() painter.begin(background) painter.fillRect(QtCore.QRect(0, 0, background.width(), background.height()), QtGui.QColor(60, 60, 60)) painter.drawImage(1, 1, self.Image) if PLATFORM == 'Windows': grey = QtGui.QColor(172, 168, 153) else: grey = QtGui.QColor(203, 201, 200) painter.fillRect(QtCore.QRect(0, self.Image.height()+2, 6, 4), grey) painter.fillRect(QtCore.QRect(self.Image.width()+ 2, 0, 4, 6), grey) if self.highlights: for rect, pg in self.highlights: if pg == self.page: (x, y) = self.ImgLabel.pt2px(QtCore.QSizeF(rect.x(), rect.y())) (w, h) = self.ImgLabel.pt2px(QtCore.QSizeF(rect.width(), rect.height())) painter.fillRect(QtCore.QRect(x + 1, y + 1, w, h), QtGui.QColor(255, 255, 0, 100)) painter.end() for note in self.ImgLabel.notes.values(): if note.page == self.page: #print note.uid x = note.pos.x() x *= self.scale * DPI_X / 72.0 y = note.pos.y() y *= self.scale * DPI_Y / 72.0 #print x,y painter = QtGui.QPainter() painter.begin(background) painter.drawImage(x, y, self.ImgLabel.noteImage) painter.end() self.ImgLabel.setPixmap(QtGui.QPixmap.fromImage(background))