Exemplo n.º 1
0
    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
Exemplo n.º 2
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
Exemplo n.º 3
0
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
Exemplo n.º 4
0
 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)
Exemplo n.º 5
0
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()
Exemplo n.º 6
0
 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)
Exemplo n.º 7
0
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()
Exemplo n.º 8
0
    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()
Exemplo n.º 9
0
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()
Exemplo n.º 10
0
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))