diff --git a/labelImg.py b/labelImg.py index 739c7f092..01a110048 100755 --- a/labelImg.py +++ b/labelImg.py @@ -57,7 +57,7 @@ class WindowMixin(object): def menu(self, title, actions=None): menu = self.menuBar().addMenu(title) if actions: - addActions(menu, actions) + add_actions(menu, actions) return menu def toolbar(self, title, actions=None): @@ -66,7 +66,7 @@ def toolbar(self, title, actions=None): # toolbar.setOrientation(Qt.Vertical) toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) if actions: - addActions(toolbar, actions) + add_actions(toolbar, actions) self.addToolBar(Qt.LeftToolBarArea, toolbar) return toolbar @@ -74,7 +74,7 @@ def toolbar(self, title, actions=None): class MainWindow(QMainWindow, WindowMixin): FIT_WINDOW, FIT_WIDTH, MANUAL_ZOOM = list(range(3)) - def __init__(self, defaultFilename=None, defaultPrefdefClassFile=None, defaultSaveDir=None): + def __init__(self, default_filename=None, default_prefdef_class_file=None, default_save_dir=None): super(MainWindow, self).__init__() self.setWindowTitle(__appname__) @@ -84,370 +84,370 @@ def __init__(self, defaultFilename=None, defaultPrefdefClassFile=None, defaultSa settings = self.settings # Load string bundle for i18n - self.stringBundle = StringBundle.getBundle() - getStr = lambda strId: self.stringBundle.getString(strId) + self.string_bundle = StringBundle.get_bundle() + get_str = lambda str_id: self.string_bundle.get_string(str_id) # Save as Pascal voc xml - self.defaultSaveDir = defaultSaveDir - self.labelFileFormat = settings.get(SETTING_LABEL_FILE_FORMAT, LabelFileFormat.PASCAL_VOC) + self.default_save_dir = default_save_dir + self.label_file_format = settings.get(SETTING_LABEL_FILE_FORMAT, LabelFileFormat.PASCAL_VOC) # For loading all image under a directory - self.mImgList = [] - self.dirname = None - self.labelHist = [] - self.lastOpenDir = None + self.m_img_list = [] + self.dir_name = None + self.label_hist = [] + self.last_open_dir = None # Whether we need to save or not. self.dirty = False - self._noSelectionSlot = False + self._no_selection_slot = False self._beginner = True - self.screencastViewer = self.getAvailableScreencastViewer() + self.screencast_viewer = self.get_available_screencast_viewer() self.screencast = "https://youtu.be/p0nR2YsCY_U" # Load predefined classes to the list - self.loadPredefinedClasses(defaultPrefdefClassFile) + self.load_predefined_classes(default_prefdef_class_file) # Main widgets and related state. - self.labelDialog = LabelDialog(parent=self, listItem=self.labelHist) + self.label_dialog = LabelDialog(parent=self, list_item=self.label_hist) - self.itemsToShapes = {} - self.shapesToItems = {} - self.prevLabelText = '' + self.items_to_shapes = {} + self.shapes_to_items = {} + self.prev_label_text = '' - listLayout = QVBoxLayout() - listLayout.setContentsMargins(0, 0, 0, 0) + list_layout = QVBoxLayout() + list_layout.setContentsMargins(0, 0, 0, 0) # Create a widget for using default label - self.useDefaultLabelCheckbox = QCheckBox(getStr('useDefaultLabel')) - self.useDefaultLabelCheckbox.setChecked(False) - self.defaultLabelTextLine = QLineEdit() - useDefaultLabelQHBoxLayout = QHBoxLayout() - useDefaultLabelQHBoxLayout.addWidget(self.useDefaultLabelCheckbox) - useDefaultLabelQHBoxLayout.addWidget(self.defaultLabelTextLine) - useDefaultLabelContainer = QWidget() - useDefaultLabelContainer.setLayout(useDefaultLabelQHBoxLayout) + self.use_default_label_checkbox = QCheckBox(get_str('useDefaultLabel')) + self.use_default_label_checkbox.setChecked(False) + self.default_label_text_line = QLineEdit() + use_default_label_qhbox_layout = QHBoxLayout() + use_default_label_qhbox_layout.addWidget(self.use_default_label_checkbox) + use_default_label_qhbox_layout.addWidget(self.default_label_text_line) + use_default_label_container = QWidget() + use_default_label_container.setLayout(use_default_label_qhbox_layout) # Create a widget for edit and diffc button - self.diffcButton = QCheckBox(getStr('useDifficult')) - self.diffcButton.setChecked(False) - self.diffcButton.stateChanged.connect(self.btnstate) - self.editButton = QToolButton() - self.editButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) + self.diffc_button = QCheckBox(get_str('useDifficult')) + self.diffc_button.setChecked(False) + self.diffc_button.stateChanged.connect(self.button_state) + self.edit_button = QToolButton() + self.edit_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) - # Add some of widgets to listLayout - listLayout.addWidget(self.editButton) - listLayout.addWidget(self.diffcButton) - listLayout.addWidget(useDefaultLabelContainer) + # Add some of widgets to list_layout + list_layout.addWidget(self.edit_button) + list_layout.addWidget(self.diffc_button) + list_layout.addWidget(use_default_label_container) # Create and add combobox for showing unique labels in group - self.comboBox = ComboBox(self) - listLayout.addWidget(self.comboBox) + self.combo_box = ComboBox(self) + list_layout.addWidget(self.combo_box) # Create and add a widget for showing current label items - self.labelList = QListWidget() - labelListContainer = QWidget() - labelListContainer.setLayout(listLayout) - self.labelList.itemActivated.connect(self.labelSelectionChanged) - self.labelList.itemSelectionChanged.connect(self.labelSelectionChanged) - self.labelList.itemDoubleClicked.connect(self.editLabel) + self.label_list = QListWidget() + label_list_container = QWidget() + label_list_container.setLayout(list_layout) + self.label_list.itemActivated.connect(self.label_selection_changed) + self.label_list.itemSelectionChanged.connect(self.label_selection_changed) + self.label_list.itemDoubleClicked.connect(self.edit_label) # Connect to itemChanged to detect checkbox changes. - self.labelList.itemChanged.connect(self.labelItemChanged) - listLayout.addWidget(self.labelList) + self.label_list.itemChanged.connect(self.label_item_changed) + list_layout.addWidget(self.label_list) - self.dock = QDockWidget(getStr('boxLabelText'), self) - self.dock.setObjectName(getStr('labels')) - self.dock.setWidget(labelListContainer) + self.dock = QDockWidget(get_str('boxLabelText'), self) + self.dock.setObjectName(get_str('labels')) + self.dock.setWidget(label_list_container) - self.fileListWidget = QListWidget() - self.fileListWidget.itemDoubleClicked.connect(self.fileitemDoubleClicked) - filelistLayout = QVBoxLayout() - filelistLayout.setContentsMargins(0, 0, 0, 0) - filelistLayout.addWidget(self.fileListWidget) - fileListContainer = QWidget() - fileListContainer.setLayout(filelistLayout) - self.filedock = QDockWidget(getStr('fileList'), self) - self.filedock.setObjectName(getStr('files')) - self.filedock.setWidget(fileListContainer) + self.file_list_widget = QListWidget() + self.file_list_widget.itemDoubleClicked.connect(self.file_item_double_clicked) + file_list_layout = QVBoxLayout() + file_list_layout.setContentsMargins(0, 0, 0, 0) + file_list_layout.addWidget(self.file_list_widget) + file_list_container = QWidget() + file_list_container.setLayout(file_list_layout) + self.file_dock = QDockWidget(get_str('fileList'), self) + self.file_dock.setObjectName(get_str('files')) + self.file_dock.setWidget(file_list_container) - self.zoomWidget = ZoomWidget() - self.colorDialog = ColorDialog(parent=self) + self.zoom_widget = ZoomWidget() + self.color_dialog = ColorDialog(parent=self) self.canvas = Canvas(parent=self) - self.canvas.zoomRequest.connect(self.zoomRequest) - self.canvas.setDrawingShapeToSquare(settings.get(SETTING_DRAW_SQUARE, False)) + self.canvas.zoomRequest.connect(self.zoom_request) + self.canvas.set_drawing_shape_to_square(settings.get(SETTING_DRAW_SQUARE, False)) scroll = QScrollArea() scroll.setWidget(self.canvas) scroll.setWidgetResizable(True) - self.scrollBars = { + self.scroll_bars = { Qt.Vertical: scroll.verticalScrollBar(), Qt.Horizontal: scroll.horizontalScrollBar() } - self.scrollArea = scroll - self.canvas.scrollRequest.connect(self.scrollRequest) + self.scroll_area = scroll + self.canvas.scrollRequest.connect(self.scroll_request) - self.canvas.newShape.connect(self.newShape) - self.canvas.shapeMoved.connect(self.setDirty) - self.canvas.selectionChanged.connect(self.shapeSelectionChanged) - self.canvas.drawingPolygon.connect(self.toggleDrawingSensitive) + self.canvas.newShape.connect(self.new_shape) + self.canvas.shapeMoved.connect(self.set_dirty) + self.canvas.selectionChanged.connect(self.shape_selection_changed) + self.canvas.drawingPolygon.connect(self.toggle_drawing_sensitive) self.setCentralWidget(scroll) self.addDockWidget(Qt.RightDockWidgetArea, self.dock) - self.addDockWidget(Qt.RightDockWidgetArea, self.filedock) - self.filedock.setFeatures(QDockWidget.DockWidgetFloatable) + self.addDockWidget(Qt.RightDockWidgetArea, self.file_dock) + self.file_dock.setFeatures(QDockWidget.DockWidgetFloatable) - self.dockFeatures = QDockWidget.DockWidgetClosable | QDockWidget.DockWidgetFloatable - self.dock.setFeatures(self.dock.features() ^ self.dockFeatures) + self.dock_features = QDockWidget.DockWidgetClosable | QDockWidget.DockWidgetFloatable + self.dock.setFeatures(self.dock.features() ^ self.dock_features) # Actions - action = partial(newAction, self) - quit = action(getStr('quit'), self.close, - 'Ctrl+Q', 'quit', getStr('quitApp')) + action = partial(new_action, self) + quit = action(get_str('quit'), self.close, + 'Ctrl+Q', 'quit', get_str('quitApp')) - open = action(getStr('openFile'), self.openFile, - 'Ctrl+O', 'open', getStr('openFileDetail')) + open = action(get_str('openFile'), self.open_file, + 'Ctrl+O', 'open', get_str('openFileDetail')) - opendir = action(getStr('openDir'), self.openDirDialog, - 'Ctrl+u', 'open', getStr('openDir')) + open_dir = action(get_str('openDir'), self.open_dir_dialog, + 'Ctrl+u', 'open', get_str('openDir')) - copyPrevBounding = action(getStr('copyPrevBounding'), self.copyPreviousBoundingBoxes, - 'Ctrl+v', 'paste', getStr('copyPrevBounding')) + copy_prev_bounding = action(get_str('copyPrevBounding'), self.copy_previous_bounding_boxes, + 'Ctrl+v', 'paste', get_str('copyPrevBounding')) - changeSavedir = action(getStr('changeSaveDir'), self.changeSavedirDialog, - 'Ctrl+r', 'open', getStr('changeSavedAnnotationDir')) + change_save_dir = action(get_str('changeSaveDir'), self.change_save_dir_dialog, + 'Ctrl+r', 'open', get_str('changeSavedAnnotationDir')) - openAnnotation = action(getStr('openAnnotation'), self.openAnnotationDialog, - 'Ctrl+Shift+O', 'open', getStr('openAnnotationDetail')) + open_annotation = action(get_str('openAnnotation'), self.open_annotation_dialog, + 'Ctrl+Shift+O', 'open', get_str('openAnnotationDetail')) - openNextImg = action(getStr('nextImg'), self.openNextImg, - 'd', 'next', getStr('nextImgDetail')) + open_next_image = action(get_str('nextImg'), self.open_next_image, + 'd', 'next', get_str('nextImgDetail')) - openPrevImg = action(getStr('prevImg'), self.openPrevImg, - 'a', 'prev', getStr('prevImgDetail')) + open_prev_image = action(get_str('prevImg'), self.open_prev_image, + 'a', 'prev', get_str('prevImgDetail')) - verify = action(getStr('verifyImg'), self.verifyImg, - 'space', 'verify', getStr('verifyImgDetail')) + verify = action(get_str('verifyImg'), self.verify_image, + 'space', 'verify', get_str('verifyImgDetail')) - save = action(getStr('save'), self.saveFile, - 'Ctrl+S', 'save', getStr('saveDetail'), enabled=False) + save = action(get_str('save'), self.save_file, + 'Ctrl+S', 'save', get_str('saveDetail'), enabled=False) - def getFormatMeta(format): + def get_format_meta(format): """ returns a tuple containing (title, icon_name) of the selected format """ if format == LabelFileFormat.PASCAL_VOC: - return ('&PascalVOC', 'format_voc') + return '&PascalVOC', 'format_voc' elif format == LabelFileFormat.YOLO: - return ('&YOLO', 'format_yolo') + return '&YOLO', 'format_yolo' elif format == LabelFileFormat.CREATE_ML: - return ('&CreateML', 'format_createml') + return '&CreateML', 'format_createml' - save_format = action(getFormatMeta(self.labelFileFormat)[0], + save_format = action(get_format_meta(self.label_file_format)[0], self.change_format, 'Ctrl+', - getFormatMeta(self.labelFileFormat)[1], - getStr('changeSaveFormat'), enabled=True) + get_format_meta(self.label_file_format)[1], + get_str('changeSaveFormat'), enabled=True) - saveAs = action(getStr('saveAs'), self.saveFileAs, - 'Ctrl+Shift+S', 'save-as', getStr('saveAsDetail'), enabled=False) + save_as = action(get_str('saveAs'), self.save_file_as, + 'Ctrl+Shift+S', 'save-as', get_str('saveAsDetail'), enabled=False) - close = action(getStr('closeCur'), self.closeFile, 'Ctrl+W', 'close', getStr('closeCurDetail')) + close = action(get_str('closeCur'), self.close_file, 'Ctrl+W', 'close', get_str('closeCurDetail')) - deleteImg = action(getStr('deleteImg'), self.deleteImg, 'Ctrl+Shift+D', 'close', getStr('deleteImgDetail')) + delete_image = action(get_str('deleteImg'), self.delete_image, 'Ctrl+Shift+D', 'close', get_str('deleteImgDetail')) - resetAll = action(getStr('resetAll'), self.resetAll, None, 'resetall', getStr('resetAllDetail')) + reset_all = action(get_str('resetAll'), self.reset_all, None, 'resetall', get_str('resetAllDetail')) - color1 = action(getStr('boxLineColor'), self.chooseColor1, - 'Ctrl+L', 'color_line', getStr('boxLineColorDetail')) + color1 = action(get_str('boxLineColor'), self.choose_color1, + 'Ctrl+L', 'color_line', get_str('boxLineColorDetail')) - createMode = action(getStr('crtBox'), self.setCreateMode, - 'w', 'new', getStr('crtBoxDetail'), enabled=False) - editMode = action('&Edit\nRectBox', self.setEditMode, - 'Ctrl+J', 'edit', u'Move and edit Boxs', enabled=False) + create_mode = action(get_str('crtBox'), self.set_create_mode, + 'w', 'new', get_str('crtBoxDetail'), enabled=False) + edit_mode = action('&Edit\nRectBox', self.set_edit_mode, + 'Ctrl+J', 'edit', u'Move and edit Boxs', enabled=False) - create = action(getStr('crtBox'), self.createShape, - 'w', 'new', getStr('crtBoxDetail'), enabled=False) - delete = action(getStr('delBox'), self.deleteSelectedShape, - 'Delete', 'delete', getStr('delBoxDetail'), enabled=False) - copy = action(getStr('dupBox'), self.copySelectedShape, - 'Ctrl+D', 'copy', getStr('dupBoxDetail'), + create = action(get_str('crtBox'), self.create_shape, + 'w', 'new', get_str('crtBoxDetail'), enabled=False) + delete = action(get_str('delBox'), self.delete_selected_shape, + 'Delete', 'delete', get_str('delBoxDetail'), enabled=False) + copy = action(get_str('dupBox'), self.copy_selected_shape, + 'Ctrl+D', 'copy', get_str('dupBoxDetail'), enabled=False) - advancedMode = action(getStr('advancedMode'), self.toggleAdvancedMode, - 'Ctrl+Shift+A', 'expert', getStr('advancedModeDetail'), - checkable=True) + advanced_mode = action(get_str('advancedMode'), self.toggle_advanced_mode, + 'Ctrl+Shift+A', 'expert', get_str('advancedModeDetail'), + checkable=True) - hideAll = action('&Hide\nRectBox', partial(self.togglePolygons, False), - 'Ctrl+H', 'hide', getStr('hideAllBoxDetail'), - enabled=False) - showAll = action('&Show\nRectBox', partial(self.togglePolygons, True), - 'Ctrl+A', 'hide', getStr('showAllBoxDetail'), - enabled=False) + hide_all = action('&Hide\nRectBox', partial(self.toggle_polygons, False), + 'Ctrl+H', 'hide', get_str('hideAllBoxDetail'), + enabled=False) + show_all = action('&Show\nRectBox', partial(self.toggle_polygons, True), + 'Ctrl+A', 'hide', get_str('showAllBoxDetail'), + enabled=False) - help = action(getStr('tutorial'), self.showTutorialDialog, None, 'help', getStr('tutorialDetail')) - showInfo = action(getStr('info'), self.showInfoDialog, None, 'help', getStr('info')) + help = action(get_str('tutorial'), self.show_tutorial_dialog, None, 'help', get_str('tutorialDetail')) + show_info = action(get_str('info'), self.show_info_dialog, None, 'help', get_str('info')) zoom = QWidgetAction(self) - zoom.setDefaultWidget(self.zoomWidget) - self.zoomWidget.setWhatsThis( + zoom.setDefaultWidget(self.zoom_widget) + self.zoom_widget.setWhatsThis( u"Zoom in or out of the image. Also accessible with" - " %s and %s from the canvas." % (fmtShortcut("Ctrl+[-+]"), - fmtShortcut("Ctrl+Wheel"))) - self.zoomWidget.setEnabled(False) - - zoomIn = action(getStr('zoomin'), partial(self.addZoom, 10), - 'Ctrl++', 'zoom-in', getStr('zoominDetail'), enabled=False) - zoomOut = action(getStr('zoomout'), partial(self.addZoom, -10), - 'Ctrl+-', 'zoom-out', getStr('zoomoutDetail'), enabled=False) - zoomOrg = action(getStr('originalsize'), partial(self.setZoom, 100), - 'Ctrl+=', 'zoom', getStr('originalsizeDetail'), enabled=False) - fitWindow = action(getStr('fitWin'), self.setFitWindow, - 'Ctrl+F', 'fit-window', getStr('fitWinDetail'), + " %s and %s from the canvas." % (format_shortcut("Ctrl+[-+]"), + format_shortcut("Ctrl+Wheel"))) + self.zoom_widget.setEnabled(False) + + zoom_in = action(get_str('zoomin'), partial(self.add_zoom, 10), + 'Ctrl++', 'zoom-in', get_str('zoominDetail'), enabled=False) + zoom_out = action(get_str('zoomout'), partial(self.add_zoom, -10), + 'Ctrl+-', 'zoom-out', get_str('zoomoutDetail'), enabled=False) + zoom_org = action(get_str('originalsize'), partial(self.set_zoom, 100), + 'Ctrl+=', 'zoom', get_str('originalsizeDetail'), enabled=False) + fit_window = action(get_str('fitWin'), self.set_fit_window, + 'Ctrl+F', 'fit-window', get_str('fitWinDetail'), + checkable=True, enabled=False) + fit_width = action(get_str('fitWidth'), self.set_fit_width, + 'Ctrl+Shift+F', 'fit-width', get_str('fitWidthDetail'), checkable=True, enabled=False) - fitWidth = action(getStr('fitWidth'), self.setFitWidth, - 'Ctrl+Shift+F', 'fit-width', getStr('fitWidthDetail'), - checkable=True, enabled=False) # Group zoom controls into a list for easier toggling. - zoomActions = (self.zoomWidget, zoomIn, zoomOut, - zoomOrg, fitWindow, fitWidth) - self.zoomMode = self.MANUAL_ZOOM + zoom_actions = (self.zoom_widget, zoom_in, zoom_out, + zoom_org, fit_window, fit_width) + self.zoom_mode = self.MANUAL_ZOOM self.scalers = { - self.FIT_WINDOW: self.scaleFitWindow, - self.FIT_WIDTH: self.scaleFitWidth, + self.FIT_WINDOW: self.scale_fit_window, + self.FIT_WIDTH: self.scale_fit_width, # Set to one to scale to 100% when loading files. self.MANUAL_ZOOM: lambda: 1, } - edit = action(getStr('editLabel'), self.editLabel, - 'Ctrl+E', 'edit', getStr('editLabelDetail'), + edit = action(get_str('editLabel'), self.edit_label, + 'Ctrl+E', 'edit', get_str('editLabelDetail'), enabled=False) - self.editButton.setDefaultAction(edit) + self.edit_button.setDefaultAction(edit) - shapeLineColor = action(getStr('shapeLineColor'), self.chshapeLineColor, - icon='color_line', tip=getStr('shapeLineColorDetail'), - enabled=False) - shapeFillColor = action(getStr('shapeFillColor'), self.chshapeFillColor, - icon='color', tip=getStr('shapeFillColorDetail'), - enabled=False) + shape_line_color = action(get_str('shapeLineColor'), self.choose_shape_line_color, + icon='color_line', tip=get_str('shapeLineColorDetail'), + enabled=False) + shape_fill_color = action(get_str('shapeFillColor'), self.choose_shape_fill_color, + icon='color', tip=get_str('shapeFillColorDetail'), + enabled=False) labels = self.dock.toggleViewAction() - labels.setText(getStr('showHide')) + labels.setText(get_str('showHide')) labels.setShortcut('Ctrl+Shift+L') # Label list context menu. - labelMenu = QMenu() - addActions(labelMenu, (edit, delete)) - self.labelList.setContextMenuPolicy(Qt.CustomContextMenu) - self.labelList.customContextMenuRequested.connect( - self.popLabelListMenu) + label_menu = QMenu() + add_actions(label_menu, (edit, delete)) + self.label_list.setContextMenuPolicy(Qt.CustomContextMenu) + self.label_list.customContextMenuRequested.connect( + self.pop_label_list_menu) # Draw squares/rectangles - self.drawSquaresOption = QAction('Draw Squares', self) - self.drawSquaresOption.setShortcut('Ctrl+Shift+R') - self.drawSquaresOption.setCheckable(True) - self.drawSquaresOption.setChecked(settings.get(SETTING_DRAW_SQUARE, False)) - self.drawSquaresOption.triggered.connect(self.toogleDrawSquare) + self.draw_squares_option = QAction('Draw Squares', self) + self.draw_squares_option.setShortcut('Ctrl+Shift+R') + self.draw_squares_option.setCheckable(True) + self.draw_squares_option.setChecked(settings.get(SETTING_DRAW_SQUARE, False)) + self.draw_squares_option.triggered.connect(self.toggle_draw_square) # Store actions for further handling. - self.actions = struct(save=save, save_format=save_format, saveAs=saveAs, open=open, close=close, resetAll = resetAll, deleteImg = deleteImg, + self.actions = Struct(save=save, save_format=save_format, saveAs=save_as, open=open, close=close, resetAll=reset_all, deleteImg=delete_image, lineColor=color1, create=create, delete=delete, edit=edit, copy=copy, - createMode=createMode, editMode=editMode, advancedMode=advancedMode, - shapeLineColor=shapeLineColor, shapeFillColor=shapeFillColor, - zoom=zoom, zoomIn=zoomIn, zoomOut=zoomOut, zoomOrg=zoomOrg, - fitWindow=fitWindow, fitWidth=fitWidth, - zoomActions=zoomActions, + createMode=create_mode, editMode=edit_mode, advancedMode=advanced_mode, + shapeLineColor=shape_line_color, shapeFillColor=shape_fill_color, + zoom=zoom, zoomIn=zoom_in, zoomOut=zoom_out, zoomOrg=zoom_org, + fitWindow=fit_window, fitWidth=fit_width, + zoomActions=zoom_actions, fileMenuActions=( - open, opendir, save, saveAs, close, resetAll, quit), + open, open_dir, save, save_as, close, reset_all, quit), beginner=(), advanced=(), editMenu=(edit, copy, delete, - None, color1, self.drawSquaresOption), + None, color1, self.draw_squares_option), beginnerContext=(create, edit, copy, delete), - advancedContext=(createMode, editMode, edit, copy, - delete, shapeLineColor, shapeFillColor), + advancedContext=(create_mode, edit_mode, edit, copy, + delete, shape_line_color, shape_fill_color), onLoadActive=( - close, create, createMode, editMode), - onShapesPresent=(saveAs, hideAll, showAll)) + close, create, create_mode, edit_mode), + onShapesPresent=(save_as, hide_all, show_all)) - self.menus = struct( - file=self.menu(getStr('menu_file')), - edit=self.menu(getStr('menu_edit')), - view=self.menu(getStr('menu_view')), - help=self.menu(getStr('menu_help')), - recentFiles=QMenu(getStr('menu_openRecent')), - labelList=labelMenu) + self.menus = Struct( + file=self.menu(get_str('menu_file')), + edit=self.menu(get_str('menu_edit')), + view=self.menu(get_str('menu_view')), + help=self.menu(get_str('menu_help')), + recentFiles=QMenu(get_str('menu_openRecent')), + labelList=label_menu) # Auto saving : Enable auto saving if pressing next - self.autoSaving = QAction(getStr('autoSaveMode'), self) - self.autoSaving.setCheckable(True) - self.autoSaving.setChecked(settings.get(SETTING_AUTO_SAVE, False)) + self.auto_saving = QAction(get_str('autoSaveMode'), self) + self.auto_saving.setCheckable(True) + self.auto_saving.setChecked(settings.get(SETTING_AUTO_SAVE, False)) # Sync single class mode from PR#106 - self.singleClassMode = QAction(getStr('singleClsMode'), self) - self.singleClassMode.setShortcut("Ctrl+Shift+S") - self.singleClassMode.setCheckable(True) - self.singleClassMode.setChecked(settings.get(SETTING_SINGLE_CLASS, False)) + self.single_class_mode = QAction(get_str('singleClsMode'), self) + self.single_class_mode.setShortcut("Ctrl+Shift+S") + self.single_class_mode.setCheckable(True) + self.single_class_mode.setChecked(settings.get(SETTING_SINGLE_CLASS, False)) self.lastLabel = None # Add option to enable/disable labels being displayed at the top of bounding boxes - self.displayLabelOption = QAction(getStr('displayLabel'), self) - self.displayLabelOption.setShortcut("Ctrl+Shift+P") - self.displayLabelOption.setCheckable(True) - self.displayLabelOption.setChecked(settings.get(SETTING_PAINT_LABEL, False)) - self.displayLabelOption.triggered.connect(self.togglePaintLabelsOption) - - addActions(self.menus.file, - (open, opendir, copyPrevBounding, changeSavedir, openAnnotation, self.menus.recentFiles, save, save_format, saveAs, close, resetAll, deleteImg, quit)) - addActions(self.menus.help, (help, showInfo)) - addActions(self.menus.view, ( - self.autoSaving, - self.singleClassMode, - self.displayLabelOption, - labels, advancedMode, None, - hideAll, showAll, None, - zoomIn, zoomOut, zoomOrg, None, - fitWindow, fitWidth)) - - self.menus.file.aboutToShow.connect(self.updateFileMenu) + self.display_label_option = QAction(get_str('displayLabel'), self) + self.display_label_option.setShortcut("Ctrl+Shift+P") + self.display_label_option.setCheckable(True) + self.display_label_option.setChecked(settings.get(SETTING_PAINT_LABEL, False)) + self.display_label_option.triggered.connect(self.toggle_paint_labels_option) + + add_actions(self.menus.file, + (open, open_dir, copy_prev_bounding, change_save_dir, open_annotation, self.menus.recentFiles, save, save_format, save_as, close, reset_all, delete_image, quit)) + add_actions(self.menus.help, (help, show_info)) + add_actions(self.menus.view, ( + self.auto_saving, + self.single_class_mode, + self.display_label_option, + labels, advanced_mode, None, + hide_all, show_all, None, + zoom_in, zoom_out, zoom_org, None, + fit_window, fit_width)) + + self.menus.file.aboutToShow.connect(self.update_file_menu) # Custom context menu for the canvas widget: - addActions(self.canvas.menus[0], self.actions.beginnerContext) - addActions(self.canvas.menus[1], ( - action('&Copy here', self.copyShape), - action('&Move here', self.moveShape))) + add_actions(self.canvas.menus[0], self.actions.beginnerContext) + add_actions(self.canvas.menus[1], ( + action('&Copy here', self.copy_shape), + action('&Move here', self.move_shape))) self.tools = self.toolbar('Tools') self.actions.beginner = ( - open, opendir, changeSavedir, openNextImg, openPrevImg, verify, save, save_format, None, create, copy, delete, None, - zoomIn, zoom, zoomOut, fitWindow, fitWidth) + open, open_dir, change_save_dir, open_next_image, open_prev_image, verify, save, save_format, None, create, copy, delete, None, + zoom_in, zoom, zoom_out, fit_window, fit_width) self.actions.advanced = ( - open, opendir, changeSavedir, openNextImg, openPrevImg, save, save_format, None, - createMode, editMode, None, - hideAll, showAll) + open, open_dir, change_save_dir, open_next_image, open_prev_image, save, save_format, None, + create_mode, edit_mode, None, + hide_all, show_all) self.statusBar().showMessage('%s started.' % __appname__) self.statusBar().show() # Application state. self.image = QImage() - self.filePath = ustr(defaultFilename) - self.lastOpenDir= None - self.recentFiles = [] - self.maxRecent = 7 - self.lineColor = None - self.fillColor = None + self.file_path = ustr(default_filename) + self.last_open_dir = None + self.recent_files = [] + self.max_recent = 7 + self.line_color = None + self.fill_color = None self.zoom_level = 100 self.fit_window = False # Add Chris self.difficult = False - ## Fix the compatible issue for qt4 and qt5. Convert the QStringList to python list + # Fix the compatible issue for qt4 and qt5. Convert the QStringList to python list if settings.get(SETTING_RECENT_FILES): if have_qstring(): - recentFileQStringList = settings.get(SETTING_RECENT_FILES) - self.recentFiles = [ustr(i) for i in recentFileQStringList] + recent_file_qstring_list = settings.get(SETTING_RECENT_FILES) + self.recent_files = [ustr(i) for i in recent_file_qstring_list] else: - self.recentFiles = recentFileQStringList = settings.get(SETTING_RECENT_FILES) + self.recent_files = recent_file_qstring_list = settings.get(SETTING_RECENT_FILES) size = settings.get(SETTING_WIN_SIZE, QSize(600, 500)) position = QPoint(0, 0) @@ -459,18 +459,18 @@ def getFormatMeta(format): break self.resize(size) self.move(position) - saveDir = ustr(settings.get(SETTING_SAVE_DIR, None)) - self.lastOpenDir = ustr(settings.get(SETTING_LAST_OPEN_DIR, None)) - if self.defaultSaveDir is None and saveDir is not None and os.path.exists(saveDir): - self.defaultSaveDir = saveDir + save_dir = ustr(settings.get(SETTING_SAVE_DIR, None)) + self.last_open_dir = ustr(settings.get(SETTING_LAST_OPEN_DIR, None)) + if self.default_save_dir is None and save_dir is not None and os.path.exists(save_dir): + self.default_save_dir = save_dir self.statusBar().showMessage('%s started. Annotation will be saved to %s' % - (__appname__, self.defaultSaveDir)) + (__appname__, self.default_save_dir)) self.statusBar().show() self.restoreState(settings.get(SETTING_WIN_STATE, QByteArray())) - Shape.line_color = self.lineColor = QColor(settings.get(SETTING_LINE_COLOR, DEFAULT_LINE_COLOR)) - Shape.fill_color = self.fillColor = QColor(settings.get(SETTING_FILL_COLOR, DEFAULT_FILL_COLOR)) - self.canvas.setDrawingColor(self.lineColor) + Shape.line_color = self.line_color = QColor(settings.get(SETTING_LINE_COLOR, DEFAULT_LINE_COLOR)) + Shape.fill_color = self.fill_color = QColor(settings.get(SETTING_FILL_COLOR, DEFAULT_FILL_COLOR)) + self.canvas.set_drawing_color(self.line_color) # Add chris Shape.difficult = self.difficult @@ -481,152 +481,152 @@ def xbool(x): if xbool(settings.get(SETTING_ADVANCE_MODE, False)): self.actions.advancedMode.setChecked(True) - self.toggleAdvancedMode() + self.toggle_advanced_mode() # Populate the File menu dynamically. - self.updateFileMenu() + self.update_file_menu() # Since loading the file may take some time, make sure it runs in the background. - if self.filePath and os.path.isdir(self.filePath): - self.queueEvent(partial(self.importDirImages, self.filePath or "")) - elif self.filePath: - self.queueEvent(partial(self.loadFile, self.filePath or "")) + if self.file_path and os.path.isdir(self.file_path): + self.queue_event(partial(self.import_dir_images, self.file_path or "")) + elif self.file_path: + self.queue_event(partial(self.load_file, self.file_path or "")) # Callbacks: - self.zoomWidget.valueChanged.connect(self.paintCanvas) + self.zoom_widget.valueChanged.connect(self.paint_canvas) - self.populateModeActions() + self.populate_mode_actions() # Display cursor coordinates at the right of status bar - self.labelCoordinates = QLabel('') - self.statusBar().addPermanentWidget(self.labelCoordinates) + self.label_coordinates = QLabel('') + self.statusBar().addPermanentWidget(self.label_coordinates) - # Open Dir if deafult file - if self.filePath and os.path.isdir(self.filePath): - self.openDirDialog(dirpath=self.filePath, silent=True) + # Open Dir if default file + if self.file_path and os.path.isdir(self.file_path): + self.open_dir_dialog(dir_path=self.file_path, silent=True) def keyReleaseEvent(self, event): if event.key() == Qt.Key_Control: - self.canvas.setDrawingShapeToSquare(False) + self.canvas.set_drawing_shape_to_square(False) def keyPressEvent(self, event): if event.key() == Qt.Key_Control: # Draw rectangle if Ctrl is pressed - self.canvas.setDrawingShapeToSquare(True) + self.canvas.set_drawing_shape_to_square(True) - ## Support Functions ## + # Support Functions # def set_format(self, save_format): if save_format == FORMAT_PASCALVOC: self.actions.save_format.setText(FORMAT_PASCALVOC) - self.actions.save_format.setIcon(newIcon("format_voc")) - self.labelFileFormat = LabelFileFormat.PASCAL_VOC + self.actions.save_format.setIcon(new_icon("format_voc")) + self.label_file_format = LabelFileFormat.PASCAL_VOC LabelFile.suffix = XML_EXT elif save_format == FORMAT_YOLO: self.actions.save_format.setText(FORMAT_YOLO) - self.actions.save_format.setIcon(newIcon("format_yolo")) - self.labelFileFormat = LabelFileFormat.YOLO + self.actions.save_format.setIcon(new_icon("format_yolo")) + self.label_file_format = LabelFileFormat.YOLO LabelFile.suffix = TXT_EXT elif save_format == FORMAT_CREATEML: self.actions.save_format.setText(FORMAT_CREATEML) - self.actions.save_format.setIcon(newIcon("format_createml")) - self.labelFileFormat = LabelFileFormat.CREATE_ML + self.actions.save_format.setIcon(new_icon("format_createml")) + self.label_file_format = LabelFileFormat.CREATE_ML LabelFile.suffix = JSON_EXT def change_format(self): - if self.labelFileFormat == LabelFileFormat.PASCAL_VOC: + if self.label_file_format == LabelFileFormat.PASCAL_VOC: self.set_format(FORMAT_YOLO) - elif self.labelFileFormat == LabelFileFormat.YOLO: + elif self.label_file_format == LabelFileFormat.YOLO: self.set_format(FORMAT_CREATEML) - elif self.labelFileFormat == LabelFileFormat.CREATE_ML: + elif self.label_file_format == LabelFileFormat.CREATE_ML: self.set_format(FORMAT_PASCALVOC) else: raise ValueError('Unknown label file format.') - self.setDirty() + self.set_dirty() - def noShapes(self): - return not self.itemsToShapes + def no_shapes(self): + return not self.items_to_shapes - def toggleAdvancedMode(self, value=True): + def toggle_advanced_mode(self, value=True): self._beginner = not value - self.canvas.setEditing(True) - self.populateModeActions() - self.editButton.setVisible(not value) + self.canvas.set_editing(True) + self.populate_mode_actions() + self.edit_button.setVisible(not value) if value: self.actions.createMode.setEnabled(True) self.actions.editMode.setEnabled(False) - self.dock.setFeatures(self.dock.features() | self.dockFeatures) + self.dock.setFeatures(self.dock.features() | self.dock_features) else: - self.dock.setFeatures(self.dock.features() ^ self.dockFeatures) + self.dock.setFeatures(self.dock.features() ^ self.dock_features) - def populateModeActions(self): + def populate_mode_actions(self): if self.beginner(): tool, menu = self.actions.beginner, self.actions.beginnerContext else: tool, menu = self.actions.advanced, self.actions.advancedContext self.tools.clear() - addActions(self.tools, tool) + add_actions(self.tools, tool) self.canvas.menus[0].clear() - addActions(self.canvas.menus[0], menu) + add_actions(self.canvas.menus[0], menu) self.menus.edit.clear() actions = (self.actions.create,) if self.beginner()\ else (self.actions.createMode, self.actions.editMode) - addActions(self.menus.edit, actions + self.actions.editMenu) + add_actions(self.menus.edit, actions + self.actions.editMenu) - def setBeginner(self): + def set_beginner(self): self.tools.clear() - addActions(self.tools, self.actions.beginner) + add_actions(self.tools, self.actions.beginner) - def setAdvanced(self): + def set_advanced(self): self.tools.clear() - addActions(self.tools, self.actions.advanced) + add_actions(self.tools, self.actions.advanced) - def setDirty(self): + def set_dirty(self): self.dirty = True self.actions.save.setEnabled(True) - def setClean(self): + def set_clean(self): self.dirty = False self.actions.save.setEnabled(False) self.actions.create.setEnabled(True) - def toggleActions(self, value=True): + def toggle_actions(self, value=True): """Enable/Disable widgets which depend on an opened image.""" for z in self.actions.zoomActions: z.setEnabled(value) for action in self.actions.onLoadActive: action.setEnabled(value) - def queueEvent(self, function): + def queue_event(self, function): QTimer.singleShot(0, function) def status(self, message, delay=5000): self.statusBar().showMessage(message, delay) - def resetState(self): - self.itemsToShapes.clear() - self.shapesToItems.clear() - self.labelList.clear() - self.filePath = None - self.imageData = None - self.labelFile = None - self.canvas.resetState() - self.labelCoordinates.clear() - self.comboBox.cb.clear() - - def currentItem(self): - items = self.labelList.selectedItems() + def reset_state(self): + self.items_to_shapes.clear() + self.shapes_to_items.clear() + self.label_list.clear() + self.file_path = None + self.image_data = None + self.label_file = None + self.canvas.reset_state() + self.label_coordinates.clear() + self.combo_box.cb.clear() + + def current_item(self): + items = self.label_list.selectedItems() if items: return items[0] return None - def addRecentFile(self, filePath): - if filePath in self.recentFiles: - self.recentFiles.remove(filePath) - elif len(self.recentFiles) >= self.maxRecent: - self.recentFiles.pop() - self.recentFiles.insert(0, filePath) + def add_recent_file(self, file_path): + if file_path in self.recent_files: + self.recent_files.remove(file_path) + elif len(self.recent_files) >= self.max_recent: + self.recent_files.pop() + self.recent_files.insert(0, file_path) def beginner(self): return self._beginner @@ -634,172 +634,172 @@ def beginner(self): def advanced(self): return not self.beginner() - def getAvailableScreencastViewer(self): - osName = platform.system() + def get_available_screencast_viewer(self): + os_name = platform.system() - if osName == 'Windows': + if os_name == 'Windows': return ['C:\\Program Files\\Internet Explorer\\iexplore.exe'] - elif osName == 'Linux': + elif os_name == 'Linux': return ['xdg-open'] - elif osName == 'Darwin': + elif os_name == 'Darwin': return ['open'] - ## Callbacks ## - def showTutorialDialog(self): - subprocess.Popen(self.screencastViewer + [self.screencast]) + # Callbacks # + def show_tutorial_dialog(self): + subprocess.Popen(self.screencast_viewer + [self.screencast]) - def showInfoDialog(self): + def show_info_dialog(self): from libs.__init__ import __version__ msg = u'Name:{0} \nApp Version:{1} \n{2} '.format(__appname__, __version__, sys.version_info) QMessageBox.information(self, u'Information', msg) - def createShape(self): + def create_shape(self): assert self.beginner() - self.canvas.setEditing(False) + self.canvas.set_editing(False) self.actions.create.setEnabled(False) - def toggleDrawingSensitive(self, drawing=True): + def toggle_drawing_sensitive(self, drawing=True): """In the middle of drawing, toggling between modes should be disabled.""" self.actions.editMode.setEnabled(not drawing) if not drawing and self.beginner(): # Cancel creation. print('Cancel creation.') - self.canvas.setEditing(True) - self.canvas.restoreCursor() + self.canvas.set_editing(True) + self.canvas.restore_cursor() self.actions.create.setEnabled(True) - def toggleDrawMode(self, edit=True): - self.canvas.setEditing(edit) + def toggle_draw_mode(self, edit=True): + self.canvas.set_editing(edit) self.actions.createMode.setEnabled(edit) self.actions.editMode.setEnabled(not edit) - def setCreateMode(self): + def set_create_mode(self): assert self.advanced() - self.toggleDrawMode(False) + self.toggle_draw_mode(False) - def setEditMode(self): + def set_edit_mode(self): assert self.advanced() - self.toggleDrawMode(True) - self.labelSelectionChanged() + self.toggle_draw_mode(True) + self.label_selection_changed() - def updateFileMenu(self): - currFilePath = self.filePath + def update_file_menu(self): + curr_file_path = self.file_path def exists(filename): return os.path.exists(filename) menu = self.menus.recentFiles menu.clear() - files = [f for f in self.recentFiles if f != - currFilePath and exists(f)] + files = [f for f in self.recent_files if f != + curr_file_path and exists(f)] for i, f in enumerate(files): - icon = newIcon('labels') + icon = new_icon('labels') action = QAction( icon, '&%d %s' % (i + 1, QFileInfo(f).fileName()), self) - action.triggered.connect(partial(self.loadRecent, f)) + action.triggered.connect(partial(self.load_recent, f)) menu.addAction(action) - def popLabelListMenu(self, point): - self.menus.labelList.exec_(self.labelList.mapToGlobal(point)) + def pop_label_list_menu(self, point): + self.menus.labelList.exec_(self.label_list.mapToGlobal(point)) - def editLabel(self): + def edit_label(self): if not self.canvas.editing(): return - item = self.currentItem() + item = self.current_item() if not item: return - text = self.labelDialog.popUp(item.text()) + text = self.label_dialog.pop_up(item.text()) if text is not None: item.setText(text) - item.setBackground(generateColorByText(text)) - self.setDirty() - self.updateComboBox() + item.setBackground(generate_color_by_text(text)) + self.set_dirty() + self.update_combo_box() # Tzutalin 20160906 : Add file list and dock to move faster - def fileitemDoubleClicked(self, item=None): - currIndex = self.mImgList.index(ustr(item.text())) - if currIndex < len(self.mImgList): - filename = self.mImgList[currIndex] + def file_item_double_clicked(self, item=None): + current_index = self.m_img_list.index(ustr(item.text())) + if current_index < len(self.m_img_list): + filename = self.m_img_list[current_index] if filename: - self.loadFile(filename) + self.load_file(filename) # Add chris - def btnstate(self, item= None): + def button_state(self, item=None): """ Function to handle difficult examples Update on each object """ if not self.canvas.editing(): return - item = self.currentItem() - if not item: # If not selected Item, take the first one - item = self.labelList.item(self.labelList.count()-1) + item = self.current_item() + if not item: # If not selected Item, take the first one + item = self.label_list.item(self.label_list.count() - 1) - difficult = self.diffcButton.isChecked() + difficult = self.diffc_button.isChecked() try: - shape = self.itemsToShapes[item] + shape = self.items_to_shapes[item] except: pass # Checked and Update try: if difficult != shape.difficult: shape.difficult = difficult - self.setDirty() + self.set_dirty() else: # User probably changed item visibility - self.canvas.setShapeVisible(shape, item.checkState() == Qt.Checked) + self.canvas.set_shape_visible(shape, item.checkState() == Qt.Checked) except: pass # React to canvas signals. - def shapeSelectionChanged(self, selected=False): - if self._noSelectionSlot: - self._noSelectionSlot = False + def shape_selection_changed(self, selected=False): + if self._no_selection_slot: + self._no_selection_slot = False else: - shape = self.canvas.selectedShape + shape = self.canvas.selected_shape if shape: - self.shapesToItems[shape].setSelected(True) + self.shapes_to_items[shape].setSelected(True) else: - self.labelList.clearSelection() + self.label_list.clearSelection() self.actions.delete.setEnabled(selected) self.actions.copy.setEnabled(selected) self.actions.edit.setEnabled(selected) self.actions.shapeLineColor.setEnabled(selected) self.actions.shapeFillColor.setEnabled(selected) - def addLabel(self, shape): - shape.paintLabel = self.displayLabelOption.isChecked() + def add_label(self, shape): + shape.paint_label = self.display_label_option.isChecked() item = HashableQListWidgetItem(shape.label) item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Checked) - item.setBackground(generateColorByText(shape.label)) - self.itemsToShapes[item] = shape - self.shapesToItems[shape] = item - self.labelList.addItem(item) + item.setBackground(generate_color_by_text(shape.label)) + self.items_to_shapes[item] = shape + self.shapes_to_items[shape] = item + self.label_list.addItem(item) for action in self.actions.onShapesPresent: action.setEnabled(True) - self.updateComboBox() + self.update_combo_box() - def remLabel(self, shape): + def remove_label(self, shape): if shape is None: # print('rm empty label') return - item = self.shapesToItems[shape] - self.labelList.takeItem(self.labelList.row(item)) - del self.shapesToItems[shape] - del self.itemsToShapes[item] - self.updateComboBox() + item = self.shapes_to_items[shape] + self.label_list.takeItem(self.label_list.row(item)) + del self.shapes_to_items[shape] + del self.items_to_shapes[item] + self.update_combo_box() - def loadLabels(self, shapes): + def load_labels(self, shapes): s = [] for label, points, line_color, fill_color, difficult in shapes: shape = Shape(label=label) for x, y in points: # Ensure the labels are within the bounds of the image. If not, fix them. - x, y, snapped = self.canvas.snapPointToCanvas(x, y) + x, y, snapped = self.canvas.snap_point_to_canvas(x, y) if snapped: - self.setDirty() + self.set_dirty() - shape.addPoint(QPointF(x, y)) + shape.add_point(QPointF(x, y)) shape.difficult = difficult shape.close() s.append(shape) @@ -807,162 +807,162 @@ def loadLabels(self, shapes): if line_color: shape.line_color = QColor(*line_color) else: - shape.line_color = generateColorByText(label) + shape.line_color = generate_color_by_text(label) if fill_color: shape.fill_color = QColor(*fill_color) else: - shape.fill_color = generateColorByText(label) + shape.fill_color = generate_color_by_text(label) - self.addLabel(shape) - self.updateComboBox() - self.canvas.loadShapes(s) + self.add_label(shape) + self.update_combo_box() + self.canvas.load_shapes(s) - def updateComboBox(self): + def update_combo_box(self): # Get the unique labels and add them to the Combobox. - itemsTextList = [str(self.labelList.item(i).text()) for i in range(self.labelList.count())] + items_text_list = [str(self.label_list.item(i).text()) for i in range(self.label_list.count())] - uniqueTextList = list(set(itemsTextList)) + unique_text_list = list(set(items_text_list)) # Add a null row for showing all the labels - uniqueTextList.append("") - uniqueTextList.sort() + unique_text_list.append("") + unique_text_list.sort() - self.comboBox.update_items(uniqueTextList) + self.combo_box.update_items(unique_text_list) - def saveLabels(self, annotationFilePath): - annotationFilePath = ustr(annotationFilePath) - if self.labelFile is None: - self.labelFile = LabelFile() - self.labelFile.verified = self.canvas.verified + def save_labels(self, annotation_file_path): + annotation_file_path = ustr(annotation_file_path) + if self.label_file is None: + self.label_file = LabelFile() + self.label_file.verified = self.canvas.verified def format_shape(s): return dict(label=s.label, line_color=s.line_color.getRgb(), fill_color=s.fill_color.getRgb(), points=[(p.x(), p.y()) for p in s.points], - # add chris - difficult = s.difficult) + # add chris + difficult=s.difficult) shapes = [format_shape(shape) for shape in self.canvas.shapes] - # Can add differrent annotation formats here + # Can add different annotation formats here try: - if self.labelFileFormat == LabelFileFormat.PASCAL_VOC: - if annotationFilePath[-4:].lower() != ".xml": - annotationFilePath += XML_EXT - self.labelFile.savePascalVocFormat(annotationFilePath, shapes, self.filePath, self.imageData, - self.lineColor.getRgb(), self.fillColor.getRgb()) - elif self.labelFileFormat == LabelFileFormat.YOLO: - if annotationFilePath[-4:].lower() != ".txt": - annotationFilePath += TXT_EXT - self.labelFile.saveYoloFormat(annotationFilePath, shapes, self.filePath, self.imageData, self.labelHist, - self.lineColor.getRgb(), self.fillColor.getRgb()) - elif self.labelFileFormat == LabelFileFormat.CREATE_ML: - if annotationFilePath[-5:].lower() != ".json": - annotationFilePath += JSON_EXT - self.labelFile.saveCreateMLFormat(annotationFilePath, shapes, self.filePath, self.imageData, - self.labelHist, self.lineColor.getRgb(), self.fillColor.getRgb()) + if self.label_file_format == LabelFileFormat.PASCAL_VOC: + if annotation_file_path[-4:].lower() != ".xml": + annotation_file_path += XML_EXT + self.label_file.save_pascal_voc_format(annotation_file_path, shapes, self.file_path, self.image_data, + self.line_color.getRgb(), self.fill_color.getRgb()) + elif self.label_file_format == LabelFileFormat.YOLO: + if annotation_file_path[-4:].lower() != ".txt": + annotation_file_path += TXT_EXT + self.label_file.save_yolo_format(annotation_file_path, shapes, self.file_path, self.image_data, self.label_hist, + self.line_color.getRgb(), self.fill_color.getRgb()) + elif self.label_file_format == LabelFileFormat.CREATE_ML: + if annotation_file_path[-5:].lower() != ".json": + annotation_file_path += JSON_EXT + self.label_file.save_create_ml_format(annotation_file_path, shapes, self.file_path, self.image_data, + self.label_hist, self.line_color.getRgb(), self.fill_color.getRgb()) else: - self.labelFile.save(annotationFilePath, shapes, self.filePath, self.imageData, - self.lineColor.getRgb(), self.fillColor.getRgb()) - print('Image:{0} -> Annotation:{1}'.format(self.filePath, annotationFilePath)) + self.label_file.save(annotation_file_path, shapes, self.file_path, self.image_data, + self.line_color.getRgb(), self.fill_color.getRgb()) + print('Image:{0} -> Annotation:{1}'.format(self.file_path, annotation_file_path)) return True except LabelFileError as e: - self.errorMessage(u'Error saving label data', u'%s' % e) + self.error_message(u'Error saving label data', u'%s' % e) return False - def copySelectedShape(self): - self.addLabel(self.canvas.copySelectedShape()) + def copy_selected_shape(self): + self.add_label(self.canvas.copy_selected_shape()) # fix copy and delete - self.shapeSelectionChanged(True) + self.shape_selection_changed(True) - def comboSelectionChanged(self, index): - text = self.comboBox.cb.itemText(index) - for i in range(self.labelList.count()): + def combo_selection_changed(self, index): + text = self.combo_box.cb.itemText(index) + for i in range(self.label_list.count()): if text == "": - self.labelList.item(i).setCheckState(2) - elif text != self.labelList.item(i).text(): - self.labelList.item(i).setCheckState(0) + self.label_list.item(i).setCheckState(2) + elif text != self.label_list.item(i).text(): + self.label_list.item(i).setCheckState(0) else: - self.labelList.item(i).setCheckState(2) + self.label_list.item(i).setCheckState(2) - def labelSelectionChanged(self): - item = self.currentItem() + def label_selection_changed(self): + item = self.current_item() if item and self.canvas.editing(): - self._noSelectionSlot = True - self.canvas.selectShape(self.itemsToShapes[item]) - shape = self.itemsToShapes[item] + self._no_selection_slot = True + self.canvas.select_shape(self.items_to_shapes[item]) + shape = self.items_to_shapes[item] # Add Chris - self.diffcButton.setChecked(shape.difficult) + self.diffc_button.setChecked(shape.difficult) - def labelItemChanged(self, item): - shape = self.itemsToShapes[item] + def label_item_changed(self, item): + shape = self.items_to_shapes[item] label = item.text() if label != shape.label: shape.label = item.text() - shape.line_color = generateColorByText(shape.label) - self.setDirty() + shape.line_color = generate_color_by_text(shape.label) + self.set_dirty() else: # User probably changed item visibility - self.canvas.setShapeVisible(shape, item.checkState() == Qt.Checked) + self.canvas.set_shape_visible(shape, item.checkState() == Qt.Checked) # Callback functions: - def newShape(self): + def new_shape(self): """Pop-up and give focus to the label editor. position MUST be in global coordinates. """ - if not self.useDefaultLabelCheckbox.isChecked() or not self.defaultLabelTextLine.text(): - if len(self.labelHist) > 0: - self.labelDialog = LabelDialog( - parent=self, listItem=self.labelHist) + if not self.use_default_label_checkbox.isChecked() or not self.default_label_text_line.text(): + if len(self.label_hist) > 0: + self.label_dialog = LabelDialog( + parent=self, list_item=self.label_hist) # Sync single class mode from PR#106 - if self.singleClassMode.isChecked() and self.lastLabel: + if self.single_class_mode.isChecked() and self.lastLabel: text = self.lastLabel else: - text = self.labelDialog.popUp(text=self.prevLabelText) + text = self.label_dialog.pop_up(text=self.prev_label_text) self.lastLabel = text else: - text = self.defaultLabelTextLine.text() + text = self.default_label_text_line.text() # Add Chris - self.diffcButton.setChecked(False) + self.diffc_button.setChecked(False) if text is not None: - self.prevLabelText = text - generate_color = generateColorByText(text) - shape = self.canvas.setLastLabel(text, generate_color, generate_color) - self.addLabel(shape) + self.prev_label_text = text + generate_color = generate_color_by_text(text) + shape = self.canvas.set_last_label(text, generate_color, generate_color) + self.add_label(shape) if self.beginner(): # Switch to edit mode. - self.canvas.setEditing(True) + self.canvas.set_editing(True) self.actions.create.setEnabled(True) else: self.actions.editMode.setEnabled(True) - self.setDirty() + self.set_dirty() - if text not in self.labelHist: - self.labelHist.append(text) + if text not in self.label_hist: + self.label_hist.append(text) else: # self.canvas.undoLastLine() - self.canvas.resetAllLines() + self.canvas.reset_all_lines() - def scrollRequest(self, delta, orientation): + def scroll_request(self, delta, orientation): units = - delta / (8 * 15) - bar = self.scrollBars[orientation] + bar = self.scroll_bars[orientation] bar.setValue(bar.value() + bar.singleStep() * units) - def setZoom(self, value): + def set_zoom(self, value): self.actions.fitWidth.setChecked(False) self.actions.fitWindow.setChecked(False) - self.zoomMode = self.MANUAL_ZOOM - self.zoomWidget.setValue(value) + self.zoom_mode = self.MANUAL_ZOOM + self.zoom_widget.setValue(value) - def addZoom(self, increment=10): - self.setZoom(self.zoomWidget.value() + increment) + def add_zoom(self, increment=10): + self.set_zoom(self.zoom_widget.value() + increment) - def zoomRequest(self, delta): + def zoom_request(self, delta): # get the current scrollbar positions # calculate the percentages ~ coordinates - h_bar = self.scrollBars[Qt.Horizontal] - v_bar = self.scrollBars[Qt.Vertical] + h_bar = self.scroll_bars[Qt.Horizontal] + v_bar = self.scroll_bars[Qt.Vertical] # get the current maximum, to know the difference after zooming h_bar_max = h_bar.maximum() @@ -980,8 +980,8 @@ def zoomRequest(self, delta): cursor_x = relative_pos.x() cursor_y = relative_pos.y() - w = self.scrollArea.width() - h = self.scrollArea.height() + w = self.scroll_area.width() + h = self.scroll_area.height() # the scaling from 0 to 1 has some padding # you don't have to hit the very leftmost pixel for a maximum-left movement @@ -996,7 +996,7 @@ def zoomRequest(self, delta): # zoom in units = delta / (8 * 15) scale = 10 - self.addZoom(scale * units) + self.add_zoom(scale * units) # get the difference in scrollbar values # this is how far we can move @@ -1010,146 +1010,146 @@ def zoomRequest(self, delta): h_bar.setValue(new_h_bar_value) v_bar.setValue(new_v_bar_value) - def setFitWindow(self, value=True): + def set_fit_window(self, value=True): if value: self.actions.fitWidth.setChecked(False) - self.zoomMode = self.FIT_WINDOW if value else self.MANUAL_ZOOM - self.adjustScale() + self.zoom_mode = self.FIT_WINDOW if value else self.MANUAL_ZOOM + self.adjust_scale() - def setFitWidth(self, value=True): + def set_fit_width(self, value=True): if value: self.actions.fitWindow.setChecked(False) - self.zoomMode = self.FIT_WIDTH if value else self.MANUAL_ZOOM - self.adjustScale() + self.zoom_mode = self.FIT_WIDTH if value else self.MANUAL_ZOOM + self.adjust_scale() - def togglePolygons(self, value): - for item, shape in self.itemsToShapes.items(): + def toggle_polygons(self, value): + for item, shape in self.items_to_shapes.items(): item.setCheckState(Qt.Checked if value else Qt.Unchecked) - def loadFile(self, filePath=None): + def load_file(self, file_path=None): """Load the specified file, or the last opened file if None.""" - self.resetState() + self.reset_state() self.canvas.setEnabled(False) - if filePath is None: - filePath = self.settings.get(SETTING_FILENAME) + if file_path is None: + file_path = self.settings.get(SETTING_FILENAME) # Make sure that filePath is a regular python string, rather than QString - filePath = ustr(filePath) + file_path = ustr(file_path) # Fix bug: An index error after select a directory when open a new file. - unicodeFilePath = ustr(filePath) - unicodeFilePath = os.path.abspath(unicodeFilePath) + unicode_file_path = ustr(file_path) + unicode_file_path = os.path.abspath(unicode_file_path) # Tzutalin 20160906 : Add file list and dock to move faster # Highlight the file item - if unicodeFilePath and self.fileListWidget.count() > 0: - if unicodeFilePath in self.mImgList: - index = self.mImgList.index(unicodeFilePath) - fileWidgetItem = self.fileListWidget.item(index) - fileWidgetItem.setSelected(True) + if unicode_file_path and self.file_list_widget.count() > 0: + if unicode_file_path in self.m_img_list: + index = self.m_img_list.index(unicode_file_path) + file_widget_item = self.file_list_widget.item(index) + file_widget_item.setSelected(True) else: - self.fileListWidget.clear() - self.mImgList.clear() + self.file_list_widget.clear() + self.m_img_list.clear() - if unicodeFilePath and os.path.exists(unicodeFilePath): - if LabelFile.isLabelFile(unicodeFilePath): + if unicode_file_path and os.path.exists(unicode_file_path): + if LabelFile.is_label_file(unicode_file_path): try: - self.labelFile = LabelFile(unicodeFilePath) + self.label_file = LabelFile(unicode_file_path) except LabelFileError as e: - self.errorMessage(u'Error opening file', - (u"
%s
" - u"Make sure %s is a valid label file.") - % (e, unicodeFilePath)) - self.status("Error reading %s" % unicodeFilePath) + self.error_message(u'Error opening file', + (u"
%s
" + u"Make sure %s is a valid label file.") + % (e, unicode_file_path)) + self.status("Error reading %s" % unicode_file_path) return False - self.imageData = self.labelFile.imageData - self.lineColor = QColor(*self.labelFile.lineColor) - self.fillColor = QColor(*self.labelFile.fillColor) - self.canvas.verified = self.labelFile.verified + self.image_data = self.label_file.image_data + self.line_color = QColor(*self.label_file.lineColor) + self.fill_color = QColor(*self.label_file.fillColor) + self.canvas.verified = self.label_file.verified else: # Load image: # read data first and store for saving into label file. - self.imageData = read(unicodeFilePath, None) - self.labelFile = None + self.image_data = read(unicode_file_path, None) + self.label_file = None self.canvas.verified = False - if isinstance(self.imageData, QImage): - image = self.imageData + if isinstance(self.image_data, QImage): + image = self.image_data else: - image = QImage.fromData(self.imageData) + image = QImage.fromData(self.image_data) if image.isNull(): - self.errorMessage(u'Error opening file', - u"
Make sure %s is a valid image file." % unicodeFilePath) - self.status("Error reading %s" % unicodeFilePath) + self.error_message(u'Error opening file', + u"
Make sure %s is a valid image file." % unicode_file_path) + self.status("Error reading %s" % unicode_file_path) return False - self.status("Loaded %s" % os.path.basename(unicodeFilePath)) + self.status("Loaded %s" % os.path.basename(unicode_file_path)) self.image = image - self.filePath = unicodeFilePath - self.canvas.loadPixmap(QPixmap.fromImage(image)) - if self.labelFile: - self.loadLabels(self.labelFile.shapes) - self.setClean() + self.file_path = unicode_file_path + self.canvas.load_pixmap(QPixmap.fromImage(image)) + if self.label_file: + self.load_labels(self.label_file.shapes) + self.set_clean() self.canvas.setEnabled(True) - self.adjustScale(initial=True) - self.paintCanvas() - self.addRecentFile(self.filePath) - self.toggleActions(True) - self.showBoundingBoxFromAnnotationFile(filePath) + self.adjust_scale(initial=True) + self.paint_canvas() + self.add_recent_file(self.file_path) + self.toggle_actions(True) + self.show_bounding_box_from_annotation_file(file_path) - self.setWindowTitle(__appname__ + ' ' + filePath) + self.setWindowTitle(__appname__ + ' ' + file_path) # Default : select last item if there is at least one item - if self.labelList.count(): - self.labelList.setCurrentItem(self.labelList.item(self.labelList.count()-1)) - self.labelList.item(self.labelList.count()-1).setSelected(True) + if self.label_list.count(): + self.label_list.setCurrentItem(self.label_list.item(self.label_list.count() - 1)) + self.label_list.item(self.label_list.count() - 1).setSelected(True) self.canvas.setFocus(True) return True return False - def showBoundingBoxFromAnnotationFile(self, filePath): - if self.defaultSaveDir is not None: - basename = os.path.basename(os.path.splitext(filePath)[0]) - filedir = filePath.split(basename)[0].split(os.path.sep)[-2:-1][0] - xmlPath = os.path.join(self.defaultSaveDir, basename + XML_EXT) - txtPath = os.path.join(self.defaultSaveDir, basename + TXT_EXT) - jsonPath = os.path.join(self.defaultSaveDir, filedir + JSON_EXT) + def show_bounding_box_from_annotation_file(self, file_path): + if self.default_save_dir is not None: + basename = os.path.basename(os.path.splitext(file_path)[0]) + file_dir = file_path.split(basename)[0].split(os.path.sep)[-2:-1][0] + xml_path = os.path.join(self.default_save_dir, basename + XML_EXT) + txt_path = os.path.join(self.default_save_dir, basename + TXT_EXT) + json_path = os.path.join(self.default_save_dir, file_dir + JSON_EXT) """Annotation file priority: PascalXML > YOLO """ - if os.path.isfile(xmlPath): - self.loadPascalXMLByFilename(xmlPath) - elif os.path.isfile(txtPath): - self.loadYOLOTXTByFilename(txtPath) - elif os.path.isfile(jsonPath): - self.loadCreateMLJSONByFilename(jsonPath, filePath) + if os.path.isfile(xml_path): + self.load_pascal_xml_by_filename(xml_path) + elif os.path.isfile(txt_path): + self.load_yolo_txt_by_filename(txt_path) + elif os.path.isfile(json_path): + self.load_create_ml_json_by_filename(json_path, file_path) else: - xmlPath = os.path.splitext(filePath)[0] + XML_EXT - txtPath = os.path.splitext(filePath)[0] + TXT_EXT - if os.path.isfile(xmlPath): - self.loadPascalXMLByFilename(xmlPath) - elif os.path.isfile(txtPath): - self.loadYOLOTXTByFilename(txtPath) + xml_path = os.path.splitext(file_path)[0] + XML_EXT + txt_path = os.path.splitext(file_path)[0] + TXT_EXT + if os.path.isfile(xml_path): + self.load_pascal_xml_by_filename(xml_path) + elif os.path.isfile(txt_path): + self.load_yolo_txt_by_filename(txt_path) def resizeEvent(self, event): if self.canvas and not self.image.isNull()\ - and self.zoomMode != self.MANUAL_ZOOM: - self.adjustScale() + and self.zoom_mode != self.MANUAL_ZOOM: + self.adjust_scale() super(MainWindow, self).resizeEvent(event) - def paintCanvas(self): + def paint_canvas(self): assert not self.image.isNull(), "cannot paint null image" - self.canvas.scale = 0.01 * self.zoomWidget.value() - self.canvas.labelFontSize = int(0.02 * max(self.image.width(), self.image.height())) + self.canvas.scale = 0.01 * self.zoom_widget.value() + self.canvas.label_font_size = int(0.02 * max(self.image.width(), self.image.height())) self.canvas.adjustSize() self.canvas.update() - def adjustScale(self, initial=False): - value = self.scalers[self.FIT_WINDOW if initial else self.zoomMode]() - self.zoomWidget.setValue(int(100 * value)) + def adjust_scale(self, initial=False): + value = self.scalers[self.FIT_WINDOW if initial else self.zoom_mode]() + self.zoom_widget.setValue(int(100 * value)) - def scaleFitWindow(self): + def scale_fit_window(self): """Figure out the size of the pixmap in order to fit the main widget.""" e = 2.0 # So that no scrollbars are generated. w1 = self.centralWidget().width() - e @@ -1161,404 +1161,404 @@ def scaleFitWindow(self): a2 = w2 / h2 return w1 / w2 if a2 >= a1 else h1 / h2 - def scaleFitWidth(self): + def scale_fit_width(self): # The epsilon does not seem to work too well here. w = self.centralWidget().width() - 2.0 return w / self.canvas.pixmap.width() def closeEvent(self, event): - if not self.mayContinue(): + if not self.may_continue(): event.ignore() settings = self.settings - # If it loads images from dir, don't load it at the begining - if self.dirname is None: - settings[SETTING_FILENAME] = self.filePath if self.filePath else '' + # If it loads images from dir, don't load it at the beginning + if self.dir_name is None: + settings[SETTING_FILENAME] = self.file_path if self.file_path else '' else: settings[SETTING_FILENAME] = '' settings[SETTING_WIN_SIZE] = self.size() settings[SETTING_WIN_POSE] = self.pos() settings[SETTING_WIN_STATE] = self.saveState() - settings[SETTING_LINE_COLOR] = self.lineColor - settings[SETTING_FILL_COLOR] = self.fillColor - settings[SETTING_RECENT_FILES] = self.recentFiles + settings[SETTING_LINE_COLOR] = self.line_color + settings[SETTING_FILL_COLOR] = self.fill_color + settings[SETTING_RECENT_FILES] = self.recent_files settings[SETTING_ADVANCE_MODE] = not self._beginner - if self.defaultSaveDir and os.path.exists(self.defaultSaveDir): - settings[SETTING_SAVE_DIR] = ustr(self.defaultSaveDir) + if self.default_save_dir and os.path.exists(self.default_save_dir): + settings[SETTING_SAVE_DIR] = ustr(self.default_save_dir) else: settings[SETTING_SAVE_DIR] = '' - if self.lastOpenDir and os.path.exists(self.lastOpenDir): - settings[SETTING_LAST_OPEN_DIR] = self.lastOpenDir + if self.last_open_dir and os.path.exists(self.last_open_dir): + settings[SETTING_LAST_OPEN_DIR] = self.last_open_dir else: settings[SETTING_LAST_OPEN_DIR] = '' - settings[SETTING_AUTO_SAVE] = self.autoSaving.isChecked() - settings[SETTING_SINGLE_CLASS] = self.singleClassMode.isChecked() - settings[SETTING_PAINT_LABEL] = self.displayLabelOption.isChecked() - settings[SETTING_DRAW_SQUARE] = self.drawSquaresOption.isChecked() - settings[SETTING_LABEL_FILE_FORMAT] = self.labelFileFormat + settings[SETTING_AUTO_SAVE] = self.auto_saving.isChecked() + settings[SETTING_SINGLE_CLASS] = self.single_class_mode.isChecked() + settings[SETTING_PAINT_LABEL] = self.display_label_option.isChecked() + settings[SETTING_DRAW_SQUARE] = self.draw_squares_option.isChecked() + settings[SETTING_LABEL_FILE_FORMAT] = self.label_file_format settings.save() - def loadRecent(self, filename): - if self.mayContinue(): - self.loadFile(filename) + def load_recent(self, filename): + if self.may_continue(): + self.load_file(filename) - def scanAllImages(self, folderPath): + def scan_all_images(self, folder_path): extensions = ['.%s' % fmt.data().decode("ascii").lower() for fmt in QImageReader.supportedImageFormats()] images = [] - for root, dirs, files in os.walk(folderPath): + for root, dirs, files in os.walk(folder_path): for file in files: if file.lower().endswith(tuple(extensions)): - relativePath = os.path.join(root, file) - path = ustr(os.path.abspath(relativePath)) + relative_path = os.path.join(root, file) + path = ustr(os.path.abspath(relative_path)) images.append(path) natural_sort(images, key=lambda x: x.lower()) return images - def changeSavedirDialog(self, _value=False): - if self.defaultSaveDir is not None: - path = ustr(self.defaultSaveDir) + def change_save_dir_dialog(self, _value=False): + if self.default_save_dir is not None: + path = ustr(self.default_save_dir) else: path = '.' - dirpath = ustr(QFileDialog.getExistingDirectory(self, - '%s - Save annotations to the directory' % __appname__, path, QFileDialog.ShowDirsOnly - | QFileDialog.DontResolveSymlinks)) + dir_path = ustr(QFileDialog.getExistingDirectory(self, + '%s - Save annotations to the directory' % __appname__, path, QFileDialog.ShowDirsOnly + | QFileDialog.DontResolveSymlinks)) - if dirpath is not None and len(dirpath) > 1: - self.defaultSaveDir = dirpath + if dir_path is not None and len(dir_path) > 1: + self.default_save_dir = dir_path self.statusBar().showMessage('%s . Annotation will be saved to %s' % - ('Change saved folder', self.defaultSaveDir)) + ('Change saved folder', self.default_save_dir)) self.statusBar().show() - def openAnnotationDialog(self, _value=False): - if self.filePath is None: + def open_annotation_dialog(self, _value=False): + if self.file_path is None: self.statusBar().showMessage('Please select image first') self.statusBar().show() return - path = os.path.dirname(ustr(self.filePath))\ - if self.filePath else '.' - if self.labelFileFormat == LabelFileFormat.PASCAL_VOC: + path = os.path.dirname(ustr(self.file_path))\ + if self.file_path else '.' + if self.label_file_format == LabelFileFormat.PASCAL_VOC: filters = "Open Annotation XML file (%s)" % ' '.join(['*.xml']) - filename = ustr(QFileDialog.getOpenFileName(self,'%s - Choose a xml file' % __appname__, path, filters)) + filename = ustr(QFileDialog.getOpenFileName(self, '%s - Choose a xml file' % __appname__, path, filters)) if filename: if isinstance(filename, (tuple, list)): filename = filename[0] - self.loadPascalXMLByFilename(filename) + self.load_pascal_xml_by_filename(filename) - def openDirDialog(self, _value=False, dirpath=None, silent=False): - if not self.mayContinue(): + def open_dir_dialog(self, _value=False, dir_path=None, silent=False): + if not self.may_continue(): return - defaultOpenDirPath = dirpath if dirpath else '.' - if self.lastOpenDir and os.path.exists(self.lastOpenDir): - defaultOpenDirPath = self.lastOpenDir + default_open_dir_path = dir_path if dir_path else '.' + if self.last_open_dir and os.path.exists(self.last_open_dir): + default_open_dir_path = self.last_open_dir else: - defaultOpenDirPath = os.path.dirname(self.filePath) if self.filePath else '.' - if silent!=True : - targetDirPath = ustr(QFileDialog.getExistingDirectory(self, - '%s - Open Directory' % __appname__, defaultOpenDirPath, - QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks)) + default_open_dir_path = os.path.dirname(self.file_path) if self.file_path else '.' + if silent != True: + target_dir_path = ustr(QFileDialog.getExistingDirectory(self, + '%s - Open Directory' % __appname__, default_open_dir_path, + QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks)) else: - targetDirPath = ustr(defaultOpenDirPath) - self.lastOpenDir = targetDirPath - self.importDirImages(targetDirPath) + target_dir_path = ustr(default_open_dir_path) + self.last_open_dir = target_dir_path + self.import_dir_images(target_dir_path) - def importDirImages(self, dirpath): - if not self.mayContinue() or not dirpath: + def import_dir_images(self, dir_path): + if not self.may_continue() or not dir_path: return - self.lastOpenDir = dirpath - self.dirname = dirpath - self.filePath = None - self.fileListWidget.clear() - self.mImgList = self.scanAllImages(dirpath) - self.openNextImg() - for imgPath in self.mImgList: + self.last_open_dir = dir_path + self.dir_name = dir_path + self.file_path = None + self.file_list_widget.clear() + self.m_img_list = self.scan_all_images(dir_path) + self.open_next_image() + for imgPath in self.m_img_list: item = QListWidgetItem(imgPath) - self.fileListWidget.addItem(item) + self.file_list_widget.addItem(item) - def verifyImg(self, _value=False): - # Proceding next image without dialog if having any label - if self.filePath is not None: + def verify_image(self, _value=False): + # Proceeding next image without dialog if having any label + if self.file_path is not None: try: - self.labelFile.toggleVerify() + self.label_file.toggle_verify() except AttributeError: # If the labelling file does not exist yet, create if and # re-save it with the verified attribute. - self.saveFile() - if self.labelFile != None: - self.labelFile.toggleVerify() + self.save_file() + if self.label_file is not None: + self.label_file.toggle_verify() else: return - self.canvas.verified = self.labelFile.verified - self.paintCanvas() - self.saveFile() + self.canvas.verified = self.label_file.verified + self.paint_canvas() + self.save_file() - def openPrevImg(self, _value=False): - # Proceding prev image without dialog if having any label - if self.autoSaving.isChecked(): - if self.defaultSaveDir is not None: + def open_prev_image(self, _value=False): + # Proceeding prev image without dialog if having any label + if self.auto_saving.isChecked(): + if self.default_save_dir is not None: if self.dirty is True: - self.saveFile() + self.save_file() else: - self.changeSavedirDialog() + self.change_save_dir_dialog() return - if not self.mayContinue(): + if not self.may_continue(): return - if len(self.mImgList) <= 0: + if len(self.m_img_list) <= 0: return - if self.filePath is None: + if self.file_path is None: return - currIndex = self.mImgList.index(self.filePath) - if currIndex - 1 >= 0: - filename = self.mImgList[currIndex - 1] + current_index = self.m_img_list.index(self.file_path) + if current_index - 1 >= 0: + filename = self.m_img_list[current_index - 1] if filename: - self.loadFile(filename) + self.load_file(filename) - def openNextImg(self, _value=False): - # Proceding prev image without dialog if having any label - if self.autoSaving.isChecked(): - if self.defaultSaveDir is not None: + def open_next_image(self, _value=False): + # Proceeding prev image without dialog if having any label + if self.auto_saving.isChecked(): + if self.default_save_dir is not None: if self.dirty is True: - self.saveFile() + self.save_file() else: - self.changeSavedirDialog() + self.change_save_dir_dialog() return - if not self.mayContinue(): + if not self.may_continue(): return - if len(self.mImgList) <= 0: + if len(self.m_img_list) <= 0: return filename = None - if self.filePath is None: - filename = self.mImgList[0] + if self.file_path is None: + filename = self.m_img_list[0] else: - currIndex = self.mImgList.index(self.filePath) - if currIndex + 1 < len(self.mImgList): - filename = self.mImgList[currIndex + 1] + current_index = self.m_img_list.index(self.file_path) + if current_index + 1 < len(self.m_img_list): + filename = self.m_img_list[current_index + 1] if filename: - self.loadFile(filename) + self.load_file(filename) - def openFile(self, _value=False): - if not self.mayContinue(): + def open_file(self, _value=False): + if not self.may_continue(): return - path = os.path.dirname(ustr(self.filePath)) if self.filePath else '.' + path = os.path.dirname(ustr(self.file_path)) if self.file_path else '.' formats = ['*.%s' % fmt.data().decode("ascii").lower() for fmt in QImageReader.supportedImageFormats()] filters = "Image & Label files (%s)" % ' '.join(formats + ['*%s' % LabelFile.suffix]) filename = QFileDialog.getOpenFileName(self, '%s - Choose Image or Label file' % __appname__, path, filters) if filename: if isinstance(filename, (tuple, list)): filename = filename[0] - self.loadFile(filename) - - def saveFile(self, _value=False): - if self.defaultSaveDir is not None and len(ustr(self.defaultSaveDir)): - if self.filePath: - imgFileName = os.path.basename(self.filePath) - savedFileName = os.path.splitext(imgFileName)[0] - savedPath = os.path.join(ustr(self.defaultSaveDir), savedFileName) - self._saveFile(savedPath) + self.load_file(filename) + + def save_file(self, _value=False): + if self.default_save_dir is not None and len(ustr(self.default_save_dir)): + if self.file_path: + image_file_name = os.path.basename(self.file_path) + saved_file_name = os.path.splitext(image_file_name)[0] + saved_path = os.path.join(ustr(self.default_save_dir), saved_file_name) + self._save_file(saved_path) else: - imgFileDir = os.path.dirname(self.filePath) - imgFileName = os.path.basename(self.filePath) - savedFileName = os.path.splitext(imgFileName)[0] - savedPath = os.path.join(imgFileDir, savedFileName) - self._saveFile(savedPath if self.labelFile - else self.saveFileDialog(removeExt=False)) - - def saveFileAs(self, _value=False): + image_file_dir = os.path.dirname(self.file_path) + image_file_name = os.path.basename(self.file_path) + saved_file_name = os.path.splitext(image_file_name)[0] + saved_path = os.path.join(image_file_dir, saved_file_name) + self._save_file(saved_path if self.label_file + else self.save_file_dialog(remove_ext=False)) + + def save_file_as(self, _value=False): assert not self.image.isNull(), "cannot save empty image" - self._saveFile(self.saveFileDialog()) + self._save_file(self.save_file_dialog()) - def saveFileDialog(self, removeExt=True): + def save_file_dialog(self, remove_ext=True): caption = '%s - Choose File' % __appname__ filters = 'File (*%s)' % LabelFile.suffix - openDialogPath = self.currentPath() - dlg = QFileDialog(self, caption, openDialogPath, filters) + open_dialog_path = self.current_path() + dlg = QFileDialog(self, caption, open_dialog_path, filters) dlg.setDefaultSuffix(LabelFile.suffix[1:]) dlg.setAcceptMode(QFileDialog.AcceptSave) - filenameWithoutExtension = os.path.splitext(self.filePath)[0] - dlg.selectFile(filenameWithoutExtension) + filename_without_extension = os.path.splitext(self.file_path)[0] + dlg.selectFile(filename_without_extension) dlg.setOption(QFileDialog.DontUseNativeDialog, False) if dlg.exec_(): - fullFilePath = ustr(dlg.selectedFiles()[0]) - if removeExt: - return os.path.splitext(fullFilePath)[0] # Return file path without the extension. + full_file_path = ustr(dlg.selectedFiles()[0]) + if remove_ext: + return os.path.splitext(full_file_path)[0] # Return file path without the extension. else: - return fullFilePath + return full_file_path return '' - def _saveFile(self, annotationFilePath): - if annotationFilePath and self.saveLabels(annotationFilePath): - self.setClean() - self.statusBar().showMessage('Saved to %s' % annotationFilePath) + def _save_file(self, annotation_file_path): + if annotation_file_path and self.save_labels(annotation_file_path): + self.set_clean() + self.statusBar().showMessage('Saved to %s' % annotation_file_path) self.statusBar().show() - def closeFile(self, _value=False): - if not self.mayContinue(): + def close_file(self, _value=False): + if not self.may_continue(): return - self.resetState() - self.setClean() - self.toggleActions(False) + self.reset_state() + self.set_clean() + self.toggle_actions(False) self.canvas.setEnabled(False) self.actions.saveAs.setEnabled(False) - def deleteImg(self): - deletePath = self.filePath - if deletePath is not None: - self.openNextImg() - if os.path.exists(deletePath): - os.remove(deletePath) - self.importDirImages(self.lastOpenDir) + def delete_image(self): + delete_path = self.file_path + if delete_path is not None: + self.open_next_image() + if os.path.exists(delete_path): + os.remove(delete_path) + self.import_dir_images(self.last_open_dir) - def resetAll(self): + def reset_all(self): self.settings.reset() self.close() - proc = QProcess() - proc.startDetached(os.path.abspath(__file__)) + process = QProcess() + process.startDetached(os.path.abspath(__file__)) - def mayContinue(self): + def may_continue(self): if not self.dirty: return True else: - discardChanges = self.discardChangesDialog() - if discardChanges == QMessageBox.No: + discard_changes = self.discard_changes_dialog() + if discard_changes == QMessageBox.No: return True - elif discardChanges == QMessageBox.Yes: - self.saveFile() + elif discard_changes == QMessageBox.Yes: + self.save_file() return True else: return False - def discardChangesDialog(self): + def discard_changes_dialog(self): yes, no, cancel = QMessageBox.Yes, QMessageBox.No, QMessageBox.Cancel msg = u'You have unsaved changes, would you like to save them and proceed?\nClick "No" to undo all changes.' return QMessageBox.warning(self, u'Attention', msg, yes | no | cancel) - def errorMessage(self, title, message): + def error_message(self, title, message): return QMessageBox.critical(self, title, '
%s
%s' % (title, message)) - def currentPath(self): - return os.path.dirname(self.filePath) if self.filePath else '.' + def current_path(self): + return os.path.dirname(self.file_path) if self.file_path else '.' - def chooseColor1(self): - color = self.colorDialog.getColor(self.lineColor, u'Choose line color', - default=DEFAULT_LINE_COLOR) + def choose_color1(self): + color = self.color_dialog.getColor(self.line_color, u'Choose line color', + default=DEFAULT_LINE_COLOR) if color: - self.lineColor = color + self.line_color = color Shape.line_color = color - self.canvas.setDrawingColor(color) + self.canvas.set_drawing_color(color) self.canvas.update() - self.setDirty() + self.set_dirty() - def deleteSelectedShape(self): - self.remLabel(self.canvas.deleteSelected()) - self.setDirty() - if self.noShapes(): + def delete_selected_shape(self): + self.remove_label(self.canvas.delete_selected()) + self.set_dirty() + if self.no_shapes(): for action in self.actions.onShapesPresent: action.setEnabled(False) - def chshapeLineColor(self): - color = self.colorDialog.getColor(self.lineColor, u'Choose line color', - default=DEFAULT_LINE_COLOR) + def choose_shape_line_color(self): + color = self.color_dialog.getColor(self.line_color, u'Choose line color', + default=DEFAULT_LINE_COLOR) if color: - self.canvas.selectedShape.line_color = color + self.canvas.selected_shape.line_color = color self.canvas.update() - self.setDirty() + self.set_dirty() - def chshapeFillColor(self): - color = self.colorDialog.getColor(self.fillColor, u'Choose fill color', - default=DEFAULT_FILL_COLOR) + def choose_shape_fill_color(self): + color = self.color_dialog.getColor(self.fill_color, u'Choose fill color', + default=DEFAULT_FILL_COLOR) if color: - self.canvas.selectedShape.fill_color = color + self.canvas.selected_shape.fill_color = color self.canvas.update() - self.setDirty() + self.set_dirty() - def copyShape(self): - self.canvas.endMove(copy=True) - self.addLabel(self.canvas.selectedShape) - self.setDirty() + def copy_shape(self): + self.canvas.end_move(copy=True) + self.add_label(self.canvas.selected_shape) + self.set_dirty() - def moveShape(self): - self.canvas.endMove(copy=False) - self.setDirty() + def move_shape(self): + self.canvas.end_move(copy=False) + self.set_dirty() - def loadPredefinedClasses(self, predefClassesFile): - if os.path.exists(predefClassesFile) is True: - with codecs.open(predefClassesFile, 'r', 'utf8') as f: + def load_predefined_classes(self, predef_classes_file): + if os.path.exists(predef_classes_file) is True: + with codecs.open(predef_classes_file, 'r', 'utf8') as f: for line in f: line = line.strip() - if self.labelHist is None: - self.labelHist = [line] + if self.label_hist is None: + self.label_hist = [line] else: - self.labelHist.append(line) + self.label_hist.append(line) - def loadPascalXMLByFilename(self, xmlPath): - if self.filePath is None: + def load_pascal_xml_by_filename(self, xml_path): + if self.file_path is None: return - if os.path.isfile(xmlPath) is False: + if os.path.isfile(xml_path) is False: return self.set_format(FORMAT_PASCALVOC) - tVocParseReader = PascalVocReader(xmlPath) - shapes = tVocParseReader.getShapes() - self.loadLabels(shapes) - self.canvas.verified = tVocParseReader.verified + t_voc_parse_reader = PascalVocReader(xml_path) + shapes = t_voc_parse_reader.get_shapes() + self.load_labels(shapes) + self.canvas.verified = t_voc_parse_reader.verified - def loadYOLOTXTByFilename(self, txtPath): - if self.filePath is None: + def load_yolo_txt_by_filename(self, txt_path): + if self.file_path is None: return - if os.path.isfile(txtPath) is False: + if os.path.isfile(txt_path) is False: return self.set_format(FORMAT_YOLO) - tYoloParseReader = YoloReader(txtPath, self.image) - shapes = tYoloParseReader.getShapes() - print (shapes) - self.loadLabels(shapes) - self.canvas.verified = tYoloParseReader.verified - - def loadCreateMLJSONByFilename(self, jsonPath, filePath): - if self.filePath is None: + t_yolo_parse_reader = YoloReader(txt_path, self.image) + shapes = t_yolo_parse_reader.get_shapes() + print(shapes) + self.load_labels(shapes) + self.canvas.verified = t_yolo_parse_reader.verified + + def load_create_ml_json_by_filename(self, json_path, file_path): + if self.file_path is None: return - if os.path.isfile(jsonPath) is False: + if os.path.isfile(json_path) is False: return self.set_format(FORMAT_CREATEML) - crmlParseReader = CreateMLReader(jsonPath, filePath) - shapes = crmlParseReader.get_shapes() - self.loadLabels(shapes) - self.canvas.verified = crmlParseReader.verified + create_ml_parse_reader = CreateMLReader(json_path, file_path) + shapes = create_ml_parse_reader.get_shapes() + self.load_labels(shapes) + self.canvas.verified = create_ml_parse_reader.verified - def copyPreviousBoundingBoxes(self): - currIndex = self.mImgList.index(self.filePath) - if currIndex - 1 >= 0: - prevFilePath = self.mImgList[currIndex - 1] - self.showBoundingBoxFromAnnotationFile(prevFilePath) - self.saveFile() + def copy_previous_bounding_boxes(self): + current_index = self.m_img_list.index(self.file_path) + if current_index - 1 >= 0: + prev_file_path = self.m_img_list[current_index - 1] + self.show_bounding_box_from_annotation_file(prev_file_path) + self.save_file() - def togglePaintLabelsOption(self): + def toggle_paint_labels_option(self): for shape in self.canvas.shapes: - shape.paintLabel = self.displayLabelOption.isChecked() + shape.paint_label = self.display_label_option.isChecked() - def toogleDrawSquare(self): - self.canvas.setDrawingShapeToSquare(self.drawSquaresOption.isChecked()) + def toggle_draw_square(self): + self.canvas.set_drawing_shape_to_square(self.draw_squares_option.isChecked()) def inverted(color): return QColor(*[255 - v for v in color.getRgb()]) @@ -1580,7 +1580,7 @@ def get_main_app(argv=[]): """ app = QApplication(argv) app.setApplicationName(__appname__) - app.setWindowIcon(newIcon("app")) + app.setWindowIcon(new_icon("app")) # Tzutalin 201705+: Accept extra agruments to change predefined class file argparser = argparse.ArgumentParser() argparser.add_argument("image_dir", nargs="?") @@ -1598,7 +1598,7 @@ def get_main_app(argv=[]): def main(): - '''construct main app and run it''' + """construct main app and run it""" app, _win = get_main_app(sys.argv) return app.exec_() diff --git a/libs/canvas.py b/libs/canvas.py index 23c5b823d..77b7867fc 100644 --- a/libs/canvas.py +++ b/libs/canvas.py @@ -7,7 +7,7 @@ from PyQt4.QtGui import * from PyQt4.QtCore import * -#from PyQt4.QtOpenGL import * +# from PyQt4.QtOpenGL import * from libs.shape import Shape from libs.utils import distance @@ -39,21 +39,21 @@ def __init__(self, *args, **kwargs): self.mode = self.EDIT self.shapes = [] self.current = None - self.selectedShape = None # save the selected shape here - self.selectedShapeCopy = None - self.drawingLineColor = QColor(0, 0, 255) - self.drawingRectColor = QColor(0, 0, 255) - self.line = Shape(line_color=self.drawingLineColor) - self.prevPoint = QPointF() + self.selected_shape = None # save the selected shape here + self.selected_shape_copy = None + self.drawing_line_color = QColor(0, 0, 255) + self.drawing_rect_color = QColor(0, 0, 255) + self.line = Shape(line_color=self.drawing_line_color) + self.prev_point = QPointF() self.offsets = QPointF(), QPointF() self.scale = 1.0 - self.labelFontSize = 8 + self.label_font_size = 8 self.pixmap = QPixmap() self.visible = {} - self._hideBackround = False - self.hideBackround = False - self.hShape = None - self.hVertex = None + self._hide_background = False + self.hide_background = False + self.h_shape = None + self.h_vertex = None self._painter = QPainter() self._cursor = CURSOR_DEFAULT # Menus: @@ -62,23 +62,23 @@ def __init__(self, *args, **kwargs): self.setMouseTracking(True) self.setFocusPolicy(Qt.WheelFocus) self.verified = False - self.drawSquare = False + self.draw_square = False - #initialisation for panning + # initialisation for panning self.pan_initial_pos = QPoint() - def setDrawingColor(self, qColor): - self.drawingLineColor = qColor - self.drawingRectColor = qColor + def set_drawing_color(self, qcolor): + self.drawing_line_color = qcolor + self.drawing_rect_color = qcolor def enterEvent(self, ev): - self.overrideCursor(self._cursor) + self.override_cursor(self._cursor) def leaveEvent(self, ev): - self.restoreCursor() + self.restore_cursor() def focusOutEvent(self, ev): - self.restoreCursor() + self.restore_cursor() def isVisible(self, shape): return self.visible.get(shape, True) @@ -89,44 +89,44 @@ def drawing(self): def editing(self): return self.mode == self.EDIT - def setEditing(self, value=True): + def set_editing(self, value=True): self.mode = self.EDIT if value else self.CREATE if not value: # Create - self.unHighlight() - self.deSelectShape() - self.prevPoint = QPointF() + self.un_highlight() + self.de_select_shape() + self.prev_point = QPointF() self.repaint() - def unHighlight(self): - if self.hShape: - self.hShape.highlightClear() - self.hVertex = self.hShape = None + def un_highlight(self): + if self.h_shape: + self.h_shape.highlight_clear() + self.h_vertex = self.h_shape = None - def selectedVertex(self): - return self.hVertex is not None + def selected_vertex(self): + return self.h_vertex is not None def mouseMoveEvent(self, ev): """Update line with last point and current coordinates.""" - pos = self.transformPos(ev.pos()) + pos = self.transform_pos(ev.pos()) # Update coordinates in status bar if image is opened window = self.parent().window() - if window.filePath is not None: - self.parent().window().labelCoordinates.setText( + if window.file_path is not None: + self.parent().window().label_coordinates.setText( 'X: %d; Y: %d' % (pos.x(), pos.y())) # Polygon drawing. if self.drawing(): - self.overrideCursor(CURSOR_DRAW) + self.override_cursor(CURSOR_DRAW) if self.current: # Display annotation width and height while drawing - currentWidth = abs(self.current[0].x() - pos.x()) - currentHeight = abs(self.current[0].y() - pos.y()) - self.parent().window().labelCoordinates.setText( - 'Width: %d, Height: %d / X: %d; Y: %d' % (currentWidth, currentHeight, pos.x(), pos.y())) + current_width = abs(self.current[0].x() - pos.x()) + current_height = abs(self.current[0].y() - pos.y()) + self.parent().window().label_coordinates.setText( + 'Width: %d, Height: %d / X: %d; Y: %d' % (current_width, current_height, pos.x(), pos.y())) - color = self.drawingLineColor - if self.outOfPixmap(pos): + color = self.drawing_line_color + if self.out_of_pixmap(pos): # Don't allow the user to draw outside the pixmap. # Clip the coordinates to 0 or max, # if they are outside the range [0, max] @@ -134,57 +134,57 @@ def mouseMoveEvent(self, ev): clipped_x = min(max(0, pos.x()), size.width()) clipped_y = min(max(0, pos.y()), size.height()) pos = QPointF(clipped_x, clipped_y) - elif len(self.current) > 1 and self.closeEnough(pos, self.current[0]): + elif len(self.current) > 1 and self.close_enough(pos, self.current[0]): # Attract line to starting point and colorise to alert the # user: pos = self.current[0] color = self.current.line_color - self.overrideCursor(CURSOR_POINT) - self.current.highlightVertex(0, Shape.NEAR_VERTEX) - - if self.drawSquare: - initPos = self.current[0] - minX = initPos.x() - minY = initPos.y() - min_size = min(abs(pos.x() - minX), abs(pos.y() - minY)) - directionX = -1 if pos.x() - minX < 0 else 1 - directionY = -1 if pos.y() - minY < 0 else 1 - self.line[1] = QPointF(minX + directionX * min_size, minY + directionY * min_size) + self.override_cursor(CURSOR_POINT) + self.current.highlight_vertex(0, Shape.NEAR_VERTEX) + + if self.draw_square: + init_pos = self.current[0] + min_x = init_pos.x() + min_y = init_pos.y() + min_size = min(abs(pos.x() - min_x), abs(pos.y() - min_y)) + direction_x = -1 if pos.x() - min_x < 0 else 1 + direction_y = -1 if pos.y() - min_y < 0 else 1 + self.line[1] = QPointF(min_x + direction_x * min_size, min_y + direction_y * min_size) else: self.line[1] = pos self.line.line_color = color - self.prevPoint = QPointF() - self.current.highlightClear() + self.prev_point = QPointF() + self.current.highlight_clear() else: - self.prevPoint = pos + self.prev_point = pos self.repaint() return # Polygon copy moving. if Qt.RightButton & ev.buttons(): - if self.selectedShapeCopy and self.prevPoint: - self.overrideCursor(CURSOR_MOVE) - self.boundedMoveShape(self.selectedShapeCopy, pos) + if self.selected_shape_copy and self.prev_point: + self.override_cursor(CURSOR_MOVE) + self.bounded_move_shape(self.selected_shape_copy, pos) self.repaint() - elif self.selectedShape: - self.selectedShapeCopy = self.selectedShape.copy() + elif self.selected_shape: + self.selected_shape_copy = self.selected_shape.copy() self.repaint() return # Polygon/Vertex moving. if Qt.LeftButton & ev.buttons(): - if self.selectedVertex(): - self.boundedMoveVertex(pos) + if self.selected_vertex(): + self.bounded_move_vertex(pos) self.shapeMoved.emit() self.repaint() - elif self.selectedShape and self.prevPoint: - self.overrideCursor(CURSOR_MOVE) - self.boundedMoveShape(self.selectedShape, pos) + elif self.selected_shape and self.prev_point: + self.override_cursor(CURSOR_MOVE) + self.bounded_move_shape(self.selected_shape, pos) self.shapeMoved.emit() self.repaint() else: - #pan + # pan delta_x = pos.x() - self.pan_initial_pos.x() delta_y = pos.y() - self.pan_initial_pos.y() self.scrollRequest.emit(delta_x, Qt.Horizontal) @@ -192,7 +192,7 @@ def mouseMoveEvent(self, ev): self.update() return - # Just hovering over the canvas, 2 posibilities: + # Just hovering over the canvas, 2 possibilities: # - Highlight shapes # - Highlight vertex # Update shape/vertex fill and tooltip value accordingly. @@ -200,163 +200,163 @@ def mouseMoveEvent(self, ev): for shape in reversed([s for s in self.shapes if self.isVisible(s)]): # Look for a nearby vertex to highlight. If that fails, # check if we happen to be inside a shape. - index = shape.nearestVertex(pos, self.epsilon) + index = shape.nearest_vertex(pos, self.epsilon) if index is not None: - if self.selectedVertex(): - self.hShape.highlightClear() - self.hVertex, self.hShape = index, shape - shape.highlightVertex(index, shape.MOVE_VERTEX) - self.overrideCursor(CURSOR_POINT) + if self.selected_vertex(): + self.h_shape.highlight_clear() + self.h_vertex, self.h_shape = index, shape + shape.highlight_vertex(index, shape.MOVE_VERTEX) + self.override_cursor(CURSOR_POINT) self.setToolTip("Click & drag to move point") self.setStatusTip(self.toolTip()) self.update() break - elif shape.containsPoint(pos): - if self.selectedVertex(): - self.hShape.highlightClear() - self.hVertex, self.hShape = None, shape + elif shape.contains_point(pos): + if self.selected_vertex(): + self.h_shape.highlight_clear() + self.h_vertex, self.h_shape = None, shape self.setToolTip( "Click & drag to move shape '%s'" % shape.label) self.setStatusTip(self.toolTip()) - self.overrideCursor(CURSOR_GRAB) + self.override_cursor(CURSOR_GRAB) self.update() break else: # Nothing found, clear highlights, reset state. - if self.hShape: - self.hShape.highlightClear() + if self.h_shape: + self.h_shape.highlight_clear() self.update() - self.hVertex, self.hShape = None, None - self.overrideCursor(CURSOR_DEFAULT) + self.h_vertex, self.h_shape = None, None + self.override_cursor(CURSOR_DEFAULT) def mousePressEvent(self, ev): - pos = self.transformPos(ev.pos()) + pos = self.transform_pos(ev.pos()) if ev.button() == Qt.LeftButton: if self.drawing(): - self.handleDrawing(pos) + self.handle_drawing(pos) else: - selection = self.selectShapePoint(pos) - self.prevPoint = pos + selection = self.select_shape_point(pos) + self.prev_point = pos if selection is None: - #pan + # pan QApplication.setOverrideCursor(QCursor(Qt.OpenHandCursor)) self.pan_initial_pos = pos elif ev.button() == Qt.RightButton and self.editing(): - self.selectShapePoint(pos) - self.prevPoint = pos + self.select_shape_point(pos) + self.prev_point = pos self.update() def mouseReleaseEvent(self, ev): if ev.button() == Qt.RightButton: - menu = self.menus[bool(self.selectedShapeCopy)] - self.restoreCursor() + menu = self.menus[bool(self.selected_shape_copy)] + self.restore_cursor() if not menu.exec_(self.mapToGlobal(ev.pos()))\ - and self.selectedShapeCopy: + and self.selected_shape_copy: # Cancel the move by deleting the shadow copy. - self.selectedShapeCopy = None + self.selected_shape_copy = None self.repaint() - elif ev.button() == Qt.LeftButton and self.selectedShape: - if self.selectedVertex(): - self.overrideCursor(CURSOR_POINT) + elif ev.button() == Qt.LeftButton and self.selected_shape: + if self.selected_vertex(): + self.override_cursor(CURSOR_POINT) else: - self.overrideCursor(CURSOR_GRAB) + self.override_cursor(CURSOR_GRAB) elif ev.button() == Qt.LeftButton: - pos = self.transformPos(ev.pos()) + pos = self.transform_pos(ev.pos()) if self.drawing(): - self.handleDrawing(pos) + self.handle_drawing(pos) else: - #pan + # pan QApplication.restoreOverrideCursor() - def endMove(self, copy=False): - assert self.selectedShape and self.selectedShapeCopy - shape = self.selectedShapeCopy - #del shape.fill_color - #del shape.line_color + def end_move(self, copy=False): + assert self.selected_shape and self.selected_shape_copy + shape = self.selected_shape_copy + # del shape.fill_color + # del shape.line_color if copy: self.shapes.append(shape) - self.selectedShape.selected = False - self.selectedShape = shape + self.selected_shape.selected = False + self.selected_shape = shape self.repaint() else: - self.selectedShape.points = [p for p in shape.points] - self.selectedShapeCopy = None + self.selected_shape.points = [p for p in shape.points] + self.selected_shape_copy = None - def hideBackroundShapes(self, value): - self.hideBackround = value - if self.selectedShape: + def hide_background_shapes(self, value): + self.hide_background = value + if self.selected_shape: # Only hide other shapes if there is a current selection. # Otherwise the user will not be able to select a shape. - self.setHiding(True) + self.set_hiding(True) self.repaint() - def handleDrawing(self, pos): - if self.current and self.current.reachMaxPoints() is False: - initPos = self.current[0] - minX = initPos.x() - minY = initPos.y() - targetPos = self.line[1] - maxX = targetPos.x() - maxY = targetPos.y() - self.current.addPoint(QPointF(maxX, minY)) - self.current.addPoint(targetPos) - self.current.addPoint(QPointF(minX, maxY)) + def handle_drawing(self, pos): + if self.current and self.current.reach_max_points() is False: + init_pos = self.current[0] + min_x = init_pos.x() + min_y = init_pos.y() + target_pos = self.line[1] + max_x = target_pos.x() + max_y = target_pos.y() + self.current.add_point(QPointF(max_x, min_y)) + self.current.add_point(target_pos) + self.current.add_point(QPointF(min_x, max_y)) self.finalise() - elif not self.outOfPixmap(pos): + elif not self.out_of_pixmap(pos): self.current = Shape() - self.current.addPoint(pos) + self.current.add_point(pos) self.line.points = [pos, pos] - self.setHiding() + self.set_hiding() self.drawingPolygon.emit(True) self.update() - def setHiding(self, enable=True): - self._hideBackround = self.hideBackround if enable else False + def set_hiding(self, enable=True): + self._hide_background = self.hide_background if enable else False - def canCloseShape(self): + def can_close_shape(self): return self.drawing() and self.current and len(self.current) > 2 def mouseDoubleClickEvent(self, ev): # We need at least 4 points here, since the mousePress handler # adds an extra one before this handler is called. - if self.canCloseShape() and len(self.current) > 3: - self.current.popPoint() + if self.can_close_shape() and len(self.current) > 3: + self.current.pop_point() self.finalise() - def selectShape(self, shape): - self.deSelectShape() + def select_shape(self, shape): + self.de_select_shape() shape.selected = True - self.selectedShape = shape - self.setHiding() + self.selected_shape = shape + self.set_hiding() self.selectionChanged.emit(True) self.update() - def selectShapePoint(self, point): + def select_shape_point(self, point): """Select the first shape created which contains this point.""" - self.deSelectShape() - if self.selectedVertex(): # A vertex is marked for selection. - index, shape = self.hVertex, self.hShape - shape.highlightVertex(index, shape.MOVE_VERTEX) - self.selectShape(shape) - return self.hVertex + self.de_select_shape() + if self.selected_vertex(): # A vertex is marked for selection. + index, shape = self.h_vertex, self.h_shape + shape.highlight_vertex(index, shape.MOVE_VERTEX) + self.select_shape(shape) + return self.h_vertex for shape in reversed(self.shapes): - if self.isVisible(shape) and shape.containsPoint(point): - self.selectShape(shape) - self.calculateOffsets(shape, point) - return self.selectedShape + if self.isVisible(shape) and shape.contains_point(point): + self.select_shape(shape) + self.calculate_offsets(shape, point) + return self.selected_shape return None - def calculateOffsets(self, shape, point): - rect = shape.boundingRect() + def calculate_offsets(self, shape, point): + rect = shape.bounding_rect() x1 = rect.x() - point.x() y1 = rect.y() - point.y() x2 = (rect.x() + rect.width()) - point.x() y2 = (rect.y() + rect.height()) - point.y() self.offsets = QPointF(x1, y1), QPointF(x2, y2) - def snapPointToCanvas(self, x, y): + def snap_point_to_canvas(self, x, y): """ Moves a point x,y to within the boundaries of the canvas. :return: (x,y,snapped) where snapped is True if x or y were changed, False if not. @@ -370,99 +370,99 @@ def snapPointToCanvas(self, x, y): return x, y, False - def boundedMoveVertex(self, pos): - index, shape = self.hVertex, self.hShape + def bounded_move_vertex(self, pos): + index, shape = self.h_vertex, self.h_shape point = shape[index] - if self.outOfPixmap(pos): + if self.out_of_pixmap(pos): size = self.pixmap.size() clipped_x = min(max(0, pos.x()), size.width()) clipped_y = min(max(0, pos.y()), size.height()) pos = QPointF(clipped_x, clipped_y) - if self.drawSquare: + if self.draw_square: opposite_point_index = (index + 2) % 4 opposite_point = shape[opposite_point_index] min_size = min(abs(pos.x() - opposite_point.x()), abs(pos.y() - opposite_point.y())) - directionX = -1 if pos.x() - opposite_point.x() < 0 else 1 - directionY = -1 if pos.y() - opposite_point.y() < 0 else 1 - shiftPos = QPointF(opposite_point.x() + directionX * min_size - point.x(), - opposite_point.y() + directionY * min_size - point.y()) + direction_x = -1 if pos.x() - opposite_point.x() < 0 else 1 + direction_y = -1 if pos.y() - opposite_point.y() < 0 else 1 + shift_pos = QPointF(opposite_point.x() + direction_x * min_size - point.x(), + opposite_point.y() + direction_y * min_size - point.y()) else: - shiftPos = pos - point + shift_pos = pos - point - shape.moveVertexBy(index, shiftPos) + shape.move_vertex_by(index, shift_pos) - lindex = (index + 1) % 4 - rindex = (index + 3) % 4 - lshift = None - rshift = None + left_index = (index + 1) % 4 + right_index = (index + 3) % 4 + left_shift = None + right_shift = None if index % 2 == 0: - rshift = QPointF(shiftPos.x(), 0) - lshift = QPointF(0, shiftPos.y()) + right_shift = QPointF(shift_pos.x(), 0) + left_shift = QPointF(0, shift_pos.y()) else: - lshift = QPointF(shiftPos.x(), 0) - rshift = QPointF(0, shiftPos.y()) - shape.moveVertexBy(rindex, rshift) - shape.moveVertexBy(lindex, lshift) + left_shift = QPointF(shift_pos.x(), 0) + right_shift = QPointF(0, shift_pos.y()) + shape.move_vertex_by(right_index, right_shift) + shape.move_vertex_by(left_index, left_shift) - def boundedMoveShape(self, shape, pos): - if self.outOfPixmap(pos): + def bounded_move_shape(self, shape, pos): + if self.out_of_pixmap(pos): return False # No need to move o1 = pos + self.offsets[0] - if self.outOfPixmap(o1): + if self.out_of_pixmap(o1): pos -= QPointF(min(0, o1.x()), min(0, o1.y())) o2 = pos + self.offsets[1] - if self.outOfPixmap(o2): + if self.out_of_pixmap(o2): pos += QPointF(min(0, self.pixmap.width() - o2.x()), min(0, self.pixmap.height() - o2.y())) # The next line tracks the new position of the cursor # relative to the shape, but also results in making it # a bit "shaky" when nearing the border and allows it to # go outside of the shape's area for some reason. XXX - #self.calculateOffsets(self.selectedShape, pos) - dp = pos - self.prevPoint + # self.calculateOffsets(self.selectedShape, pos) + dp = pos - self.prev_point if dp: - shape.moveBy(dp) - self.prevPoint = pos + shape.move_by(dp) + self.prev_point = pos return True return False - def deSelectShape(self): - if self.selectedShape: - self.selectedShape.selected = False - self.selectedShape = None - self.setHiding(False) + def de_select_shape(self): + if self.selected_shape: + self.selected_shape.selected = False + self.selected_shape = None + self.set_hiding(False) self.selectionChanged.emit(False) self.update() - def deleteSelected(self): - if self.selectedShape: - shape = self.selectedShape - self.shapes.remove(self.selectedShape) - self.selectedShape = None + def delete_selected(self): + if self.selected_shape: + shape = self.selected_shape + self.shapes.remove(self.selected_shape) + self.selected_shape = None self.update() return shape - def copySelectedShape(self): - if self.selectedShape: - shape = self.selectedShape.copy() - self.deSelectShape() + def copy_selected_shape(self): + if self.selected_shape: + shape = self.selected_shape.copy() + self.de_select_shape() self.shapes.append(shape) shape.selected = True - self.selectedShape = shape - self.boundedShiftShape(shape) + self.selected_shape = shape + self.bounded_shift_shape(shape) return shape - def boundedShiftShape(self, shape): + def bounded_shift_shape(self, shape): # Try to move in one direction, and if it fails in another. # Give up if both fail. point = shape[0] offset = QPointF(2.0, 2.0) - self.calculateOffsets(shape, point) - self.prevPoint = point - if not self.boundedMoveShape(shape, point - offset): - self.boundedMoveShape(shape, point + offset) + self.calculate_offsets(shape, point) + self.prev_point = point + if not self.bounded_move_shape(shape, point - offset): + self.bounded_move_shape(shape, point + offset) def paintEvent(self, event): if not self.pixmap: @@ -475,36 +475,36 @@ def paintEvent(self, event): p.setRenderHint(QPainter.SmoothPixmapTransform) p.scale(self.scale, self.scale) - p.translate(self.offsetToCenter()) + p.translate(self.offset_to_center()) p.drawPixmap(0, 0, self.pixmap) Shape.scale = self.scale - Shape.labelFontSize = self.labelFontSize + Shape.label_font_size = self.label_font_size for shape in self.shapes: - if (shape.selected or not self._hideBackround) and self.isVisible(shape): - shape.fill = shape.selected or shape == self.hShape + if (shape.selected or not self._hide_background) and self.isVisible(shape): + shape.fill = shape.selected or shape == self.h_shape shape.paint(p) if self.current: self.current.paint(p) self.line.paint(p) - if self.selectedShapeCopy: - self.selectedShapeCopy.paint(p) + if self.selected_shape_copy: + self.selected_shape_copy.paint(p) # Paint rect if self.current is not None and len(self.line) == 2: - leftTop = self.line[0] - rightBottom = self.line[1] - rectWidth = rightBottom.x() - leftTop.x() - rectHeight = rightBottom.y() - leftTop.y() - p.setPen(self.drawingRectColor) + left_top = self.line[0] + right_bottom = self.line[1] + rect_width = right_bottom.x() - left_top.x() + rect_height = right_bottom.y() - left_top.y() + p.setPen(self.drawing_rect_color) brush = QBrush(Qt.BDiagPattern) p.setBrush(brush) - p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight) + p.drawRect(left_top.x(), left_top.y(), rect_width, rect_height) - if self.drawing() and not self.prevPoint.isNull() and not self.outOfPixmap(self.prevPoint): + if self.drawing() and not self.prev_point.isNull() and not self.out_of_pixmap(self.prev_point): p.setPen(QColor(0, 0, 0)) - p.drawLine(self.prevPoint.x(), 0, self.prevPoint.x(), self.pixmap.height()) - p.drawLine(0, self.prevPoint.y(), self.pixmap.width(), self.prevPoint.y()) + p.drawLine(self.prev_point.x(), 0, self.prev_point.x(), self.pixmap.height()) + p.drawLine(0, self.prev_point.y(), self.pixmap.width(), self.prev_point.y()) self.setAutoFillBackground(True) if self.verified: @@ -518,11 +518,11 @@ def paintEvent(self, event): p.end() - def transformPos(self, point): + def transform_pos(self, point): """Convert from widget-logical coordinates to painter-logical coordinates.""" - return point / self.scale - self.offsetToCenter() + return point / self.scale - self.offset_to_center() - def offsetToCenter(self): + def offset_to_center(self): s = self.scale area = super(Canvas, self).size() w, h = self.pixmap.width() * s, self.pixmap.height() * s @@ -531,7 +531,7 @@ def offsetToCenter(self): y = (ah - h) / (2 * s) if ah > h else 0 return QPointF(x, y) - def outOfPixmap(self, p): + def out_of_pixmap(self, p): w, h = self.pixmap.width(), self.pixmap.height() return not (0 <= p.x() <= w and 0 <= p.y() <= h) @@ -546,13 +546,13 @@ def finalise(self): self.current.close() self.shapes.append(self.current) self.current = None - self.setHiding(False) + self.set_hiding(False) self.newShape.emit() self.update() - def closeEnough(self, p1, p2): - #d = distance(p1 - p2) - #m = (p1-p2).manhattanLength() + def close_enough(self, p1, p2): + # d = distance(p1 - p2) + # m = (p1-p2).manhattanLength() # print "d %.2f, m %d, %.2f" % (d, m, d - m) return distance(p1 - p2) < self.epsilon @@ -595,51 +595,51 @@ def keyPressEvent(self, ev): self.current = None self.drawingPolygon.emit(False) self.update() - elif key == Qt.Key_Return and self.canCloseShape(): + elif key == Qt.Key_Return and self.can_close_shape(): self.finalise() - elif key == Qt.Key_Left and self.selectedShape: - self.moveOnePixel('Left') - elif key == Qt.Key_Right and self.selectedShape: - self.moveOnePixel('Right') - elif key == Qt.Key_Up and self.selectedShape: - self.moveOnePixel('Up') - elif key == Qt.Key_Down and self.selectedShape: - self.moveOnePixel('Down') - - def moveOnePixel(self, direction): + elif key == Qt.Key_Left and self.selected_shape: + self.move_one_pixel('Left') + elif key == Qt.Key_Right and self.selected_shape: + self.move_one_pixel('Right') + elif key == Qt.Key_Up and self.selected_shape: + self.move_one_pixel('Up') + elif key == Qt.Key_Down and self.selected_shape: + self.move_one_pixel('Down') + + def move_one_pixel(self, direction): # print(self.selectedShape.points) - if direction == 'Left' and not self.moveOutOfBound(QPointF(-1.0, 0)): + if direction == 'Left' and not self.move_out_of_bound(QPointF(-1.0, 0)): # print("move Left one pixel") - self.selectedShape.points[0] += QPointF(-1.0, 0) - self.selectedShape.points[1] += QPointF(-1.0, 0) - self.selectedShape.points[2] += QPointF(-1.0, 0) - self.selectedShape.points[3] += QPointF(-1.0, 0) - elif direction == 'Right' and not self.moveOutOfBound(QPointF(1.0, 0)): + self.selected_shape.points[0] += QPointF(-1.0, 0) + self.selected_shape.points[1] += QPointF(-1.0, 0) + self.selected_shape.points[2] += QPointF(-1.0, 0) + self.selected_shape.points[3] += QPointF(-1.0, 0) + elif direction == 'Right' and not self.move_out_of_bound(QPointF(1.0, 0)): # print("move Right one pixel") - self.selectedShape.points[0] += QPointF(1.0, 0) - self.selectedShape.points[1] += QPointF(1.0, 0) - self.selectedShape.points[2] += QPointF(1.0, 0) - self.selectedShape.points[3] += QPointF(1.0, 0) - elif direction == 'Up' and not self.moveOutOfBound(QPointF(0, -1.0)): + self.selected_shape.points[0] += QPointF(1.0, 0) + self.selected_shape.points[1] += QPointF(1.0, 0) + self.selected_shape.points[2] += QPointF(1.0, 0) + self.selected_shape.points[3] += QPointF(1.0, 0) + elif direction == 'Up' and not self.move_out_of_bound(QPointF(0, -1.0)): # print("move Up one pixel") - self.selectedShape.points[0] += QPointF(0, -1.0) - self.selectedShape.points[1] += QPointF(0, -1.0) - self.selectedShape.points[2] += QPointF(0, -1.0) - self.selectedShape.points[3] += QPointF(0, -1.0) - elif direction == 'Down' and not self.moveOutOfBound(QPointF(0, 1.0)): + self.selected_shape.points[0] += QPointF(0, -1.0) + self.selected_shape.points[1] += QPointF(0, -1.0) + self.selected_shape.points[2] += QPointF(0, -1.0) + self.selected_shape.points[3] += QPointF(0, -1.0) + elif direction == 'Down' and not self.move_out_of_bound(QPointF(0, 1.0)): # print("move Down one pixel") - self.selectedShape.points[0] += QPointF(0, 1.0) - self.selectedShape.points[1] += QPointF(0, 1.0) - self.selectedShape.points[2] += QPointF(0, 1.0) - self.selectedShape.points[3] += QPointF(0, 1.0) + self.selected_shape.points[0] += QPointF(0, 1.0) + self.selected_shape.points[1] += QPointF(0, 1.0) + self.selected_shape.points[2] += QPointF(0, 1.0) + self.selected_shape.points[3] += QPointF(0, 1.0) self.shapeMoved.emit() self.repaint() - def moveOutOfBound(self, step): - points = [p1+p2 for p1, p2 in zip(self.selectedShape.points, [step]*4)] - return True in map(self.outOfPixmap, points) + def move_out_of_bound(self, step): + points = [p1 + p2 for p1, p2 in zip(self.selected_shape.points, [step] * 4)] + return True in map(self.out_of_pixmap, points) - def setLastLabel(self, text, line_color = None, fill_color = None): + def set_last_label(self, text, line_color=None, fill_color=None): assert text self.shapes[-1].label = text if line_color: @@ -650,57 +650,57 @@ def setLastLabel(self, text, line_color = None, fill_color = None): return self.shapes[-1] - def undoLastLine(self): + def undo_last_line(self): assert self.shapes self.current = self.shapes.pop() - self.current.setOpen() + self.current.set_open() self.line.points = [self.current[-1], self.current[0]] self.drawingPolygon.emit(True) - def resetAllLines(self): + def reset_all_lines(self): assert self.shapes self.current = self.shapes.pop() - self.current.setOpen() + self.current.set_open() self.line.points = [self.current[-1], self.current[0]] self.drawingPolygon.emit(True) self.current = None self.drawingPolygon.emit(False) self.update() - def loadPixmap(self, pixmap): + def load_pixmap(self, pixmap): self.pixmap = pixmap self.shapes = [] self.repaint() - def loadShapes(self, shapes): + def load_shapes(self, shapes): self.shapes = list(shapes) self.current = None self.repaint() - def setShapeVisible(self, shape, value): + def set_shape_visible(self, shape, value): self.visible[shape] = value self.repaint() - def currentCursor(self): + def current_cursor(self): cursor = QApplication.overrideCursor() if cursor is not None: cursor = cursor.shape() return cursor - def overrideCursor(self, cursor): + def override_cursor(self, cursor): self._cursor = cursor - if self.currentCursor() is None: + if self.current_cursor() is None: QApplication.setOverrideCursor(cursor) else: QApplication.changeOverrideCursor(cursor) - def restoreCursor(self): + def restore_cursor(self): QApplication.restoreOverrideCursor() - def resetState(self): - self.restoreCursor() + def reset_state(self): + self.restore_cursor() self.pixmap = None self.update() - def setDrawingShapeToSquare(self, status): - self.drawSquare = status + def set_drawing_shape_to_square(self, status): + self.draw_square = status diff --git a/libs/colorDialog.py b/libs/colorDialog.py index d5d947591..5cd28025c 100644 --- a/libs/colorDialog.py +++ b/libs/colorDialog.py @@ -22,7 +22,7 @@ def __init__(self, parent=None): self.default = None self.bb = self.layout().itemAt(1).widget() self.bb.addButton(BB.RestoreDefaults) - self.bb.clicked.connect(self.checkRestore) + self.bb.clicked.connect(self.check_restore) def getColor(self, value=None, title=None, default=None): self.default = default @@ -32,6 +32,6 @@ def getColor(self, value=None, title=None, default=None): self.setCurrentColor(value) return self.currentColor() if self.exec_() else None - def checkRestore(self, button): + def check_restore(self, button): if self.bb.buttonRole(button) & BB.ResetRole and self.default: self.setCurrentColor(self.default) diff --git a/libs/combobox.py b/libs/combobox.py index 298bfa06a..8743b648e 100644 --- a/libs/combobox.py +++ b/libs/combobox.py @@ -21,7 +21,7 @@ def __init__(self, parent=None, items=[]): self.items = items self.cb.addItems(self.items) - self.cb.currentIndexChanged.connect(parent.comboSelectionChanged) + self.cb.currentIndexChanged.connect(parent.combo_selection_changed) layout.addWidget(self.cb) self.setLayout(layout) diff --git a/libs/create_ml_io.py b/libs/create_ml_io.py index 0d0781464..922ae1f63 100644 --- a/libs/create_ml_io.py +++ b/libs/create_ml_io.py @@ -11,26 +11,26 @@ class CreateMLWriter: - def __init__(self, foldername, filename, imgsize, shapes, outputfile, databasesrc='Unknown', localimgpath=None): - self.foldername = foldername + def __init__(self, folder_name, filename, img_size, shapes, output_file, database_src='Unknown', local_img_path=None): + self.folder_name = folder_name self.filename = filename - self.databasesrc = databasesrc - self.imgsize = imgsize - self.boxlist = [] - self.localimgpath = localimgpath + self.database_src = database_src + self.img_size = img_size + self.box_list = [] + self.local_img_path = local_img_path self.verified = False self.shapes = shapes - self.outputfile = outputfile + self.output_file = output_file def write(self): - if os.path.isfile(self.outputfile): - with open(self.outputfile, "r") as file: + if os.path.isfile(self.output_file): + with open(self.output_file, "r") as file: input_data = file.read() - outputdict = json.loads(input_data) + output_dict = json.loads(input_data) else: - outputdict = [] + output_dict = [] - outputimagedict = { + output_image_dict = { "image": self.filename, "annotations": [] } @@ -45,7 +45,7 @@ def write(self): height, width, x, y = self.calculate_coordinates(x1, x2, y1, y2) - shapedict = { + shape_dict = { "label": shape["label"], "coordinates": { "x": x, @@ -54,77 +54,77 @@ def write(self): "height": height } } - outputimagedict["annotations"].append(shapedict) + output_image_dict["annotations"].append(shape_dict) # check if image already in output exists = False - for i in range(0, len(outputdict)): - if outputdict[i]["image"] == outputimagedict["image"]: + for i in range(0, len(output_dict)): + if output_dict[i]["image"] == output_image_dict["image"]: exists = True - outputdict[i] = outputimagedict + output_dict[i] = output_image_dict break if not exists: - outputdict.append(outputimagedict) + output_dict.append(output_image_dict) - Path(self.outputfile).write_text(json.dumps(outputdict), ENCODE_METHOD) + Path(self.output_file).write_text(json.dumps(output_dict), ENCODE_METHOD) def calculate_coordinates(self, x1, x2, y1, y2): if x1 < x2: - xmin = x1 - xmax = x2 + x_min = x1 + x_max = x2 else: - xmin = x2 - xmax = x1 + x_min = x2 + x_max = x1 if y1 < y2: - ymin = y1 - ymax = y2 + y_min = y1 + y_max = y2 else: - ymin = y2 - ymax = y1 - width = xmax - xmin + y_min = y2 + y_max = y1 + width = x_max - x_min if width < 0: width = width * -1 - height = ymax - ymin + height = y_max - y_min # x and y from center of rect - x = xmin + width / 2 - y = ymin + height / 2 + x = x_min + width / 2 + y = y_min + height / 2 return height, width, x, y class CreateMLReader: - def __init__(self, jsonpath, filepath): - self.jsonpath = jsonpath + def __init__(self, json_path, file_path): + self.json_path = json_path self.shapes = [] self.verified = False - self.filename = filepath.split("/")[-1:][0] + self.filename = file_path.split("/")[-1:][0] try: self.parse_json() except ValueError: print("JSON decoding failed") def parse_json(self): - with open(self.jsonpath, "r") as file: - inputdata = file.read() + with open(self.json_path, "r") as file: + input_data = file.read() - outputdict = json.loads(inputdata) + output_dict = json.loads(input_data) self.verified = True if len(self.shapes) > 0: self.shapes = [] - for image in outputdict: + for image in output_dict: if image["image"] == self.filename: for shape in image["annotations"]: self.add_shape(shape["label"], shape["coordinates"]) - def add_shape(self, label, bndbox): - xmin = bndbox["x"] - (bndbox["width"] / 2) - ymin = bndbox["y"] - (bndbox["height"] / 2) + def add_shape(self, label, bnd_box): + x_min = bnd_box["x"] - (bnd_box["width"] / 2) + y_min = bnd_box["y"] - (bnd_box["height"] / 2) - xmax = bndbox["x"] + (bndbox["width"] / 2) - ymax = bndbox["y"] + (bndbox["height"] / 2) + x_max = bnd_box["x"] + (bnd_box["width"] / 2) + y_max = bnd_box["y"] + (bnd_box["height"] / 2) - points = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)] + points = [(x_min, y_min), (x_max, y_min), (x_max, y_max), (x_min, y_max)] self.shapes.append((label, points, None, None, True)) def get_shapes(self): diff --git a/libs/labelDialog.py b/libs/labelDialog.py index 924a4a8d3..1ee3a1991 100644 --- a/libs/labelDialog.py +++ b/libs/labelDialog.py @@ -6,43 +6,43 @@ from PyQt4.QtGui import * from PyQt4.QtCore import * -from libs.utils import newIcon, labelValidator +from libs.utils import new_icon, label_validator BB = QDialogButtonBox class LabelDialog(QDialog): - def __init__(self, text="Enter object label", parent=None, listItem=None): + def __init__(self, text="Enter object label", parent=None, list_item=None): super(LabelDialog, self).__init__(parent) self.edit = QLineEdit() self.edit.setText(text) - self.edit.setValidator(labelValidator()) - self.edit.editingFinished.connect(self.postProcess) + self.edit.setValidator(label_validator()) + self.edit.editingFinished.connect(self.post_process) model = QStringListModel() - model.setStringList(listItem) + model.setStringList(list_item) completer = QCompleter() completer.setModel(model) self.edit.setCompleter(completer) layout = QVBoxLayout() layout.addWidget(self.edit) - self.buttonBox = bb = BB(BB.Ok | BB.Cancel, Qt.Horizontal, self) - bb.button(BB.Ok).setIcon(newIcon('done')) - bb.button(BB.Cancel).setIcon(newIcon('undo')) + self.button_box = bb = BB(BB.Ok | BB.Cancel, Qt.Horizontal, self) + bb.button(BB.Ok).setIcon(new_icon('done')) + bb.button(BB.Cancel).setIcon(new_icon('undo')) bb.accepted.connect(self.validate) bb.rejected.connect(self.reject) layout.addWidget(bb) - if listItem is not None and len(listItem) > 0: - self.listWidget = QListWidget(self) - for item in listItem: - self.listWidget.addItem(item) - self.listWidget.itemClicked.connect(self.listItemClick) - self.listWidget.itemDoubleClicked.connect(self.listItemDoubleClick) - layout.addWidget(self.listWidget) + if list_item is not None and len(list_item) > 0: + self.list_widget = QListWidget(self) + for item in list_item: + self.list_widget.addItem(item) + self.list_widget.itemClicked.connect(self.list_item_click) + self.list_widget.itemDoubleClicked.connect(self.list_item_double_click) + layout.addWidget(self.list_widget) self.setLayout(layout) @@ -55,22 +55,22 @@ def validate(self): if self.edit.text().strip(): self.accept() - def postProcess(self): + def post_process(self): try: self.edit.setText(self.edit.text().trimmed()) except AttributeError: # PyQt5: AttributeError: 'str' object has no attribute 'trimmed' self.edit.setText(self.edit.text()) - def popUp(self, text='', move=True): + def pop_up(self, text='', move=True): self.edit.setText(text) self.edit.setSelection(0, len(text)) self.edit.setFocus(Qt.PopupFocusReason) if move: cursor_pos = QCursor.pos() - parent_bottomRight = self.parentWidget().geometry() - max_x = parent_bottomRight.x() + parent_bottomRight.width() - self.sizeHint().width() - max_y = parent_bottomRight.y() + parent_bottomRight.height() - self.sizeHint().height() + parent_bottom_right = self.parentWidget().geometry() + max_x = parent_bottom_right.x() + parent_bottom_right.width() - self.sizeHint().width() + max_y = parent_bottom_right.y() + parent_bottom_right.height() - self.sizeHint().height() max_global = self.parentWidget().mapToGlobal(QPoint(max_x, max_y)) if cursor_pos.x() > max_global.x(): cursor_pos.setX(max_global.x()) @@ -79,14 +79,14 @@ def popUp(self, text='', move=True): self.move(cursor_pos) return self.edit.text() if self.exec_() else None - def listItemClick(self, tQListWidgetItem): + def list_item_click(self, t_qlist_widget_item): try: - text = tQListWidgetItem.text().trimmed() + text = t_qlist_widget_item.text().trimmed() except AttributeError: # PyQt5: AttributeError: 'str' object has no attribute 'trimmed' - text = tQListWidgetItem.text().strip() + text = t_qlist_widget_item.text().strip() self.edit.setText(text) - def listItemDoubleClick(self, tQListWidgetItem): - self.listItemClick(tQListWidgetItem) + def list_item_double_click(self, t_qlist_widget_item): + self.list_item_click(t_qlist_widget_item) self.validate() diff --git a/libs/labelFile.py b/libs/labelFile.py index b366d45e5..378576771 100644 --- a/libs/labelFile.py +++ b/libs/labelFile.py @@ -18,7 +18,7 @@ class LabelFileFormat(Enum): - PASCAL_VOC= 1 + PASCAL_VOC = 1 YOLO = 2 CREATE_ML = 3 @@ -34,44 +34,44 @@ class LabelFile(object): def __init__(self, filename=None): self.shapes = () - self.imagePath = None - self.imageData = None + self.image_path = None + self.image_data = None self.verified = False - def saveCreateMLFormat(self, filename, shapes, imagePath, imageData, classList, lineColor=None, fillColor=None, databaseSrc=None): - imgFolderPath = os.path.dirname(imagePath) - imgFolderName = os.path.split(imgFolderPath)[-1] - imgFileName = os.path.basename(imagePath) - outputFilePath = "/".join(filename.split("/")[:-1]) - outputFile = outputFilePath + "/" + imgFolderName + JSON_EXT + def save_create_ml_format(self, filename, shapes, image_path, image_data, class_list, line_color=None, fill_color=None, database_src=None): + img_folder_path = os.path.dirname(image_path) + img_folder_name = os.path.split(img_folder_path)[-1] + img_file_name = os.path.basename(image_path) + output_file_path = "/".join(filename.split("/")[:-1]) + output_file = output_file_path + "/" + img_folder_name + JSON_EXT image = QImage() - image.load(imagePath) - imageShape = [image.height(), image.width(), - 1 if image.isGrayscale() else 3] - writer = CreateMLWriter(imgFolderName, imgFileName, - imageShape, shapes, outputFile, localimgpath=imagePath) + image.load(image_path) + image_shape = [image.height(), image.width(), + 1 if image.isGrayscale() else 3] + writer = CreateMLWriter(img_folder_name, img_file_name, + image_shape, shapes, output_file, local_img_path=image_path) writer.verified = self.verified writer.write() - def savePascalVocFormat(self, filename, shapes, imagePath, imageData, - lineColor=None, fillColor=None, databaseSrc=None): - imgFolderPath = os.path.dirname(imagePath) - imgFolderName = os.path.split(imgFolderPath)[-1] - imgFileName = os.path.basename(imagePath) - #imgFileNameWithoutExt = os.path.splitext(imgFileName)[0] + def save_pascal_voc_format(self, filename, shapes, image_path, image_data, + line_color=None, fill_color=None, database_src=None): + img_folder_path = os.path.dirname(image_path) + img_folder_name = os.path.split(img_folder_path)[-1] + img_file_name = os.path.basename(image_path) + # imgFileNameWithoutExt = os.path.splitext(img_file_name)[0] # Read from file path because self.imageData might be empty if saving to # Pascal format - if isinstance(imageData, QImage): - image = imageData + if isinstance(image_data, QImage): + image = image_data else: image = QImage() - image.load(imagePath) - imageShape = [image.height(), image.width(), - 1 if image.isGrayscale() else 3] - writer = PascalVocWriter(imgFolderName, imgFileName, - imageShape, localImgPath=imagePath) + image.load(image_path) + image_shape = [image.height(), image.width(), + 1 if image.isGrayscale() else 3] + writer = PascalVocWriter(img_folder_name, img_file_name, + image_shape, local_img_path=image_path) writer.verified = self.verified for shape in shapes: @@ -79,29 +79,29 @@ def savePascalVocFormat(self, filename, shapes, imagePath, imageData, label = shape['label'] # Add Chris difficult = int(shape['difficult']) - bndbox = LabelFile.convertPoints2BndBox(points) - writer.addBndBox(bndbox[0], bndbox[1], bndbox[2], bndbox[3], label, difficult) + bnd_box = LabelFile.convert_points_to_bnd_box(points) + writer.add_bnd_box(bnd_box[0], bnd_box[1], bnd_box[2], bnd_box[3], label, difficult) - writer.save(targetFile=filename) + writer.save(target_file=filename) return - def saveYoloFormat(self, filename, shapes, imagePath, imageData, classList, - lineColor=None, fillColor=None, databaseSrc=None): - imgFolderPath = os.path.dirname(imagePath) - imgFolderName = os.path.split(imgFolderPath)[-1] - imgFileName = os.path.basename(imagePath) - #imgFileNameWithoutExt = os.path.splitext(imgFileName)[0] + def save_yolo_format(self, filename, shapes, image_path, image_data, class_list, + line_color=None, fill_color=None, database_src=None): + img_folder_path = os.path.dirname(image_path) + img_folder_name = os.path.split(img_folder_path)[-1] + img_file_name = os.path.basename(image_path) + # imgFileNameWithoutExt = os.path.splitext(img_file_name)[0] # Read from file path because self.imageData might be empty if saving to # Pascal format - if isinstance(imageData, QImage): - image = imageData + if isinstance(image_data, QImage): + image = image_data else: image = QImage() - image.load(imagePath) - imageShape = [image.height(), image.width(), - 1 if image.isGrayscale() else 3] - writer = YOLOWriter(imgFolderName, imgFileName, - imageShape, localImgPath=imagePath) + image.load(image_path) + image_shape = [image.height(), image.width(), + 1 if image.isGrayscale() else 3] + writer = YOLOWriter(img_folder_name, img_file_name, + image_shape, local_img_path=image_path) writer.verified = self.verified for shape in shapes: @@ -109,13 +109,13 @@ def saveYoloFormat(self, filename, shapes, imagePath, imageData, classList, label = shape['label'] # Add Chris difficult = int(shape['difficult']) - bndbox = LabelFile.convertPoints2BndBox(points) - writer.addBndBox(bndbox[0], bndbox[1], bndbox[2], bndbox[3], label, difficult) + bnd_box = LabelFile.convert_points_to_bnd_box(points) + writer.add_bnd_box(bnd_box[0], bnd_box[1], bnd_box[2], bnd_box[3], label, difficult) - writer.save(targetFile=filename, classList=classList) + writer.save(target_file=filename, class_list=class_list) return - def toggleVerify(self): + def toggle_verify(self): self.verified = not self.verified ''' ttf is disable @@ -148,31 +148,31 @@ def save(self, filename, shapes, imagePath, imageData, lineColor=None, fillColor ''' @staticmethod - def isLabelFile(filename): - fileSuffix = os.path.splitext(filename)[1].lower() - return fileSuffix == LabelFile.suffix + def is_label_file(filename): + file_suffix = os.path.splitext(filename)[1].lower() + return file_suffix == LabelFile.suffix @staticmethod - def convertPoints2BndBox(points): - xmin = float('inf') - ymin = float('inf') - xmax = float('-inf') - ymax = float('-inf') + def convert_points_to_bnd_box(points): + x_min = float('inf') + y_min = float('inf') + x_max = float('-inf') + y_max = float('-inf') for p in points: x = p[0] y = p[1] - xmin = min(x, xmin) - ymin = min(y, ymin) - xmax = max(x, xmax) - ymax = max(y, ymax) + x_min = min(x, x_min) + y_min = min(y, y_min) + x_max = max(x, x_max) + y_max = max(y, y_max) # Martin Kersner, 2015/11/12 # 0-valued coordinates of BB caused an error while # training faster-rcnn object detector. - if xmin < 1: - xmin = 1 + if x_min < 1: + x_min = 1 - if ymin < 1: - ymin = 1 + if y_min < 1: + y_min = 1 - return (int(xmin), int(ymin), int(xmax), int(ymax)) + return int(x_min), int(y_min), int(x_max), int(y_max) diff --git a/libs/pascal_voc_io.py b/libs/pascal_voc_io.py index 627e315b4..d8f7d690b 100644 --- a/libs/pascal_voc_io.py +++ b/libs/pascal_voc_io.py @@ -14,13 +14,13 @@ class PascalVocWriter: - def __init__(self, foldername, filename, imgSize,databaseSrc='Unknown', localImgPath=None): - self.foldername = foldername + def __init__(self, folder_name, filename, img_size, database_src='Unknown', local_img_path=None): + self.folder_name = folder_name self.filename = filename - self.databaseSrc = databaseSrc - self.imgSize = imgSize - self.boxlist = [] - self.localImgPath = localImgPath + self.database_src = database_src + self.img_size = img_size + self.box_list = [] + self.local_img_path = local_img_path self.verified = False def prettify(self, elem): @@ -31,17 +31,17 @@ def prettify(self, elem): root = etree.fromstring(rough_string) return etree.tostring(root, pretty_print=True, encoding=ENCODE_METHOD).replace(" ".encode(), "\t".encode()) # minidom does not support UTF-8 - '''reparsed = minidom.parseString(rough_string) - return reparsed.toprettyxml(indent="\t", encoding=ENCODE_METHOD)''' + # reparsed = minidom.parseString(rough_string) + # return reparsed.toprettyxml(indent="\t", encoding=ENCODE_METHOD) - def genXML(self): + def gen_xml(self): """ Return XML root """ # Check conditions if self.filename is None or \ - self.foldername is None or \ - self.imgSize is None: + self.folder_name is None or \ + self.img_size is None: return None top = Element('annotation') @@ -49,27 +49,27 @@ def genXML(self): top.set('verified', 'yes') folder = SubElement(top, 'folder') - folder.text = self.foldername + folder.text = self.folder_name filename = SubElement(top, 'filename') filename.text = self.filename - if self.localImgPath is not None: - localImgPath = SubElement(top, 'path') - localImgPath.text = self.localImgPath + if self.local_img_path is not None: + local_img_path = SubElement(top, 'path') + local_img_path.text = self.local_img_path source = SubElement(top, 'source') database = SubElement(source, 'database') - database.text = self.databaseSrc + database.text = self.database_src size_part = SubElement(top, 'size') width = SubElement(size_part, 'width') height = SubElement(size_part, 'height') depth = SubElement(size_part, 'depth') - width.text = str(self.imgSize[1]) - height.text = str(self.imgSize[0]) - if len(self.imgSize) == 3: - depth.text = str(self.imgSize[2]) + width.text = str(self.img_size[1]) + height.text = str(self.img_size[0]) + if len(self.img_size) == 3: + depth.text = str(self.img_size[2]) else: depth.text = '1' @@ -77,95 +77,95 @@ def genXML(self): segmented.text = '0' return top - def addBndBox(self, xmin, ymin, xmax, ymax, name, difficult): - bndbox = {'xmin': xmin, 'ymin': ymin, 'xmax': xmax, 'ymax': ymax} - bndbox['name'] = name - bndbox['difficult'] = difficult - self.boxlist.append(bndbox) + def add_bnd_box(self, x_min, y_min, x_max, y_max, name, difficult): + bnd_box = {'xmin': x_min, 'ymin': y_min, 'xmax': x_max, 'ymax': y_max} + bnd_box['name'] = name + bnd_box['difficult'] = difficult + self.box_list.append(bnd_box) - def appendObjects(self, top): - for each_object in self.boxlist: + def append_objects(self, top): + for each_object in self.box_list: object_item = SubElement(top, 'object') name = SubElement(object_item, 'name') name.text = ustr(each_object['name']) pose = SubElement(object_item, 'pose') pose.text = "Unspecified" truncated = SubElement(object_item, 'truncated') - if int(float(each_object['ymax'])) == int(float(self.imgSize[0])) or (int(float(each_object['ymin']))== 1): - truncated.text = "1" # max == height or min - elif (int(float(each_object['xmax']))==int(float(self.imgSize[1]))) or (int(float(each_object['xmin']))== 1): - truncated.text = "1" # max == width or min + if int(float(each_object['ymax'])) == int(float(self.img_size[0])) or (int(float(each_object['ymin'])) == 1): + truncated.text = "1" # max == height or min + elif (int(float(each_object['xmax'])) == int(float(self.img_size[1]))) or (int(float(each_object['xmin'])) == 1): + truncated.text = "1" # max == width or min else: truncated.text = "0" difficult = SubElement(object_item, 'difficult') - difficult.text = str( bool(each_object['difficult']) & 1 ) - bndbox = SubElement(object_item, 'bndbox') - xmin = SubElement(bndbox, 'xmin') - xmin.text = str(each_object['xmin']) - ymin = SubElement(bndbox, 'ymin') - ymin.text = str(each_object['ymin']) - xmax = SubElement(bndbox, 'xmax') - xmax.text = str(each_object['xmax']) - ymax = SubElement(bndbox, 'ymax') - ymax.text = str(each_object['ymax']) - - def save(self, targetFile=None): - root = self.genXML() - self.appendObjects(root) + difficult.text = str(bool(each_object['difficult']) & 1) + bnd_box = SubElement(object_item, 'bndbox') + x_min = SubElement(bnd_box, 'xmin') + x_min.text = str(each_object['xmin']) + y_min = SubElement(bnd_box, 'ymin') + y_min.text = str(each_object['ymin']) + x_max = SubElement(bnd_box, 'xmax') + x_max.text = str(each_object['xmax']) + y_max = SubElement(bnd_box, 'ymax') + y_max.text = str(each_object['ymax']) + + def save(self, target_file=None): + root = self.gen_xml() + self.append_objects(root) out_file = None - if targetFile is None: + if target_file is None: out_file = codecs.open( self.filename + XML_EXT, 'w', encoding=ENCODE_METHOD) else: - out_file = codecs.open(targetFile, 'w', encoding=ENCODE_METHOD) + out_file = codecs.open(target_file, 'w', encoding=ENCODE_METHOD) - prettifyResult = self.prettify(root) - out_file.write(prettifyResult.decode('utf8')) + prettify_result = self.prettify(root) + out_file.write(prettify_result.decode('utf8')) out_file.close() class PascalVocReader: - def __init__(self, filepath): + def __init__(self, file_path): # shapes type: # [labbel, [(x1,y1), (x2,y2), (x3,y3), (x4,y4)], color, color, difficult] self.shapes = [] - self.filepath = filepath + self.file_path = file_path self.verified = False try: - self.parseXML() + self.parse_xml() except: pass - def getShapes(self): + def get_shapes(self): return self.shapes - def addShape(self, label, bndbox, difficult): - xmin = int(float(bndbox.find('xmin').text)) - ymin = int(float(bndbox.find('ymin').text)) - xmax = int(float(bndbox.find('xmax').text)) - ymax = int(float(bndbox.find('ymax').text)) - points = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)] + def add_shape(self, label, bnd_box, difficult): + x_min = int(float(bnd_box.find('xmin').text)) + y_min = int(float(bnd_box.find('ymin').text)) + x_max = int(float(bnd_box.find('xmax').text)) + y_max = int(float(bnd_box.find('ymax').text)) + points = [(x_min, y_min), (x_max, y_min), (x_max, y_max), (x_min, y_max)] self.shapes.append((label, points, None, None, difficult)) - def parseXML(self): - assert self.filepath.endswith(XML_EXT), "Unsupport file format" + def parse_xml(self): + assert self.file_path.endswith(XML_EXT), "Unsupported file format" parser = etree.XMLParser(encoding=ENCODE_METHOD) - xmltree = ElementTree.parse(self.filepath, parser=parser).getroot() - filename = xmltree.find('filename').text + xml_tree = ElementTree.parse(self.file_path, parser=parser).getroot() + filename = xml_tree.find('filename').text try: - verified = xmltree.attrib['verified'] + verified = xml_tree.attrib['verified'] if verified == 'yes': self.verified = True except KeyError: self.verified = False - for object_iter in xmltree.findall('object'): - bndbox = object_iter.find("bndbox") + for object_iter in xml_tree.findall('object'): + bnd_box = object_iter.find("bndbox") label = object_iter.find('name').text # Add chris difficult = False if object_iter.find('difficult') is not None: difficult = bool(int(object_iter.find('difficult').text)) - self.addShape(label, bndbox, difficult) + self.add_shape(label, bnd_box, difficult) return True diff --git a/libs/shape.py b/libs/shape.py index 466e046dc..04cf1f96f 100644 --- a/libs/shape.py +++ b/libs/shape.py @@ -32,23 +32,23 @@ class Shape(object): select_line_color = DEFAULT_SELECT_LINE_COLOR select_fill_color = DEFAULT_SELECT_FILL_COLOR vertex_fill_color = DEFAULT_VERTEX_FILL_COLOR - hvertex_fill_color = DEFAULT_HVERTEX_FILL_COLOR + h_vertex_fill_color = DEFAULT_HVERTEX_FILL_COLOR point_type = P_ROUND point_size = 8 scale = 1.0 - labelFontSize = 8 + label_font_size = 8 - def __init__(self, label=None, line_color=None, difficult=False, paintLabel=False): + def __init__(self, label=None, line_color=None, difficult=False, paint_label=False): self.label = label self.points = [] self.fill = False self.selected = False self.difficult = difficult - self.paintLabel = paintLabel + self.paint_label = paint_label - self._highlightIndex = None - self._highlightMode = self.NEAR_VERTEX - self._highlightSettings = { + self._highlight_index = None + self._highlight_mode = self.NEAR_VERTEX + self._highlight_settings = { self.NEAR_VERTEX: (4, self.P_ROUND), self.MOVE_VERTEX: (1.5, self.P_SQUARE), } @@ -64,24 +64,24 @@ def __init__(self, label=None, line_color=None, difficult=False, paintLabel=Fals def close(self): self._closed = True - def reachMaxPoints(self): + def reach_max_points(self): if len(self.points) >= 4: return True return False - def addPoint(self, point): - if not self.reachMaxPoints(): + def add_point(self, point): + if not self.reach_max_points(): self.points.append(point) - def popPoint(self): + def pop_point(self): if self.points: return self.points.pop() return None - def isClosed(self): + def is_closed(self): return self._closed - def setOpen(self): + def set_open(self): self._closed = False def paint(self, painter): @@ -93,40 +93,40 @@ def paint(self, painter): painter.setPen(pen) line_path = QPainterPath() - vrtx_path = QPainterPath() + vertex_path = QPainterPath() line_path.moveTo(self.points[0]) # Uncommenting the following line will draw 2 paths # for the 1st vertex, and make it non-filled, which # may be desirable. - #self.drawVertex(vrtx_path, 0) + # self.drawVertex(vertex_path, 0) for i, p in enumerate(self.points): line_path.lineTo(p) - self.drawVertex(vrtx_path, i) - if self.isClosed(): + self.draw_vertex(vertex_path, i) + if self.is_closed(): line_path.lineTo(self.points[0]) painter.drawPath(line_path) - painter.drawPath(vrtx_path) - painter.fillPath(vrtx_path, self.vertex_fill_color) + painter.drawPath(vertex_path) + painter.fillPath(vertex_path, self.vertex_fill_color) # Draw text at the top-left - if self.paintLabel: + if self.paint_label: min_x = sys.maxsize min_y = sys.maxsize - min_y_label = int(1.25 * self.labelFontSize) + min_y_label = int(1.25 * self.label_font_size) for point in self.points: min_x = min(min_x, point.x()) min_y = min(min_y, point.y()) if min_x != sys.maxsize and min_y != sys.maxsize: font = QFont() - font.setPointSize(self.labelFontSize) + font.setPointSize(self.label_font_size) font.setBold(True) painter.setFont(font) - if(self.label == None): + if self.label is None: self.label = "" - if(min_y < min_y_label): + if min_y < min_y_label: min_y += min_y_label painter.drawText(min_x, min_y, self.label) @@ -134,15 +134,15 @@ def paint(self, painter): color = self.select_fill_color if self.selected else self.fill_color painter.fillPath(line_path, color) - def drawVertex(self, path, i): + def draw_vertex(self, path, i): d = self.point_size / self.scale shape = self.point_type point = self.points[i] - if i == self._highlightIndex: - size, shape = self._highlightSettings[self._highlightMode] + if i == self._highlight_index: + size, shape = self._highlight_settings[self._highlight_mode] d *= size - if self._highlightIndex is not None: - self.vertex_fill_color = self.hvertex_fill_color + if self._highlight_index is not None: + self.vertex_fill_color = self.h_vertex_fill_color else: self.vertex_fill_color = Shape.vertex_fill_color if shape == self.P_SQUARE: @@ -152,36 +152,36 @@ def drawVertex(self, path, i): else: assert False, "unsupported vertex shape" - def nearestVertex(self, point, epsilon): + def nearest_vertex(self, point, epsilon): for i, p in enumerate(self.points): if distance(p - point) <= epsilon: return i return None - def containsPoint(self, point): - return self.makePath().contains(point) + def contains_point(self, point): + return self.make_path().contains(point) - def makePath(self): + def make_path(self): path = QPainterPath(self.points[0]) for p in self.points[1:]: path.lineTo(p) return path - def boundingRect(self): - return self.makePath().boundingRect() + def bounding_rect(self): + return self.make_path().boundingRect() - def moveBy(self, offset): + def move_by(self, offset): self.points = [p + offset for p in self.points] - def moveVertexBy(self, i, offset): + def move_vertex_by(self, i, offset): self.points[i] = self.points[i] + offset - def highlightVertex(self, i, action): - self._highlightIndex = i - self._highlightMode = action + def highlight_vertex(self, i, action): + self._highlight_index = i + self._highlight_mode = action - def highlightClear(self): - self._highlightIndex = None + def highlight_clear(self): + self._highlight_index = None def copy(self): shape = Shape("%s" % self.label) diff --git a/libs/stringBundle.py b/libs/stringBundle.py index a84f169bc..dfd785908 100644 --- a/libs/stringBundle.py +++ b/libs/stringBundle.py @@ -19,43 +19,43 @@ class StringBundle: __create_key = object() - def __init__(self, create_key, localeStr): + def __init__(self, create_key, locale_str): assert(create_key == StringBundle.__create_key), "StringBundle must be created using StringBundle.getBundle" - self.idToMessage = {} - paths = self.__createLookupFallbackList(localeStr) + self.id_to_message = {} + paths = self.__create_lookup_fallback_list(locale_str) for path in paths: - self.__loadBundle(path) + self.__load_bundle(path) @classmethod - def getBundle(cls, localeStr=None): - if localeStr is None: + def get_bundle(cls, locale_str=None): + if locale_str is None: try: - localeStr = locale.getlocale()[0] if locale.getlocale() and len( + locale_str = locale.getlocale()[0] if locale.getlocale() and len( locale.getlocale()) > 0 else os.getenv('LANG') except: print('Invalid locale') - localeStr = 'en' + locale_str = 'en' - return StringBundle(cls.__create_key, localeStr) + return StringBundle(cls.__create_key, locale_str) - def getString(self, stringId): - assert(stringId in self.idToMessage), "Missing string id : " + stringId - return self.idToMessage[stringId] + def get_string(self, string_id): + assert(string_id in self.id_to_message), "Missing string id : " + string_id + return self.id_to_message[string_id] - def __createLookupFallbackList(self, localeStr): - resultPaths = [] - basePath = ":/strings" - resultPaths.append(basePath) - if localeStr is not None: + def __create_lookup_fallback_list(self, locale_str): + result_paths = [] + base_path = ":/strings" + result_paths.append(base_path) + if locale_str is not None: # Don't follow standard BCP47. Simple fallback - tags = re.split('[^a-zA-Z]', localeStr) + tags = re.split('[^a-zA-Z]', locale_str) for tag in tags: - lastPath = resultPaths[-1] - resultPaths.append(lastPath + '-' + tag) + last_path = result_paths[-1] + result_paths.append(last_path + '-' + tag) - return resultPaths + return result_paths - def __loadBundle(self, path): + def __load_bundle(self, path): PROP_SEPERATOR = '=' f = QFile(path) if f.exists(): @@ -68,6 +68,6 @@ def __loadBundle(self, path): key_value = line.split(PROP_SEPERATOR) key = key_value[0].strip() value = PROP_SEPERATOR.join(key_value[1:]).strip().strip('"') - self.idToMessage[key] = value + self.id_to_message[key] = value f.close() diff --git a/libs/ustr.py b/libs/ustr.py index 511e68b5e..bfcaff5d7 100644 --- a/libs/ustr.py +++ b/libs/ustr.py @@ -2,15 +2,15 @@ from libs.constants import DEFAULT_ENCODING def ustr(x): - '''py2/py3 unicode helper''' + """py2/py3 unicode helper""" if sys.version_info < (3, 0, 0): from PyQt4.QtCore import QString if type(x) == str: return x.decode(DEFAULT_ENCODING) if type(x) == QString: - #https://blog.csdn.net/friendan/article/details/51088476 - #https://blog.csdn.net/xxm524/article/details/74937308 + # https://blog.csdn.net/friendan/article/details/51088476 + # https://blog.csdn.net/xxm524/article/details/74937308 return unicode(x.toUtf8(), DEFAULT_ENCODING, 'ignore') return x else: diff --git a/libs/utils.py b/libs/utils.py index dacf682a1..d440a14d4 100644 --- a/libs/utils.py +++ b/libs/utils.py @@ -13,25 +13,25 @@ from PyQt4.QtCore import * -def newIcon(icon): +def new_icon(icon): return QIcon(':/' + icon) -def newButton(text, icon=None, slot=None): +def new_button(text, icon=None, slot=None): b = QPushButton(text) if icon is not None: - b.setIcon(newIcon(icon)) + b.setIcon(new_icon(icon)) if slot is not None: b.clicked.connect(slot) return b -def newAction(parent, text, slot=None, shortcut=None, icon=None, - tip=None, checkable=False, enabled=True): +def new_action(parent, text, slot=None, shortcut=None, icon=None, + tip=None, checkable=False, enabled=True): """Create a new action and assign callbacks, shortcuts, etc.""" a = QAction(text, parent) if icon is not None: - a.setIcon(newIcon(icon)) + a.setIcon(new_icon(icon)) if shortcut is not None: if isinstance(shortcut, (list, tuple)): a.setShortcuts(shortcut) @@ -48,7 +48,7 @@ def newAction(parent, text, slot=None, shortcut=None, icon=None, return a -def addActions(widget, actions): +def add_actions(widget, actions): for action in actions: if action is None: widget.addSeparator() @@ -58,11 +58,11 @@ def addActions(widget, actions): widget.addAction(action) -def labelValidator(): +def label_validator(): return QRegExpValidator(QRegExp(r'^[^ \t].+'), None) -class struct(object): +class Struct(object): def __init__(self, **kwargs): self.__dict__.update(kwargs) @@ -72,21 +72,21 @@ def distance(p): return sqrt(p.x() * p.x() + p.y() * p.y()) -def fmtShortcut(text): +def format_shortcut(text): mod, key = text.split('+', 1) return '%s+%s' % (mod, key) -def generateColorByText(text): +def generate_color_by_text(text): s = ustr(text) - hashCode = int(hashlib.sha256(s.encode('utf-8')).hexdigest(), 16) - r = int((hashCode / 255) % 255) - g = int((hashCode / 65025) % 255) - b = int((hashCode / 16581375) % 255) + hash_code = int(hashlib.sha256(s.encode('utf-8')).hexdigest(), 16) + r = int((hash_code / 255) % 255) + g = int((hash_code / 65025) % 255) + b = int((hash_code / 16581375) % 255) return QColor(r, g, b, 100) def have_qstring(): - '''p3/qt5 get rid of QString wrapper as py3 has native unicode str type''' + """p3/qt5 get rid of QString wrapper as py3 has native unicode str type""" return not (sys.version_info.major >= 3 or QT_VERSION_STR.startswith('5.')) def util_qt_strlistclass(): diff --git a/libs/yolo_io.py b/libs/yolo_io.py index 216fba388..039acdb6d 100644 --- a/libs/yolo_io.py +++ b/libs/yolo_io.py @@ -13,67 +13,67 @@ class YOLOWriter: - def __init__(self, foldername, filename, imgSize, databaseSrc='Unknown', localImgPath=None): - self.foldername = foldername + def __init__(self, folder_name, filename, img_size, database_src='Unknown', local_img_path=None): + self.folder_name = folder_name self.filename = filename - self.databaseSrc = databaseSrc - self.imgSize = imgSize - self.boxlist = [] - self.localImgPath = localImgPath + self.database_src = database_src + self.img_size = img_size + self.box_list = [] + self.local_img_path = local_img_path self.verified = False - def addBndBox(self, xmin, ymin, xmax, ymax, name, difficult): - bndbox = {'xmin': xmin, 'ymin': ymin, 'xmax': xmax, 'ymax': ymax} - bndbox['name'] = name - bndbox['difficult'] = difficult - self.boxlist.append(bndbox) + def add_bnd_box(self, x_min, y_min, x_max, y_max, name, difficult): + bnd_box = {'xmin': x_min, 'ymin': y_min, 'xmax': x_max, 'ymax': y_max} + bnd_box['name'] = name + bnd_box['difficult'] = difficult + self.box_list.append(bnd_box) - def BndBox2YoloLine(self, box, classList=[]): - xmin = box['xmin'] - xmax = box['xmax'] - ymin = box['ymin'] - ymax = box['ymax'] + def bnd_box_to_yolo_line(self, box, class_list=[]): + x_min = box['xmin'] + x_max = box['xmax'] + y_min = box['ymin'] + y_max = box['ymax'] - xcen = float((xmin + xmax)) / 2 / self.imgSize[1] - ycen = float((ymin + ymax)) / 2 / self.imgSize[0] + x_center = float((x_min + x_max)) / 2 / self.img_size[1] + y_center = float((y_min + y_max)) / 2 / self.img_size[0] - w = float((xmax - xmin)) / self.imgSize[1] - h = float((ymax - ymin)) / self.imgSize[0] + w = float((x_max - x_min)) / self.img_size[1] + h = float((y_max - y_min)) / self.img_size[0] # PR387 - boxName = box['name'] - if boxName not in classList: - classList.append(boxName) + box_name = box['name'] + if box_name not in class_list: + class_list.append(box_name) - classIndex = classList.index(boxName) + class_index = class_list.index(box_name) - return classIndex, xcen, ycen, w, h + return class_index, x_center, y_center, w, h - def save(self, classList=[], targetFile=None): + def save(self, class_list=[], target_file=None): - out_file = None #Update yolo .txt - out_class_file = None #Update class list .txt + out_file = None # Update yolo .txt + out_class_file = None # Update class list .txt - if targetFile is None: + if target_file is None: out_file = open( self.filename + TXT_EXT, 'w', encoding=ENCODE_METHOD) - classesFile = os.path.join(os.path.dirname(os.path.abspath(self.filename)), "classes.txt") - out_class_file = open(classesFile, 'w') + classes_file = os.path.join(os.path.dirname(os.path.abspath(self.filename)), "classes.txt") + out_class_file = open(classes_file, 'w') else: - out_file = codecs.open(targetFile, 'w', encoding=ENCODE_METHOD) - classesFile = os.path.join(os.path.dirname(os.path.abspath(targetFile)), "classes.txt") - out_class_file = open(classesFile, 'w') + out_file = codecs.open(target_file, 'w', encoding=ENCODE_METHOD) + classes_file = os.path.join(os.path.dirname(os.path.abspath(target_file)), "classes.txt") + out_class_file = open(classes_file, 'w') - for box in self.boxlist: - classIndex, xcen, ycen, w, h = self.BndBox2YoloLine(box, classList) - # print (classIndex, xcen, ycen, w, h) - out_file.write("%d %.6f %.6f %.6f %.6f\n" % (classIndex, xcen, ycen, w, h)) + for box in self.box_list: + class_index, x_center, y_center, w, h = self.bnd_box_to_yolo_line(box, class_list) + # print (classIndex, x_center, y_center, w, h) + out_file.write("%d %.6f %.6f %.6f %.6f\n" % (class_index, x_center, y_center, w, h)) # print (classList) # print (out_class_file) - for c in classList: + for c in class_list: out_class_file.write(c+'\n') out_class_file.close() @@ -83,64 +83,64 @@ def save(self, classList=[], targetFile=None): class YoloReader: - def __init__(self, filepath, image, classListPath=None): + def __init__(self, file_path, image, class_list_path=None): # shapes type: # [labbel, [(x1,y1), (x2,y2), (x3,y3), (x4,y4)], color, color, difficult] self.shapes = [] - self.filepath = filepath + self.file_path = file_path - if classListPath is None: - dir_path = os.path.dirname(os.path.realpath(self.filepath)) - self.classListPath = os.path.join(dir_path, "classes.txt") + if class_list_path is None: + dir_path = os.path.dirname(os.path.realpath(self.file_path)) + self.class_list_path = os.path.join(dir_path, "classes.txt") else: - self.classListPath = classListPath + self.class_list_path = class_list_path - # print (filepath, self.classListPath) + # print (file_path, self.class_list_path) - classesFile = open(self.classListPath, 'r') - self.classes = classesFile.read().strip('\n').split('\n') + classes_file = open(self.class_list_path, 'r') + self.classes = classes_file.read().strip('\n').split('\n') # print (self.classes) - imgSize = [image.height(), image.width(), - 1 if image.isGrayscale() else 3] + img_size = [image.height(), image.width(), + 1 if image.isGrayscale() else 3] - self.imgSize = imgSize + self.img_size = img_size self.verified = False # try: - self.parseYoloFormat() + self.parse_yolo_format() # except: - # pass + # pass - def getShapes(self): + def get_shapes(self): return self.shapes - def addShape(self, label, xmin, ymin, xmax, ymax, difficult): + def add_shape(self, label, x_min, y_min, x_max, y_max, difficult): - points = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)] + points = [(x_min, y_min), (x_max, y_min), (x_max, y_max), (x_min, y_max)] self.shapes.append((label, points, None, None, difficult)) - def yoloLine2Shape(self, classIndex, xcen, ycen, w, h): - label = self.classes[int(classIndex)] + def yolo_line_to_shape(self, class_index, x_center, y_center, w, h): + label = self.classes[int(class_index)] - xmin = max(float(xcen) - float(w) / 2, 0) - xmax = min(float(xcen) + float(w) / 2, 1) - ymin = max(float(ycen) - float(h) / 2, 0) - ymax = min(float(ycen) + float(h) / 2, 1) + x_min = max(float(x_center) - float(w) / 2, 0) + x_max = min(float(x_center) + float(w) / 2, 1) + y_min = max(float(y_center) - float(h) / 2, 0) + y_max = min(float(y_center) + float(h) / 2, 1) - xmin = int(self.imgSize[1] * xmin) - xmax = int(self.imgSize[1] * xmax) - ymin = int(self.imgSize[0] * ymin) - ymax = int(self.imgSize[0] * ymax) + x_min = int(self.img_size[1] * x_min) + x_max = int(self.img_size[1] * x_max) + y_min = int(self.img_size[0] * y_min) + y_max = int(self.img_size[0] * y_max) - return label, xmin, ymin, xmax, ymax + return label, x_min, y_min, x_max, y_max - def parseYoloFormat(self): - bndBoxFile = open(self.filepath, 'r') - for bndBox in bndBoxFile: - classIndex, xcen, ycen, w, h = bndBox.strip().split(' ') - label, xmin, ymin, xmax, ymax = self.yoloLine2Shape(classIndex, xcen, ycen, w, h) + def parse_yolo_format(self): + bnd_box_file = open(self.file_path, 'r') + for bndBox in bnd_box_file: + class_index, x_center, y_center, w, h = bndBox.strip().split(' ') + label, x_min, y_min, x_max, y_max = self.yolo_line_to_shape(class_index, x_center, y_center, w, h) # Caveat: difficult flag is discarded when saved as yolo format. - self.addShape(label, xmin, ymin, xmax, ymax, False) + self.add_shape(label, x_min, y_min, x_max, y_max, False) diff --git a/tests/test_io.py b/tests/test_io.py index 7a6d022e1..ce733a492 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -14,17 +14,17 @@ def test_upper(self): # Test Write/Read writer = PascalVocWriter('tests', 'test', (512, 512, 1), localImgPath='tests/test.512.512.bmp') difficult = 1 - writer.addBndBox(60, 40, 430, 504, 'person', difficult) - writer.addBndBox(113, 40, 450, 403, 'face', difficult) + writer.add_bnd_box(60, 40, 430, 504, 'person', difficult) + writer.add_bnd_box(113, 40, 450, 403, 'face', difficult) writer.save('tests/test.xml') reader = PascalVocReader('tests/test.xml') - shapes = reader.getShapes() + shapes = reader.get_shapes() - personBndBox = shapes[0] + person_bnd_box = shapes[0] face = shapes[1] - self.assertEqual(personBndBox[0], 'person') - self.assertEqual(personBndBox[1], [(60, 40), (430, 40), (430, 504), (60, 504)]) + self.assertEqual(person_bnd_box[0], 'person') + self.assertEqual(person_bnd_box[1], [(60, 40), (430, 40), (430, 504), (60, 504)]) self.assertEqual(face[0], 'face') self.assertEqual(face[1], [(113, 40), (450, 40), (450, 403), (113, 403)]) @@ -54,15 +54,15 @@ def test_a_write(self): # check written json with open(output_file, "r") as file: - inputdata = file.read() + input_data = file.read() import json - data_dict = json.loads(inputdata)[0] + data_dict = json.loads(input_data)[0] self.assertEqual('test.512.512.bmp', data_dict['image'], 'filename not correct in .json') - self.assertEqual(2, len(data_dict['annotations']), 'outputfile contains to less annotations') + self.assertEqual(2, len(data_dict['annotations']), 'output file contains to less annotations') face = data_dict['annotations'][1] - self.assertEqual('face', face['label'], 'labelname is wrong') + self.assertEqual('face', face['label'], 'label name is wrong') face_coords = face['coordinates'] self.assertEqual(expected_width, face_coords['width'], 'calculated width is wrong') self.assertEqual(expected_height, face_coords['height'], 'calculated height is wrong') @@ -84,15 +84,15 @@ def test_b_read(self): self.assertEqual('face', face[0], 'label is wrong') face_coords = face[1] - xmin = face_coords[0][0] - xmax = face_coords[1][0] - ymin = face_coords[0][1] - ymax = face_coords[2][1] - - self.assertEqual(245, xmin, 'xmin is wrong') - self.assertEqual(350, xmax, 'xmax is wrong') - self.assertEqual(250, ymin, 'ymin is wrong') - self.assertEqual(365, ymax, 'ymax is wrong') + x_min = face_coords[0][0] + x_max = face_coords[1][0] + y_min = face_coords[0][1] + y_max = face_coords[2][1] + + self.assertEqual(245, x_min, 'xmin is wrong') + self.assertEqual(350, x_max, 'xmax is wrong') + self.assertEqual(250, y_min, 'ymin is wrong') + self.assertEqual(365, y_max, 'ymax is wrong') if __name__ == '__main__': diff --git a/tests/test_stringBundle.py b/tests/test_stringBundle.py index d4eb7f262..0dea1044a 100644 --- a/tests/test_stringBundle.py +++ b/tests/test_stringBundle.py @@ -7,20 +7,20 @@ class TestStringBundle(unittest.TestCase): def test_loadDefaultBundle_withoutError(self): - strBundle = StringBundle.getBundle('en') - self.assertEqual(strBundle.getString("openDir"), 'Open Dir', 'Fail to load the default bundle') + str_bundle = StringBundle.get_bundle('en') + self.assertEqual(str_bundle.get_string("openDir"), 'Open Dir', 'Fail to load the default bundle') def test_fallback_withoutError(self): - strBundle = StringBundle.getBundle('zh-TW') - self.assertEqual(strBundle.getString("openDir"), u'\u958B\u555F\u76EE\u9304', 'Fail to load the zh-TW bundle') + str_bundle = StringBundle.get_bundle('zh-TW') + self.assertEqual(str_bundle.get_string("openDir"), u'\u958B\u555F\u76EE\u9304', 'Fail to load the zh-TW bundle') def test_setInvaleLocaleToEnv_printErrorMsg(self): prev_lc = os.environ['LC_ALL'] prev_lang = os.environ['LANG'] os.environ['LC_ALL'] = 'UTF-8' os.environ['LANG'] = 'UTF-8' - strBundle = StringBundle.getBundle() - self.assertEqual(strBundle.getString("openDir"), 'Open Dir', 'Fail to load the default bundle') + str_bundle = StringBundle.get_bundle() + self.assertEqual(str_bundle.get_string("openDir"), 'Open Dir', 'Fail to load the default bundle') os.environ['LC_ALL'] = prev_lc os.environ['LANG'] = prev_lang diff --git a/tests/test_utils.py b/tests/test_utils.py index 1b0a6c45e..a1971efcd 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,22 +1,22 @@ import os import sys import unittest -from libs.utils import struct, newAction, newIcon, addActions, fmtShortcut, generateColorByText, natural_sort +from libs.utils import Struct, new_action, new_icon, add_actions, format_shortcut, generate_color_by_text, natural_sort class TestUtils(unittest.TestCase): def test_generateColorByGivingUniceText_noError(self): - res = generateColorByText(u'\u958B\u555F\u76EE\u9304') + res = generate_color_by_text(u'\u958B\u555F\u76EE\u9304') self.assertTrue(res.green() >= 0) self.assertTrue(res.red() >= 0) self.assertTrue(res.blue() >= 0) def test_nautalSort_noError(self): - l1 = ['f1', 'f11', 'f3' ] - exptected_l1 = ['f1', 'f3', 'f11'] + l1 = ['f1', 'f11', 'f3'] + expected_l1 = ['f1', 'f3', 'f11'] natural_sort(l1) for idx, val in enumerate(l1): - self.assertTrue(val == exptected_l1[idx]) + self.assertTrue(val == expected_l1[idx]) if __name__ == '__main__': unittest.main()