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
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
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
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)