class OTModuleSelectComponent(OTModulePlugin,TypeBWVideoPipe):
	

	def __init__(self, name):
		icon_path = tools.getFileInSameDirectory(__file__, 'iconsubbg.jpg')
		TypeBWVideoPipe.__init__(self)
		OTModulePlugin.__init__(self, name,  iconFile=icon_path)

		self._video 		 = ModuleConnection("Video", connecting=TypeComponentsVideoPipe)
		self._player 		 = ControlPlayer("Video player")
		self._colorComponent = ControlCombo("Component")
		
		self._colorComponent.addItem("A", 0)
		self._colorComponent.addItem("B", 1)
		self._colorComponent.addItem("C", 2)
		self._colorComponent.valueUpdated = self.refreshValue
		self._video.changed 		  	  = self.newVideoInputChoosen

		self._formset = [ 
				'_video',
				'_colorComponent',
				"_player",
			]
		
	def refreshValue(self, value): self._player.refresh()


	def newVideoInputChoosen(self):
		ModuleConnection.changed(self._video)
		value = self._video.value
		if value:
			self.open(value)
			self._player.value = self
			print value
			
	def read(self):
		res, imgs = self._video.value.read()
		return res, imgs[self._colorComponent.value]
class BaseModule(BaseWidget):
    """Application form"""
    def __init__(self):
        global conf
        conf += 'pythonvideoannotator.resources'  # Resources can only be loaded after pyqt is running

        super(BaseModule, self).__init__('Video annotation editor')

        self._project = Project(parent=self)
        Dialog.project = self._project

        self._player = ControlPlayer("Player")
        self._time = ControlEventTimeline('Time')
        self._dock = ControlDockWidget("Timeline",
                                       side='bottom',
                                       order=1,
                                       margin=5)

        self.formset = ['_player']

        self._dock.value = self._time
        self._player.process_frame_event = self.process_frame_event
        self._player.click_event = self.on_player_click_event
        self._time.key_release_event = self.__timeline_key_release_event

        self.load_order = []

        self.mainmenu.insert(
            0, {
                'File': [{
                    'Open': self.__open_project_event,
                    'icon': conf.ANNOTATOR_ICON_OPEN
                }, '-', {
                    'Save': self.__save_project_event,
                    'icon': conf.ANNOTATOR_ICON_SAVE
                }, {
                    'Save as': self.__save_project_as_event,
                    'icon': conf.ANNOTATOR_ICON_SAVE
                }, '-', {
                    'Exit': QApplication.closeAllWindows,
                    'icon': conf.ANNOTATOR_ICON_EXIT
                }]
            })
        self.mainmenu.insert(1, {'Modules': []})
        self.mainmenu.insert(2, {'Windows': []})

    ######################################################################################
    #### FUNCTIONS #######################################################################
    ######################################################################################

    def init_form(self):
        super(BaseModule, self).init_form()

        if conf.CHART_FILE_PATH: self._time.import_chart(*conf.CHART_FILE_PATH)
        if conf.PROJECT_PATH: self.load_project(conf.PROJECT_PATH)

    ######################################################################################
    #### IO FUNCTIONS ####################################################################
    ######################################################################################

    def save(self, data, project_path=None):
        self._project.save(data, project_path)
        return data

    def load(self, data, project_path=None):
        self._project.load(data, project_path)

    def save_project(self, project_path=None):
        try:
            if project_path is None:
                project_path = QFileDialog.getExistingDirectory(
                    self, "Select the project directory")

            if project_path is not None and str(project_path) != '':
                project_path = str(project_path)
                self.save({}, project_path)
        except Exception as e:
            QMessageBox.critical(self, "Error", str(e))

    def load_project(self, project_path=None):
        if project_path is None:
            project_path = QFileDialog.getExistingDirectory(
                self, "Select the project directory")
        if project_path is not None and str(project_path) != '':
            self.load({}, str(project_path))

    ######################################################################################
    #### EVENTS ##########################################################################
    ######################################################################################

    def on_player_click_event(self, event, x, y):
        """
		Code to select a blob with the mouse
		"""
        super(VideoAnnotationEditor, self).on_player_click_event(event, x, y)
        self._player.refresh()

    def process_frame_event(self, frame):
        """
		Function called before render each frame
		"""
        return frame

    def __open_project_event(self):
        self.load_project()

    def __save_project_event(self):
        self.save_project(self._project.directory)

    def __save_project_as_event(self):
        self.save_project()

    def __timeline_key_release_event(self, event):
        """
		Control video playback using the space bar to Play/Pause
		"""
        if event.key() == QtCore.Qt.Key_Space:
            self._player.stop(
            ) if self._player.is_playing else _player._video.play()

    ######################################################################################
    #### PROPERTIES ######################################################################
    ######################################################################################

    @property
    def timeline(self):
        return self._time

    @property
    def player(self):
        return self._player

    @property
    def video(self):
        return self._player.value

    @video.setter
    def video(self, value):
        self._player.value = value
        self._player.enabled = value is not None
        if value:
            self._time.max = self._player.max

    @property
    def project(self):
        return self._project
class GeometryManualDesigner(BaseWidget):
    def __init__(self, title, parent=None):
        super(GeometryManualDesigner, self).__init__(title, parent_win=parent)

        self._threshold_win = None
        self._start_point = None
        self._end_point = None

        self._selected_poly = None
        self._selected_point = None

        self._video = ControlFile("Video file")
        self._player = ControlPlayer("Video")
        self._remove = ControlButton("Remove")
        self._square = ControlButton("Square", checkable=True)
        self._circle = ControlButton("Circle", checkable=True)
        self._threshold = ControlButton("Threshold")
        self._export = ControlButton("Export")
        self._import = ControlButton("Import")
        self._polygons = ControlList('Polygons')

        self._apply = ControlButton('Apply')

        self._formset = [
            '_video', "_player",
            ("_square", "_circle", "_threshold", " ", "_remove", " ",
             "_export", "_import"), "=", "_polygons", '_apply'
        ]

        self._video.changedchanged_event = self.videoSelected
        self._square.value = self.square_toggle
        self._circle.value = self.circle_toggle
        self._remove.value = self.remove_clicked
        self._export.value = self.export_clicked
        self._import.value = self.import_clicked
        self._threshold.value = self.threshold_btn_click

        self._player.drag_event = self.on_player_drag_in_video_window
        self._player.end_drag_event = self.on_player_end_drag_in_video_window
        self._player.click_event = self.on_player_click_in_video_window
        self._player.double_click_event = self.on_player_double_click_in_video_window
        self._player.process_frame_event = self.process_frame
        self._player.key_release_event = self.on_player_key_release

        self._apply.hide()

    def on_player_key_release(self, event):
        if event.key() == QtCore.Qt.Key_Delete:
            if self._selected_poly != None and self._selected_point != None:
                poly = self._polygons.get_value(1, self._selected_poly)
                try:
                    points = list(eval(poly))
                    p = points.pop(self._selected_point)
                    self._polygons.set_value(1, self._selected_poly,
                                             str(points)[1:-1])
                    if not self._player.is_playing: self._player.refresh()
                except:
                    pass

    def export_clicked(self):
        filename = str(QFileDialog.getSaveFileName(self, 'Choose a file', ''))
        if filename != "":
            output = open(filename, 'w')
            for values in self._polygons.value:
                output.write((';'.join(values) + '\n'))
            output.close()

    def import_clicked(self):
        filename = str(QFileDialog.getOpenFileName(self, 'Choose a file', ''))
        if filename != "":
            infile = open(filename, 'r')
            polygons = []
            for line in infile:
                values = line.split(';')
                name = values[0]
                poly = values[1]
                polygons.append((name, poly))
            self._polygons.value += polygons

    def process_frame(self, frame):
        rows = self._polygons.value
        for objIndex, obj in enumerate(rows):
            points = eval(obj[1])
            cv2.polylines(frame, [np.array(points, np.int32)],
                          True, (0, 255, 0),
                          2,
                          lineType=cv2.LINE_AA)
            for pointIndex, point in enumerate(points):
                if self._selected_point == pointIndex and objIndex == self._selected_poly:
                    cv2.circle(frame, point, 4, (0, 0, 255), 2)
                else:
                    cv2.circle(frame, point, 4, (0, 255, 0), 2)

        if self._start_point and self._end_point:
            if self._square.checked:
                cv2.rectangle(frame, self._start_point, self._end_point,
                              (233, 44, 44), 2)
            elif self._circle.checked and self._end_point[
                    0] > self._start_point[0] and self._end_point[
                        1] > self._start_point[1]:
                width = self._end_point[0] - self._start_point[0]
                height = self._end_point[1] - self._start_point[1]
                center = (self._start_point[0] + width / 2,
                          self._start_point[1] + height / 2)

                cv2.ellipse(frame, (center, (width, height), 0), (233, 44, 44),
                            2)

        return frame

    def selectPoint(self, x, y):
        rows = self._polygons.value
        for objIndex, obj in enumerate(rows):
            try:
                mouseCoord = (x, y)
                points = eval(obj[1])
                for pointIndex, point in enumerate(points):
                    if pointsDistance(mouseCoord, point) <= 5:
                        self._selected_point = pointIndex
                        self._selected_poly = objIndex
                        return
                self._selected_point = None
                self._selected_poly = None
            except:
                pass

    def get_intersection_point_distance(self, test_point, point1, point2):
        p1 = np.float32(point1)
        p2 = np.float32(point2)
        p3 = np.float32(test_point)
        dist = np.linalg.norm(np.cross(p2 - p1,
                                       p1 - p3)) / np.linalg.norm(p2 - p1)
        return dist

    def on_player_double_click_in_video_window(self, event, x, y):
        mouse = (int(x), int(y))
        rows = self._polygons.value

        distances = []
        for obj_index, obj in enumerate(rows):
            try:
                points = list(eval(obj[1]))
                n_points = len(points)
                for point_index, point in enumerate(points):
                    next_point = points[(point_index + 1) % n_points]
                    distance = self.get_intersection_point_distance(
                        mouse, point, next_point)
                    if distance <= 5:
                        vector = next_point[0] - point[0], next_point[
                            1] - point[1]
                        center = point[0] + vector[0] / 2, point[
                            1] + vector[1] / 2
                        radius = pointsDistance(center, point)

                        mouse_distance = pointsDistance(center, mouse)
                        if mouse_distance < radius:
                            distances.append(
                                (distance, obj_index, point_index))
            except:
                pass

        if len(distances) > 0:
            distances = sorted(distances, key=lambda x: x[0])
            obj_index = distances[0][1]
            point_index = distances[0][2]
            points = list(eval(rows[obj_index][1]))

            points.insert(point_index + 1, mouse)
            self._polygons.set_value(1, obj_index, str(points)[1:-1])

            self._selected_poly = obj_index
            self._selected_point = point_index + 1

            if not self._player.is_playing: self._player.refresh()

    def on_player_click_in_video_window(self, event, x, y):
        self._selected_poly = None
        self._selected_point = None

        if not self._square.checked and not self._circle.checked:
            self.selectPoint(int(x), int(y))

    def on_player_drag_in_video_window(self, startPoint, endPoint):
        self._start_point = (int(startPoint[0]), int(startPoint[1]))
        self._end_point = (int(endPoint[0]), int(endPoint[1]))

        if self._selected_poly != None and self._selected_point != None:
            poly = self._polygons.get_value(1, self._selected_poly)
            try:
                points = list(eval(poly))
                points[self._selected_point] = self._end_point
                self._polygons.set_value(1, self._selected_poly,
                                         str(points)[1:-1])
            except Exception as e:
                print(e)

        if not self._player.is_playing: self._player.refresh()

    def on_player_end_drag_in_video_window(self, startPoint, endPoint):
        self._start_point = int(startPoint[0]), int(startPoint[1])
        self._end_point = int(endPoint[0]), int(endPoint[1])

        points = None
        if self._square.checked:
            points = createRectanglePoints(self._start_point, self._end_point)
        elif self._circle.checked and self._end_point[0] > self._start_point[
                0] and self._end_point[1] > self._start_point[1]:
            points = createEllipsePoints(self._start_point, self._end_point)

        if points:
            self._polygons += [
                "Poly_%d" % self._polygons.rows_count,
                str(points)[1:-1]
            ]

        self._start_point = None
        self._end_point = None
        self._square.checked = False
        self._circle.checked = False

        if not self._player.is_playing: self._player.refresh()

    def __add_contours_from_threshold_win(self, contours):
        for contour in contours:
            if contour.any():
                points = [tuple(p[0]) for p in contour.tolist()]
                self._polygons += [
                    "Poly_%d" % self._polygons.rows_count,
                    str(points)[1:-1]
                ]

    def videoSelected(self):
        self._player.value = self._video.value

    def square_toggle(self, checked):
        if checked: self._circle.checked = False

    def circle_toggle(self, checked):
        if checked: self._square.checked = False

    def threshold_btn_click(self):
        if self._threshold_win is None:
            self._threshold_win = GeometryFromThreshold(self)
            self._threshold_win.add_contours = self.__add_contours_from_threshold_win

        self._threshold_win.show()
        if len(self._video.value) > 0:
            self._threshold_win._filename.value = self._video.value

    def remove_clicked(self):
        self._polygons -= -1  #Remove the selected row
        if not self._player.is_playing: self._player.refresh()

    @property
    def geometries(self):
        polys = []
        rows = self._polygons.value
        for objIndex, obj in enumerate(rows):
            points = eval(obj[1])
            polys.append([obj[0], points])
        return polys

    @geometries.setter
    def geometries(self, value):
        self._polygons.value = []

        for name, poly in value:
            points = [tuple(p) for p in poly]
            self._polygons += [name, str(points)[1:-1]]

    @property
    def polygons(self):
        polys = []
        rows = self._polygons.value
        for objIndex, obj in enumerate(rows):
            points = eval(obj[1])
            polys.append(np.array(points, np.int32))
        return np.array(polys)

    @property
    def apply_event(self):
        return self._apply.value

    @apply_event.setter
    def apply_event(self, value):
        self._apply.value = value
        self._show_apply = value is not None

    def show(self):
        super(GeometryManualDesigner, self).show()
        if hasattr(self, '_show_apply') and self._show_apply:
            self._apply.show()

    @property
    def video_filename(self):
        return None

    @video_filename.setter
    def video_filename(self, value):
        self._video.hide()
        self._player.value = value

    @property
    def video_capture(self):
        return self.video_capture.value

    @video_capture.setter
    def video_capture(self, value):
        self._video.hide()
        self._player.value = value

    @property
    def total_n_frames(self):
        if self._player._value is not None and self._player.value != '':
            return self._player.max
        else:
            return 0
Esempio n. 4
0
class Base(BaseWidget):
    """Application form"""
    def __init__(self):
        global conf
        conf += 'pythonvideoannotator.resources'  # Resources can only be loaded after pyqt is running

        super().__init__('Video annotation editor')

        self._project = Project(parent=self)
        Dialog.project = self._project

        self._player = ControlPlayer("Player")
        self._time = ControlEventTimeline('Time')
        self._dock = ControlDockWidget("Timeline",
                                       side='bottom',
                                       order=1,
                                       margin=5)
        self._progress = ControlProgress('Progress', visible=False)

        # define the application toolbar
        self.toolbar = [
            ControlButton('Open',
                          icon=conf.ANNOTATOR_ICON_OPEN,
                          default=self.__open_project_event),
            ControlButton('Save',
                          icon=conf.ANNOTATOR_ICON_SAVE,
                          default=self.__save_project_event)
        ]

        self.formset = ['_player', '_progress']

        self._dock.value = self._time
        self._player.process_frame_event = self.process_frame_event
        self._player.click_event = self.on_player_click_event
        self._player.double_click_event = self.on_player_double_click_event
        self._player.drag_event = self.on_player_drag_event
        self._player.end_drag_event = self.on_player_end_drag_event

        # ignore these controls key release event
        self._time.key_release_event = lambda x: x
        self._player.key_release_event = lambda x: x

        self.load_order = []

        self.mainmenu.insert(
            0, {
                'File': [{
                    'Open': self.__open_project_event,
                    'icon': conf.ANNOTATOR_ICON_OPEN
                }, '-', {
                    'Save': self.__save_project_event,
                    'icon': conf.ANNOTATOR_ICON_SAVE
                }, {
                    'Save as': self.__save_project_as_event,
                    'icon': conf.ANNOTATOR_ICON_SAVE
                }, '-', {
                    'Exit': QApplication.closeAllWindows,
                    'icon': conf.ANNOTATOR_ICON_EXIT
                }]
            })
        self.mainmenu.insert(1, {'Modules': []})
        self.mainmenu.insert(2, {'Windows': []})

        track_user_stats()

        ########################################################################
        ###### CHECK NEW VERSIONS RELEASES #####################################
        ########################################################################
        try:
            versions = pypi_xmlrpc.package_releases('Python-video-annotator')

            if versions is not None:
                new_version = versions[0]
                if float(new_version) > float(__version__):
                    response = self.question(
                        "<h2>New version <b>[{0}]</b> available</h2>"
                        "<p>Do you wish to update the software?</p>"
                        "<p>The software can be updated later by running the next command in the terminal:</p>"
                        "<i>pip install python-video-annotator --force-reinstall</i>"
                        .format(new_version),
                        'New version [{0}]'.format(new_version))

                    if response == 'yes':
                        subprocess.call([
                            sys.executable, "-m", "pip", "install",
                            'python-video-annotator', '--force-reinstall'
                        ])

                        self.message(
                            'The software was updated and this session will be closed. Please execute the software again.',
                            'Restart required')
                        exit()

            else:
                print('Unable to check new versions')

        except Exception as e:
            print('Unable to check new versions:')

    ######################################################################################
    #### FUNCTIONS #######################################################################
    ######################################################################################

    def init_form(self):
        super().init_form()

        if conf.CHART_FILE_PATH:
            self._time.import_chart(*conf.CHART_FILE_PATH)
        if conf.VIDEOANNOTATOR_PROJECTPATH:
            self.load_project(conf.VIDEOANNOTATOR_PROJECTPATH)

        if len(sys.argv) > 1:
            QTimer.singleShot(1000, self.__load_project_from_argv)

    ######################################################################################
    #### EVENTS ##########################################################################
    ######################################################################################

    def on_player_drag_event(self, p1, p2):
        if self._project:
            self._project.player_on_drag(p1, p2)
        self._player.refresh()

    def on_player_end_drag_event(self, p1, p2):
        if self._project:
            self._project.player_on_end_drag(p1, p2)
        self._player.refresh()

    def on_player_click_event(self, event, x, y):
        """
        Code to select a blob with the mouse
        """
        if self._project:
            self._project.player_on_click(event, x, y)
        self._player.refresh()

    def on_player_double_click_event(self, event, x, y):
        """
        Code to select a blob with the mouse
        """
        if self._project:
            self._project.player_on_double_click(event, x, y)
        self._player.refresh()

    def process_frame_event(self, frame):
        """
        Function called before render each frame
        """
        return frame

    def add_dataset_event(self, dataset):
        pass

    def removed_dataset_event(self, dataset):
        pass

    def removed_object_event(self, obj):
        pass

    def __open_project_event(self):
        self.load_project()

    def __save_project_event(self):
        self.save_project(self._project.directory)

    def __save_project_as_event(self):
        self.save_project()

    def __load_project_from_argv(self):
        self.load_project(sys.argv[-1])

    ######################################################################################
    #### EVENT FUNCTIONS #################################################################
    ######################################################################################

    def select_next_path(self):

        selected = self.project.tree.selected_item

        if selected is not None:

            # If it's a video, try to select its first object and the object's first child
            if isinstance(selected.win, Video):

                if selected.childCount() > 0:
                    child_object = selected.child(0)

                    if child_object.childCount() > 0:
                        self.project.tree.selected_item = child_object.child(0)

            # If it's an object, try to select it's first child
            elif isinstance(selected.win, Object2D):
                if selected.childCount() > 0:
                    self.project.tree.selected_item = selected.child(0)

            # If it's a path try to select the first child of the next object of their parent video
            elif isinstance(selected.win, Path):

                parent_object = selected.parent()
                parent_video = parent_object.parent()

                parent_object_index = parent_video.indexOfChild(parent_object)

                if parent_object_index < parent_video.childCount() - 1:
                    next_object = parent_video.child(
                        parent_video.indexOfChild(parent_object) + 1)

                    if next_object.childCount() > 0:
                        self.project.tree.selected_item = next_object.child(0)

                # If it's the last object of the video, go back to the path of the first one
                else:
                    next_object = parent_video.child(0)

                    if next_object.childCount() > 0:
                        self.project.tree.selected_item = next_object.child(0)

    ######################################################################################
    ######################################################################################
    #### PROPERTIES ######################################################################
    ######################################################################################

    @property
    def progress_bar(self):
        return self._progress

    @property
    def timeline(self):
        return self._time

    @property
    def player(self):
        return self._player

    @property
    def video(self):
        return self._player.value

    @video.setter
    def video(self, value):
        self._player.value = value
        self._player.enabled = value is not None
        if value:
            self._time.max = self._player.max

    @property
    def project(self):
        return self._project
class BaseModule(BaseWidget):
    """Application form"""

    def __init__(self):
        global conf;
        conf += 'pythonvideoannotator.resources'  # Resources can only be loaded after pyqt is running

        super(BaseModule, self).__init__('Video annotation editor')

        self._project  = Project(parent=self)
        Dialog.project = self._project

        self._player    = ControlPlayer("Player")
        self._time      = ControlEventTimeline('Time')
        self._dock      = ControlDockWidget("Timeline", side='bottom', order=1, margin=5)

        self.formset    = ['_player']

        self._dock.value                    = self._time
        self._player.process_frame_event    = self.process_frame_event
        self._player.click_event            = self.on_player_click_event
        self._time.key_release_event        = lambda x: x
        self._player.key_release_event      = lambda x: x

        self.load_order = []

        self.mainmenu.insert(0,
            {'File': [
                {'Open': self.__open_project_event, 'icon': conf.ANNOTATOR_ICON_OPEN},
                '-',
                {'Save': self.__save_project_event , 'icon': conf.ANNOTATOR_ICON_SAVE},
                {'Save as': self.__save_project_as_event, 'icon': conf.ANNOTATOR_ICON_SAVE},
                '-',
                {'Exit': QApplication.closeAllWindows, 'icon': conf.ANNOTATOR_ICON_EXIT}
            ] }
        )
        self.mainmenu.insert(1, {'Modules': []} )
        self.mainmenu.insert(2, {'Windows': []} )

        track_user_stats()

        ########################################################################
        ###### CHECK NEW VERSIONS RELEASES #####################################
        ########################################################################
        try:
            versions = pypi_xmlrpc.package_releases('Python-video-annotator')

            if versions is not None:
                new_version = versions[0]
                if float(new_version) > float(__version__):
                    response = self.question(
                        "<h2>New version <b>[{0}]</b> available</h2>"
                        "<p>Do you wish to update the software?</p>"
                        "<p>The software can be updated later by running the next command in the terminal:</p>"
                        "<i>pip install python-video-annotator --force-reinstall</i>".format(new_version),
                        'New version [{0}]'.format(new_version)
                    )

                    if response == 'yes':
                        subprocess.call([sys.executable, "-m", "pip", "install", 'python-video-annotator', '--force-reinstall'])

                        self.message('The software was updated and this session will be closed. Please execute the software again.', 'Restart required')
                        exit()

            else:
                print('Enabled to check new versions')

        except Exception as e:
            print('Enabled to check new versions:')

    ######################################################################################
    #### FUNCTIONS #######################################################################
    ######################################################################################
        
    def init_form(self):
        super(BaseModule, self).init_form()

        if conf.CHART_FILE_PATH: self._time.import_chart(*conf.CHART_FILE_PATH)
        if conf.PROJECT_PATH:    self.load_project(conf.PROJECT_PATH)


    ######################################################################################
    #### IO FUNCTIONS ####################################################################
    ######################################################################################

    def save(self, data, project_path=None):
        self._project.save(data, project_path)
        return data


    def load(self, data, project_path=None):
        try:
            self._project.load(data, project_path)
        except FileNotFoundError as e:
            QMessageBox.critical(self, "Error", str(e))

    def save_project(self, project_path=None):
        try:
            if project_path is None:
                project_path = QFileDialog.getExistingDirectory(self, "Select the project directory")

            if project_path is not None and str(project_path)!='':
                project_path = str(project_path)
                self.save({}, project_path)
        except Exception as e:
            QMessageBox.critical(self, "Error", str(e))

    def load_project(self, project_path=None):
        if project_path is None:
            project_path = QFileDialog.getExistingDirectory(self, "Select the project directory")
        if project_path is not None and str(project_path)!='':
            self.load({}, str(project_path) )



    ######################################################################################
    #### EVENTS ##########################################################################
    ######################################################################################

    def on_player_click_event(self, event, x, y):
        """
        Code to select a blob with the mouse
        """
        super(VideoAnnotationEditor, self).on_player_click_event(event, x, y)
        self._player.refresh()

    def process_frame_event(self, frame):
        """
        Function called before render each frame
        """
        return frame


    def add_dataset_event(self, dataset):
        pass

    def removed_dataset_event(self, dataset):
        pass

    def removed_object_event(self, obj):
        pass


    def __open_project_event(self): self.load_project()

    def __save_project_event(self):
        print('Project saved')
        self.save_project(self._project.directory)

    def __save_project_as_event(self): self.save_project()

    def keyReleaseEvent(self, event):
        ######################################################################################
        #### SPECIFIC SHORTCUTS###############################################################
        ######################################################################################
        
        #Go to the next event and then click the mark the point button
        if event.key() == QtCore.Qt.Key_I:
            if self.timeline.timeline_widget.selected is not None and self.timeline.timeline_widget.selected != self.timeline.timeline_widget.pointer:
                self.move_to_next_event()
                self.mark_point()

        ######################################################################################
        #### DOCK SHORTCUTS ##################################################################
        ######################################################################################

        #Select the path of the next object
        if event.key() == QtCore.Qt.Key_U:
            self.select_next_path()

        #"Click" the Mark Point button in the current Path
        elif event.key() == QtCore.Qt.Key_O:
            self.mark_point()

        ######################################################################################
        #### TIMELINE SHORTCUTS ##############################################################
        ######################################################################################

        if self.timeline.timeline_widget.selected is not None:

            modifier = int(event.modifiers())

            if modifier == QtCore.Qt.ControlModifier and event.key() == QtCore.Qt.Key_Left:
                self.move_event_or_pointer_left(event)

            if modifier == QtCore.Qt.ControlModifier and event.key() == QtCore.Qt.Key_Right:
                self.move_event_or_pointer_right(event)

            if self.timeline.timeline_widget.selected != self.timeline.timeline_widget.pointer:

                # Delete the selected event
                if event.key() == QtCore.Qt.Key_Delete:
                    self.timeline.timeline_widget.remove_selected_event()

                # Lock or unlock an event
                if event.key() == QtCore.Qt.Key_L:
                    self.timeline.timeline_widget.toggle_selected_event_lock()

                if event.key() == QtCore.Qt.Key_E:
                    self.move_to_next_event()

                if event.key() == QtCore.Qt.Key_Q:
                    self.move_to_previous_event()

                if modifier == QtCore.Qt.ControlModifier and event.key() == QtCore.Qt.Key_Up:
                    self.move_event_up()

                if modifier == QtCore.Qt.ControlModifier and event.key() == QtCore.Qt.Key_Down:
                    self.move_event_down()

                if modifier == int(
                        QtCore.Qt.ShiftModifier | QtCore.Qt.ControlModifier) and event.key() == QtCore.Qt.Key_Left:
                    self.move_event_end_left()

                if modifier == int(
                        QtCore.Qt.ShiftModifier | QtCore.Qt.ControlModifier) and event.key() == QtCore.Qt.Key_Right:
                    self.move_event_end_right()

                if modifier == QtCore.Qt.ShiftModifier and event.key() == QtCore.Qt.Key_Left:
                    self.move_event_begin_left()

                if modifier == QtCore.Qt.ShiftModifier and event.key() == QtCore.Qt.Key_Right:
                    self.move_event_begin_right()

        else:
            if event.key() == QtCore.Qt.Key_S:

                self.create_event_at_current_frame()

            # walk backwards 1 step
            elif event.key() == QtCore.Qt.Key_A:
                self.timeline.timeline_widget.position = self.timeline.timeline_widget.position - 1

            # forward 1 step
            elif event.key() == QtCore.Qt.Key_D:
                self.timeline.timeline_widget.position = self.timeline.timeline_widget.position + 1

            elif event.key() == QtCore.Qt.Key_E:
                self.move_to_first_event()

            elif event.key() == QtCore.Qt.Key_Q:
                self.move_to_last_event()

        ######################################################################################
        #### PLAYER SHORTCUTS ################################################################
        ######################################################################################

        # Control video playback using the space bar to Play/Pause
        if event.key() == QtCore.Qt.Key_Space:

            if self.player.video_widget.control.is_playing:
                self.player.video_widget.control.stop()
            else:
                self.player.video_widget.control.play()

        # Jumps 1 frame backwards
        elif event.key() == QtCore.Qt.Key_A:
            self.player.video_widget.control.video_index -= 2
            self.player.video_widget.control.call_next_frame()

        # Jumps 1 frame forward
        elif event.key() == QtCore.Qt.Key_D:
            self.player.video_widget.control.call_next_frame()

        # Jumps 20 seconds backwards
        elif event.key() == QtCore.Qt.Key_Z:
            self.player.video_widget.control.jump_backward()
            self.player.video_widget.control.call_next_frame()

        # Jumps 20 seconds forward
        elif event.key() == QtCore.Qt.Key_C:
            self.player.video_widget.control.jump_forward()
            self.player.video_widget.control.call_next_frame()

        elif event.key() == QtCore.Qt.Key_M:
            self._move_img = False

        elif event.key() == QtCore.Qt.Key_1:
            self.player.video_widget.control.next_frame_step = 1
            self.player.video_widget.show_tmp_msg('Speed: 1x')

        elif event.key() == QtCore.Qt.Key_2:
            self.player.video_widget.control.next_frame_step = 2
            self.player.video_widget.show_tmp_msg('Speed: 2x')

        elif event.key() == QtCore.Qt.Key_3:
            self.player.video_widget.control.next_frame_step = 3
            self.player.video_widget.show_tmp_msg('Speed: 3x')

        elif event.key() == QtCore.Qt.Key_4:
            self.player.video_widget.control.next_frame_step = 4
            self.player.video_widget.show_tmp_msg('Speed: 4x')

        elif event.key() == QtCore.Qt.Key_5:
            self.player.video_widget.control.next_frame_step = 5
            self.player.video_widget.show_tmp_msg('Speed: 5x')

        elif event.key() == QtCore.Qt.Key_6:
            self.player.video_widget.control.next_frame_step = 6
            self.player.video_widget.show_tmp_msg('Speed: 6x')

        elif event.key() == QtCore.Qt.Key_7:
            self.player.video_widget.control.next_frame_step = 7
            self.player.video_widget.show_tmp_msg('Speed: 7x')

        elif event.key() == QtCore.Qt.Key_8:
            self.player.video_widget.control.next_frame_step = 8
            self.player.video_widget.show_tmp_msg('Speed: 8x')

        elif event.key() == QtCore.Qt.Key_9:
            self.player.video_widget.control.next_frame_step = 9
            self.player.video_widget.show_tmp_msg('Speed: 9x')

    ######################################################################################
    #### EVENT FUNCTIONS #################################################################
    ######################################################################################

    def select_next_path(self):

        selected = self.project.tree.selected_item

        if selected is not None:

            #If it's a video, try to select its first object and the object's first child 
            if isinstance(selected.win, Video):

                if selected.childCount() > 0:
                    child_object = selected.child(0)

                    if child_object.childCount() > 0:
                        self.project.tree.selected_item = child_object.child(0)

            #If it's an object, try to select it's first child
            elif isinstance(selected.win, Object2D):
                if selected.childCount() > 0:
                    self.project.tree.selected_item = selected.child(0)

            #If it's a path try to select the first child of the next object of their parent video
            elif isinstance(selected.win, Path):

                parent_object = selected.parent()
                parent_video = parent_object.parent()

                parent_object_index = parent_video.indexOfChild(parent_object)

                if parent_object_index < parent_video.childCount() -1 :
                    next_object = parent_video.child(parent_video.indexOfChild(parent_object)+1)

                    if next_object.childCount() > 0:
                        self.project.tree.selected_item = next_object.child(0)

                #If it's the last object of the video, go back to the path of the first one
                else:
                    next_object = parent_video.child(0)

                    if next_object.childCount() > 0:
                        self.project.tree.selected_item = next_object.child(0)


    def mark_point(self):
        selected = self.project.tree.selected_item

        if selected is not None and isinstance(selected.win, Path):
            path = selected.win

            path.mark_point_button.click()


    def move_to_next_event(self):
        index = self.timeline.timeline_widget.selected_row.events.index(self.timeline.timeline_widget.selected)
        if index < len(self.timeline.timeline_widget.selected_row.events)-1:
            self.timeline.timeline_widget.selected = self.timeline.timeline_widget.selected_row.events[index+1]
            self.timeline.timeline_widget.position = self.timeline.timeline_widget.selected.begin

    def move_to_previous_event(self):
        index = self.timeline.timeline_widget.selected_row.events.index(self.timeline.timeline_widget.selected)
        if index > 0:
            self.timeline.timeline_widget.selected = self.timeline.timeline_widget.selected_row.events[index - 1]
            self.timeline.timeline_widget.position = self.timeline.timeline_widget.selected.begin

    def move_to_first_event(self):
        if self.timeline.timeline_widget.selected_row is not None and len(self.timeline.timeline_widget.selected_row)>0:
            self.timeline.timeline_widget.selected = self.timeline.timeline_widget.selected_row.events[0]
            self.timeline.timeline_widget.position = self.timeline.timeline_widget.selected.begin

    def move_to_last_event(self):
        if self.timeline.timeline_widget.selected_row is not None and len(self.timeline.timeline_widget.selected_row)>0:
            self.timeline.timeline_widget.selected = self.timeline.timeline_widget.selected_row.events[len(self.timeline.timeline_widget.selected_row)-1]
            self.timeline.timeline_widget.position = self.timeline.timeline_widget.selected.begin

    def move_event_or_pointer_left(self, event):
        modifier = int(event.modifiers())

        self.timeline.timeline_widget.selected.move(-1, 0)
        event.ignore()
        self.timeline.timeline_widget.repaint()

    def move_event_or_pointer_right(self, event):
        modifier = int(event.modifiers())

        self.timeline.timeline_widget.selected.move(1, 0)
        event.ignore()
        self.timeline.timeline_widget.repaint()

    def move_event_up(self):
        self.timeline.timeline_widget.selected.move(0, self.timeline.timeline_widget.selected.top_coordinate - self.timeline.timeline_widget.TRACK_HEIGHT)
        self.timeline.timeline_widget.repaint()

    def move_event_down(self):
        self.timeline.timeline_widget.selected.move(0, self.timeline.timeline_widget.selected.top_coordinate + self.timeline.timeline_widget.TRACK_HEIGHT)
        self.timeline.timeline_widget.repaint()

    def move_event_end_left(self):
        self.timeline.timeline_widget.selected.move_end(-1)
        self.timeline.timeline_widget.repaint()

    def move_event_end_right(self):
        self.timeline.timeline_widget.selected.move_end(1)
        self.timeline.timeline_widget.repaint()

    def move_event_begin_left(self):
        self.timeline.timeline_widget.selected.move_begin(-1)
        self.timeline.timeline_widget.repaint()

    def move_event_begin_right(self):
        self.timeline.timeline_widget.selected.move_begin(1)
        self.timeline.timeline_widget.repaint()

    def create_event_at_current_frame(self):
        if not self.timeline.timeline_widget.creating_event:
            # Start
            self.timeline.timeline_widget.creating_event_start = self.timeline.timeline_widget.pointer.frame
            self.timeline.timeline_widget.creating_event = True

            # TODO Add some indicator that an event is being recorded, like
            # using the track selector circle to become red

        else:
            # End, must be followed right after Start key and have no
            # effect otherwise
            self.timeline.timeline_widget.creating_event_end = self.timeline.timeline_widget.pointer.frame

            start = self.timeline.timeline_widget.creating_event_start
            end = self.timeline.timeline_widget.creating_event_end
            comment = ""

            if end > start:
                track = self.timeline.timeline_widget.selected_row
                if track is None and len(self.timeline.timeline_widget.tracks)>0:
                    track = self.timeline.timeline_widget.tracks[0]
                if track is None:
                    track = self.timeline.timeline_widget.add_track()

                self.timeline.timeline_widget.add_event(start, end, comment, track=track )
                self.timeline.timeline_widget.repaint()
                self.timeline.timeline_widget.creating_event = False
            else:
                self.timeline.timeline_widget.creating_event = False




    ######################################################################################
    #### PROPERTIES ######################################################################
    ######################################################################################

    @property
    def timeline(self): return self._time

    @property
    def player(self): return self._player
    
    @property
    def video(self): return self._player.value
    @video.setter
    def video(self, value): 
        self._player.value      = value
        self._player.enabled    = value is not None
        if value:
            self._time.max = self._player.max

    @property
    def project(self): return self._project
Esempio n. 6
0
class BaseModule(BaseWidget):
    """Application form"""
    def __init__(self):
        global conf
        conf += 'pythonvideoannotator.resources'  # Resources can only be loaded after pyqt is running

        super(BaseModule, self).__init__('Video annotation editor')

        self._project = Project(parent=self)
        Dialog.project = self._project

        self._player = ControlPlayer("Player")
        self._time = ControlEventTimeline('Time')
        self._dock = ControlDockWidget("Timeline",
                                       side='bottom',
                                       order=1,
                                       margin=5)

        self.formset = ['_player']

        self._dock.value = self._time
        self._player.process_frame_event = self.process_frame_event
        self._player.click_event = self.on_player_click_event
        self._time.key_release_event = self.__timeline_key_release_event

        self.load_order = []

        self.mainmenu.insert(
            0, {
                'File': [{
                    'Open': self.__open_project_event,
                    'icon': conf.ANNOTATOR_ICON_OPEN
                }, '-', {
                    'Save': self.__save_project_event,
                    'icon': conf.ANNOTATOR_ICON_SAVE
                }, {
                    'Save as': self.__save_project_as_event,
                    'icon': conf.ANNOTATOR_ICON_SAVE
                }, '-', {
                    'Exit': QApplication.closeAllWindows,
                    'icon': conf.ANNOTATOR_ICON_EXIT
                }]
            })
        self.mainmenu.insert(1, {'Modules': []})
        self.mainmenu.insert(2, {'Windows': []})

        track_user_stats()

        ########################################################################
        ###### CHECK NEW VERSIONS RELEASES #####################################
        ########################################################################
        try:
            versions = pypi_xmlrpc.package_releases('Python-video-annotator')

            if versions is not None:
                new_version = versions[0]
                new_version_numbers = [int(x) for x in new_version.split('.')]
                version_numbers = [int(x) for x in __version__.split('.')]
                for new_n, n in zip(new_version_numbers, version_numbers):
                    if new_n > n:
                        response = self.question(
                            "<h2>New version <b>[{0}]</b> available</h2>"
                            "<p>Do you wish to update the software?</p>"
                            "<p>The software can be updated later by running the next command in the terminal:</p>"
                            "<i>pip install python-video-annotator --force-reinstall</i>"
                            .format(new_version),
                            'New version [{0}]'.format(new_version))

                        if response == 'yes':
                            subprocess.call([
                                sys.executable, "-m", "pip", "install",
                                'python-video-annotator', '--force-reinstall'
                            ])

                            self.message(
                                'The software was updated and this session will be closed. Please execute the software again.',
                                'Restart required')
                            exit()
                        break
            else:
                print('Enabled to check new versions')

        except Exception as e:
            print('Enabled to check new versions:')

    ######################################################################################
    #### FUNCTIONS #######################################################################
    ######################################################################################

    def init_form(self):
        super(BaseModule, self).init_form()

        if conf.CHART_FILE_PATH: self._time.import_chart(*conf.CHART_FILE_PATH)
        if conf.PROJECT_PATH: self.load_project(conf.PROJECT_PATH)

    ######################################################################################
    #### IO FUNCTIONS ####################################################################
    ######################################################################################

    def save(self, data, project_path=None):
        self._project.save(data, project_path)
        return data

    def load(self, data, project_path=None):
        try:
            self._project.load(data, project_path)
        except FileNotFoundError as e:
            QMessageBox.critical(self, "Error", str(e))

    def save_project(self, project_path=None):
        try:
            if project_path is None:
                project_path = QFileDialog.getExistingDirectory(
                    self, "Select the project directory")

            if project_path is not None and str(project_path) != '':
                project_path = str(project_path)
                self.save({}, project_path)
        except Exception as e:
            QMessageBox.critical(self, "Error", str(e))

    def load_project(self, project_path=None):
        if project_path is None:
            project_path = QFileDialog.getExistingDirectory(
                self, "Select the project directory")
        if project_path is not None and str(project_path) != '':
            self.load({}, str(project_path))

    ######################################################################################
    #### EVENTS ##########################################################################
    ######################################################################################

    def on_player_click_event(self, event, x, y):
        """
        Code to select a blob with the mouse
        """
        super(VideoAnnotationEditor, self).on_player_click_event(event, x, y)
        self._player.refresh()

    def process_frame_event(self, frame):
        """
        Function called before render each frame
        """
        return frame

    def add_dataset_event(self, dataset):
        pass

    def removed_dataset_event(self, dataset):
        pass

    def removed_object_event(self, obj):
        pass

    def __open_project_event(self):
        self.load_project()

    def __save_project_event(self):
        print('Project saved')
        self.save_project(self._project.directory)

    def __save_project_as_event(self):
        self.save_project()

    def __timeline_key_release_event(self, event):
        """
        Control video playback using the space bar to Play/Pause
        """
        if event.key() == QtCore.Qt.Key_Space:
            self._player.stop(
            ) if self._player.is_playing else _player._video.play()

    ######################################################################################
    #### PROPERTIES ######################################################################
    ######################################################################################

    @property
    def timeline(self):
        return self._time

    @property
    def player(self):
        return self._player

    @property
    def video(self):
        return self._player.value

    @video.setter
    def video(self, value):
        self._player.value = value
        self._player.enabled = value is not None
        if value:
            self._time.max = self._player.max

    @property
    def project(self):
        return self._project
class BaseModule(BaseWidget):
    """Application form"""
    def __init__(self):
        global conf
        conf += 'pythonvideoannotator.resources'  # Resources can only be loaded after pyqt is running

        super(BaseModule, self).__init__('Video annotation editor')

        self._project = Project(parent=self)
        Dialog.project = self._project

        self._player = ControlPlayer("Player")
        self._time = ControlEventTimeline('Time')
        self._dock = ControlDockWidget("Timeline",
                                       side='bottom',
                                       order=1,
                                       margin=5)
        self._progress = ControlProgress('Progress', visible=False)

        # define the application toolbar
        self.toolbar = [
            ControlButton('Open',
                          icon=conf.ANNOTATOR_ICON_OPEN,
                          default=self.__open_project_event),
            ControlButton('Save',
                          icon=conf.ANNOTATOR_ICON_SAVE,
                          default=self.__save_project_event)
        ]

        self.formset = ['_player', '_progress']

        self._dock.value = self._time
        self._player.process_frame_event = self.process_frame_event
        self._player.click_event = self.on_player_click_event
        self._player.double_click_event = self.on_player_double_click_event
        self._player.drag_event = self.on_player_drag_event
        self._player.end_drag_event = self.on_player_end_drag_event
        self._time.key_release_event = lambda x: x
        self._player.key_release_event = lambda x: x

        self.load_order = []

        self.mainmenu.insert(
            0, {
                'File': [{
                    'Open': self.__open_project_event,
                    'icon': conf.ANNOTATOR_ICON_OPEN
                }, '-', {
                    'Save': self.__save_project_event,
                    'icon': conf.ANNOTATOR_ICON_SAVE
                }, {
                    'Save as': self.__save_project_as_event,
                    'icon': conf.ANNOTATOR_ICON_SAVE
                }, '-', {
                    'Exit': QApplication.closeAllWindows,
                    'icon': conf.ANNOTATOR_ICON_EXIT
                }]
            })
        self.mainmenu.insert(1, {'Modules': []})
        self.mainmenu.insert(2, {'Windows': []})

        track_user_stats()

        ########################################################################
        ###### CHECK NEW VERSIONS RELEASES #####################################
        ########################################################################
        try:
            versions = pypi_xmlrpc.package_releases('Python-video-annotator')

            if versions is not None:
                new_version = versions[0]
                if float(new_version) > float(__version__):
                    response = self.question(
                        "<h2>New version <b>[{0}]</b> available</h2>"
                        "<p>Do you wish to update the software?</p>"
                        "<p>The software can be updated later by running the next command in the terminal:</p>"
                        "<i>pip install python-video-annotator --force-reinstall</i>"
                        .format(new_version),
                        'New version [{0}]'.format(new_version))

                    if response == 'yes':
                        subprocess.call([
                            sys.executable, "-m", "pip", "install",
                            'python-video-annotator', '--force-reinstall'
                        ])

                        self.message(
                            'The software was updated and this session will be closed. Please execute the software again.',
                            'Restart required')
                        exit()

            else:
                print('Unable to check new versions')

        except Exception as e:
            print('Unable to check new versions:')

    ######################################################################################
    #### FUNCTIONS #######################################################################
    ######################################################################################

    def init_form(self):
        super(BaseModule, self).init_form()

        if conf.CHART_FILE_PATH:
            self._time.import_chart(*conf.CHART_FILE_PATH)
        if conf.VIDEOANNOTATOR_PROJECTPATH:
            self.load_project(conf.VIDEOANNOTATOR_PROJECTPATH)

        if len(sys.argv) > 1:
            QTimer.singleShot(1000, self.__load_project_from_argv)

    ######################################################################################
    #### IO FUNCTIONS ####################################################################
    ######################################################################################

    def save(self, data, project_path=None):
        self._project.save(data, project_path)
        return data

    def load(self, data, project_path=None):
        try:
            self._project.load(data, project_path)
        except FileNotFoundError as e:
            QMessageBox.critical(self, "Error", str(e))

    def save_project(self, project_path=None):
        try:
            if project_path is None:
                project_path = QFileDialog.getExistingDirectory(
                    self, "Select the project directory")

            if project_path is not None and str(project_path) != '':
                project_path = str(project_path)
                self.save({}, project_path)
        except Exception as e:
            traceback.print_exc()
            QMessageBox.critical(self, "Error", str(e))

    def load_project(self, project_path=None):
        if project_path is None:
            project_path = QFileDialog.getExistingDirectory(
                self, "Select the project directory")

        if project_path is not None and str(project_path) != '':
            self.load({}, str(project_path))

    ######################################################################################
    #### EVENTS ##########################################################################
    ######################################################################################

    def on_player_drag_event(self, p1, p2):
        if self._project:
            self._project.player_on_drag(p1, p2)
        self._player.refresh()

    def on_player_end_drag_event(self, p1, p2):
        if self._project:
            self._project.player_on_end_drag(p1, p2)
        self._player.refresh()

    def on_player_click_event(self, event, x, y):
        """
        Code to select a blob with the mouse
        """
        if self._project:
            self._project.player_on_click(event, x, y)
        self._player.refresh()

    def on_player_double_click_event(self, event, x, y):
        """
        Code to select a blob with the mouse
        """
        if self._project:
            self._project.player_on_double_click(event, x, y)
        self._player.refresh()

    def process_frame_event(self, frame):
        """
        Function called before render each frame
        """
        return frame

    def add_dataset_event(self, dataset):
        pass

    def removed_dataset_event(self, dataset):
        pass

    def removed_object_event(self, obj):
        pass

    def __open_project_event(self):
        self.load_project()

    def __save_project_event(self):
        self.save_project(self._project.directory)

    def __save_project_as_event(self):
        self.save_project()

    def keyReleaseEvent(self, event):
        ######################################################################################
        #### SPECIFIC SHORTCUTS###############################################################
        ######################################################################################

        #Go to the next event and then click the mark the point button
        if event.key() == QtCore.Qt.Key_I:
            if self.timeline.timeline_widget.selected is not None and self.timeline.timeline_widget.selected != self.timeline.timeline_widget.pointer:
                self.move_to_next_event()
                self.mark_point()

        #Select the path of the next object and click the mark the point button
        if event.key() == QtCore.Qt.Key_U:
            self.select_next_path()
            self.mark_point()

        #"Click" the Mark Point button in the current Path
        elif event.key() == QtCore.Qt.Key_O:
            self.mark_point()

        ######################################################################################
        #### TIMELINE SHORTCUTS ##############################################################
        ######################################################################################

        if self.timeline.timeline_widget.selected is not None:

            modifier = int(event.modifiers())

            if modifier == QtCore.Qt.ControlModifier and event.key(
            ) == QtCore.Qt.Key_Left:
                self.move_event_or_pointer_left(event)

            if modifier == QtCore.Qt.ControlModifier and event.key(
            ) == QtCore.Qt.Key_Right:
                self.move_event_or_pointer_right(event)

            if self.timeline.timeline_widget.selected != self.timeline.timeline_widget.pointer:

                # Delete the selected event
                if event.key() == QtCore.Qt.Key_Delete:
                    self.timeline.timeline_widget.remove_selected_event()

                # Lock or unlock an event
                if event.key() == QtCore.Qt.Key_L:
                    self.timeline.timeline_widget.toggle_selected_event_lock()

                if event.key() == QtCore.Qt.Key_E:
                    self.move_to_next_event()

                if event.key() == QtCore.Qt.Key_Q:
                    self.move_to_previous_event()

                if modifier == QtCore.Qt.ControlModifier and event.key(
                ) == QtCore.Qt.Key_Up:
                    self.move_event_up()

                if modifier == QtCore.Qt.ControlModifier and event.key(
                ) == QtCore.Qt.Key_Down:
                    self.move_event_down()

                if modifier == int(QtCore.Qt.ShiftModifier
                                   | QtCore.Qt.ControlModifier) and event.key(
                                   ) == QtCore.Qt.Key_Left:
                    self.move_event_end_left()

                if modifier == int(QtCore.Qt.ShiftModifier
                                   | QtCore.Qt.ControlModifier) and event.key(
                                   ) == QtCore.Qt.Key_Right:
                    self.move_event_end_right()

                if modifier == QtCore.Qt.ShiftModifier and event.key(
                ) == QtCore.Qt.Key_Left:
                    self.move_event_begin_left()

                if modifier == QtCore.Qt.ShiftModifier and event.key(
                ) == QtCore.Qt.Key_Right:
                    self.move_event_begin_right()

        else:
            if event.key() == QtCore.Qt.Key_S:

                self.create_event_at_current_frame()

            # walk backwards 1 step
            elif event.key() == QtCore.Qt.Key_A:
                self.timeline.timeline_widget.position = self.timeline.timeline_widget.position - 1

            # forward 1 step
            elif event.key() == QtCore.Qt.Key_D:
                self.timeline.timeline_widget.position = self.timeline.timeline_widget.position + 1

            elif event.key() == QtCore.Qt.Key_E:
                self.move_to_first_event()

            elif event.key() == QtCore.Qt.Key_Q:
                self.move_to_last_event()

        ######################################################################################
        #### PLAYER SHORTCUTS ################################################################
        ######################################################################################

        # Control video playback using the space bar to Play/Pause
        if event.key() == QtCore.Qt.Key_Space:
            if self.player.is_playing:
                self.player.stop()
            else:
                self.player.play()

        # Jumps 1 frame backwards
        elif event.key() == QtCore.Qt.Key_A:
            self.player.video_widget.control.video_index -= 2
            self.player.video_widget.control.call_next_frame()

        # Jumps 1 frame forward
        elif event.key() == QtCore.Qt.Key_D:
            self.player.video_widget.control.call_next_frame()

        # Jumps 20 seconds backwards
        elif event.key() == QtCore.Qt.Key_Z:
            self.player.video_widget.control.jump_backward()
            self.player.video_widget.control.call_next_frame()

        # Jumps 20 seconds forward
        elif event.key() == QtCore.Qt.Key_C:
            self.player.video_widget.control.jump_forward()
            self.player.video_widget.control.call_next_frame()

        elif event.key() == QtCore.Qt.Key_M:
            self._move_img = False

        elif event.key() == QtCore.Qt.Key_1:
            self.player.video_widget.control.next_frame_step = 1
            self.player.video_widget.show_tmp_msg('Speed: 1x')

        elif event.key() == QtCore.Qt.Key_2:
            self.player.video_widget.control.next_frame_step = 2
            self.player.video_widget.show_tmp_msg('Speed: 2x')

        elif event.key() == QtCore.Qt.Key_3:
            self.player.video_widget.control.next_frame_step = 3
            self.player.video_widget.show_tmp_msg('Speed: 3x')

        elif event.key() == QtCore.Qt.Key_4:
            self.player.video_widget.control.next_frame_step = 4
            self.player.video_widget.show_tmp_msg('Speed: 4x')

        elif event.key() == QtCore.Qt.Key_5:
            self.player.video_widget.control.next_frame_step = 5
            self.player.video_widget.show_tmp_msg('Speed: 5x')

        elif event.key() == QtCore.Qt.Key_6:
            self.player.video_widget.control.next_frame_step = 6
            self.player.video_widget.show_tmp_msg('Speed: 6x')

        elif event.key() == QtCore.Qt.Key_7:
            self.player.video_widget.control.next_frame_step = 7
            self.player.video_widget.show_tmp_msg('Speed: 7x')

        elif event.key() == QtCore.Qt.Key_8:
            self.player.video_widget.control.next_frame_step = 8
            self.player.video_widget.show_tmp_msg('Speed: 8x')

        elif event.key() == QtCore.Qt.Key_9:
            self.player.video_widget.control.next_frame_step = 9
            self.player.video_widget.show_tmp_msg('Speed: 9x')

    def __load_project_from_argv(self):
        self.load_project(sys.argv[-1])

    ######################################################################################
    #### EVENT FUNCTIONS #################################################################
    ######################################################################################

    def select_next_path(self):

        selected = self.project.tree.selected_item

        if selected is not None:

            #If it's a video, try to select its first object and the object's first child
            if isinstance(selected.win, Video):

                if selected.childCount() > 0:
                    child_object = selected.child(0)

                    if child_object.childCount() > 0:
                        self.project.tree.selected_item = child_object.child(0)

            #If it's an object, try to select it's first child
            elif isinstance(selected.win, Object2D):
                if selected.childCount() > 0:
                    self.project.tree.selected_item = selected.child(0)

            #If it's a path try to select the first child of the next object of their parent video
            elif isinstance(selected.win, Path):

                parent_object = selected.parent()
                parent_video = parent_object.parent()

                parent_object_index = parent_video.indexOfChild(parent_object)

                if parent_object_index < parent_video.childCount() - 1:
                    next_object = parent_video.child(
                        parent_video.indexOfChild(parent_object) + 1)

                    if next_object.childCount() > 0:
                        self.project.tree.selected_item = next_object.child(0)

                #If it's the last object of the video, go back to the path of the first one
                else:
                    next_object = parent_video.child(0)

                    if next_object.childCount() > 0:
                        self.project.tree.selected_item = next_object.child(0)

    def mark_point(self):
        selected = self.project.tree.selected_item

        if selected is not None and isinstance(selected.win, Path):
            path = selected.win

            if not path.mark_point_button.checked:
                path.mark_point_button.click()

    def move_to_next_event(self):
        index = self.timeline.timeline_widget.selected_row.events.index(
            self.timeline.timeline_widget.selected)
        if index < len(self.timeline.timeline_widget.selected_row.events) - 1:
            self.timeline.timeline_widget.selected = self.timeline.timeline_widget.selected_row.events[
                index + 1]
            self.timeline.timeline_widget.position = self.timeline.timeline_widget.selected.begin

    def move_to_previous_event(self):
        index = self.timeline.timeline_widget.selected_row.events.index(
            self.timeline.timeline_widget.selected)
        if index > 0:
            self.timeline.timeline_widget.selected = self.timeline.timeline_widget.selected_row.events[
                index - 1]
            self.timeline.timeline_widget.position = self.timeline.timeline_widget.selected.begin

    def move_to_first_event(self):
        if self.timeline.timeline_widget.selected_row is not None and len(
                self.timeline.timeline_widget.selected_row) > 0:
            self.timeline.timeline_widget.selected = self.timeline.timeline_widget.selected_row.events[
                0]
            self.timeline.timeline_widget.position = self.timeline.timeline_widget.selected.begin

    def move_to_last_event(self):
        if self.timeline.timeline_widget.selected_row is not None and len(
                self.timeline.timeline_widget.selected_row) > 0:
            self.timeline.timeline_widget.selected = self.timeline.timeline_widget.selected_row.events[
                len(self.timeline.timeline_widget.selected_row) - 1]
            self.timeline.timeline_widget.position = self.timeline.timeline_widget.selected.begin

    def move_event_or_pointer_left(self, event):
        modifier = int(event.modifiers())

        self.timeline.timeline_widget.selected.move(-1, 0)
        event.ignore()
        self.timeline.timeline_widget.repaint()

    def move_event_or_pointer_right(self, event):
        modifier = int(event.modifiers())

        self.timeline.timeline_widget.selected.move(1, 0)
        event.ignore()
        self.timeline.timeline_widget.repaint()

    def move_event_up(self):
        self.timeline.timeline_widget.selected.move(
            0, self.timeline.timeline_widget.selected.top_coordinate -
            self.timeline.timeline_widget.TRACK_HEIGHT)
        self.timeline.timeline_widget.repaint()

    def move_event_down(self):
        self.timeline.timeline_widget.selected.move(
            0, self.timeline.timeline_widget.selected.top_coordinate +
            self.timeline.timeline_widget.TRACK_HEIGHT)
        self.timeline.timeline_widget.repaint()

    def move_event_end_left(self):
        self.timeline.timeline_widget.selected.move_end(-1)
        self.timeline.timeline_widget.repaint()

    def move_event_end_right(self):
        self.timeline.timeline_widget.selected.move_end(1)
        self.timeline.timeline_widget.repaint()

    def move_event_begin_left(self):
        self.timeline.timeline_widget.selected.move_begin(-1)
        self.timeline.timeline_widget.repaint()

    def move_event_begin_right(self):
        self.timeline.timeline_widget.selected.move_begin(1)
        self.timeline.timeline_widget.repaint()

    def create_event_at_current_frame(self):
        if not self.timeline.timeline_widget.creating_event:
            # Start
            self.timeline.timeline_widget.creating_event_start = self.timeline.timeline_widget.pointer.frame
            self.timeline.timeline_widget.creating_event = True

            # TODO Add some indicator that an event is being recorded, like
            # using the track selector circle to become red

        else:
            # End, must be followed right after Start key and have no
            # effect otherwise
            self.timeline.timeline_widget.creating_event_end = self.timeline.timeline_widget.pointer.frame

            start = self.timeline.timeline_widget.creating_event_start
            end = self.timeline.timeline_widget.creating_event_end
            comment = ""

            if end > start:
                track = self.timeline.timeline_widget.selected_row
                if track is None and len(
                        self.timeline.timeline_widget.tracks) > 0:
                    track = self.timeline.timeline_widget.tracks[0]
                if track is None:
                    track = self.timeline.timeline_widget.add_track()

                self.timeline.timeline_widget.add_event(start,
                                                        end,
                                                        comment,
                                                        track=track)
                self.timeline.timeline_widget.repaint()
                self.timeline.timeline_widget.creating_event = False
            else:
                self.timeline.timeline_widget.creating_event = False

    ######################################################################################
    ######################################################################################
    #### PROPERTIES ######################################################################
    ######################################################################################

    @property
    def progress_bar(self):
        return self._progress

    @property
    def timeline(self):
        return self._time

    @property
    def player(self):
        return self._player

    @property
    def video(self):
        return self._player.value

    @video.setter
    def video(self, value):
        self._player.value = value
        self._player.enabled = value is not None
        if value:
            self._time.max = self._player.max

    @property
    def project(self):
        return self._project
Esempio n. 8
0
class VideoWindow(BaseWidget):

    def __init__(self, *args, **kwargs):
        super().__init__('Hazard Labelling')

        self._args = {  "filepath": FILEPATH,
                        "folder": FOLDER,
                        "dest": DEST
                        }

        self.set_margin(10)

        #Definition of the forms fields
        self._videofile = ControlFile('Video')
        self._hazardbutton = ControlButton('Hazard')
        self._next = ControlButton('Next Video')
        self._save_data = ControlButton('Save labels')
        self._player = ControlPlayer('Player')
        self._timeline = ControlEventTimeline('Timeline')
        self._panel = ControlDockWidget(label='Timeline', side='bottom', margin=10)
        self._status = ControlText('Status')
        self._file_list = []

        if self._args["folder"] is None:
            if self._args["filepath"] is not None:
                self._file_list = self._args["filepath"]
        elif self._args["folder"] is not None:
            if os.path.isdir(self._args["folder"]):
                self.__updateStatus("Source folder found at: {}".format(self._args["folder"]))
                print("Scanning folder and all subfolders... {}".format(self._args["folder"]))
                count = 0
                for (dirpath, dirnames, filenames) in os.walk(self._args["folder"]):
                    path = []
                    for f in filenames:
                        if f.rsplit('.')[-1] in ACCEPTABLE_EXT:
                            count += 1
                            path.append(dirpath + "/" + f)
                    self._file_list.extend(path)
                    if count % 100 == 0:
                        print("Found {} files...".format(count))
                print("Scan complete, found {} acceptable files".format(count))
        self._video_count = len(self._file_list)
        self._progress = ControlProgress(label="Video %p of " + str(self._video_count), defaultValue=1, min=1, max=self._video_count)

        self._hazard_counter = 0
        self._current_video = 0

        #Define function calls on button presses
        self._videofile.changed_event = self.__videoFileSelectionEvent
        self._hazardbutton.value = self.__labelHazard
        self._next.value = self.__nextVideo
        self._save_data.value = self.__saveData
        self._panel.value = self._timeline
        self._progress.value = self._current_video + 1

        #Define events
        self._player.process_frame_event = self.__processFrame
        self._player.click_event = self.__clickEvent
        self._player.key_release_event = self.__tagEvent

        #Define the organization of the Form Controls
        self._formset = [
            '_player',
            # '_hazardbutton',
            '_panel',
            ('_videofile', '_next'),
            ('_status', '_save_data'),
            '_progress'
            ]

        self._video_loaded = False

        try:
            self.__videoFileSelect(self._file_list[self._current_video])
        except Exception as e:
            self.__updateStatus("Select video...")

        self._hazard_default_duration = 0

    def __videoFileSelect(self, filepath):
        try:
            self._videofile.value = str(filepath)
            self._player.value = self._videofile.value
            self._player.refresh()
            self._player.update_frame()
            # Hazard set to occur for 60 frames upon flagging
            self._hazard_default_duration = int(self._player.fps * 2)
            self._video_loaded = True
        except Exception as e:
            self.__updateStatus("Unable to select video")

    def __videoFileSelectionEvent(self):
        """
        When the videofile is selected instantiate the video in the player
        """
        try:
            self._player.value = self._videofile.value
            # Hazard set to occur for 60 frames upon flagging
            self._hazard_default_duration = int(self._player.fps * 2)
            self._video_loaded = True
        except Exception as e:
            self.__updateStatus("No video selected")

    def __nextVideo(self):
        if self._current_video >= (self._video_count - 1):
            self.__updateStatus("No more videos")
        else:
            self._current_video += 1
            self.__reset()
            self.__videoFileSelect(self._file_list[self._current_video])
            self._progress.value = self._current_video + 1

    def __reset(self):
        self._player.stop()
        self.__saveData()
        print("saving on reset")
        self._timeline.clean()
        self.__updateStatus("")

    def __processFrame(self, frame):
        """
        Do some processing to the frame and return the result frame
        """
        return frame

    def __clickEvent(self, click_event, x, y):
        self.__labelHazard()

    def __tagEvent(self, event):
        """
        Label hazard using Enter key
        """
        key = event.key()

        # QtCore.Qt.Key_Enter gives wrong value (at least on test PC) for Enter key
        # Desired == 16777221, actual == 16777220

        key_id = 16777220

        if event.key() == key_id:
            self.__labelHazard()

    def __addFlag(self, value):
        self._timeline.add_period(value)

    def __labelHazard(self):
        if self._video_loaded:
            if self._player.video_index > (self._player.max - self._hazard_default_duration):
                flag_duration = round(self._player.max - self._player.video_index)
            else:
                flag_duration = self._hazard_default_duration
            try:
                self._hazard_counter += 1
                self.__updateStatus("Hazard flagged! | Frame: {} Timestamp: {}".format(self._player.video_index,
                                round(self._player.video_index/self._player.fps, 3)))
                self.__addFlag((self._player.video_index, self._player.video_index + flag_duration, str(self._hazard_counter)))
            except Exception as e:
                try:
                    self._player.refresh()
                    self.__updateStatus("Hazard flagged! | Frame: {} Timestamp: {}".format(self._player.video_index,
                                    round(self._player.video_index/self._player.fps, 3)))
                except Exception as e:
                    self.__updateStatus("Unable to label, exiting...")
                    sys.exit(0)

    def __saveData(self):
        self._timeline.export_csv_file(self._videofile.value + "_hazard.csv")
        self.__updateStatus("Saving {} to {}".format(self._videofile.value + "_hazard.csv", self._args["dest"]))

    def __updateStatus(self, msg):
        self._status.value = str(msg)