From b08cc74536c5da5049ea7ce6cade53c11480cde8 Mon Sep 17 00:00:00 2001 From: Kentaro Wada Date: Sun, 22 Apr 2018 22:12:29 +0900 Subject: [PATCH] Load ~/.labelmerc for user defined shortcuts --- labelme/app.py | 141 +++++++++++++++++++---------- labelme/config/__init__.py | 9 ++ labelme/config/default_config.yaml | 25 +++++ 3 files changed, 129 insertions(+), 46 deletions(-) create mode 100644 labelme/config/__init__.py create mode 100644 labelme/config/default_config.yaml diff --git a/labelme/app.py b/labelme/app.py index 557425a54..bb6ffb9f7 100644 --- a/labelme/app.py +++ b/labelme/app.py @@ -3,17 +3,20 @@ import os.path import subprocess import sys +import warnings from qtpy import QT_VERSION from qtpy import QtCore from qtpy.QtCore import Qt from qtpy import QtGui from qtpy import QtWidgets +import yaml QT5 = QT_VERSION[0] == '5' from labelme.canvas import Canvas from labelme.colorDialog import ColorDialog +from labelme.config import default_config from labelme.labelDialog import LabelDialog from labelme.labelFile import LabelFile from labelme.labelFile import LabelFileError @@ -219,44 +222,55 @@ def __init__(self, filename=None, output=None, store_data=True, QtWidgets.QDockWidget.DockWidgetFloatable) self.dock.setFeatures(self.dock.features() ^ self.dockFeatures) + config = self.getConfig() + # Actions action = functools.partial(newAction, self) - quit = action('&Quit', self.close, 'Ctrl+Q', 'quit', + shortcuts = config['shortcuts'] + quit = action('&Quit', self.close, shortcuts['quit'], 'quit', 'Quit application') - open_ = action('&Open', self.openFile, 'Ctrl+O', 'open', + open_ = action('&Open', self.openFile, shortcuts['open'], 'open', 'Open image or label file') - opendir = action('&Open Dir', self.openDirDialog, 'Ctrl+U', 'open', - u'Open Dir') - openNextImg = action('&Next Image', self.openNextImg, 'D', 'next', - u'Open Next') - - openPrevImg = action('&Prev Image', self.openPrevImg, 'A', 'prev', - u'Open Prev') - save = action('&Save', self.saveFile, 'Ctrl+S', 'save', + opendir = action('&Open Dir', self.openDirDialog, + shortcuts['open_dir'], 'open', u'Open Dir') + openNextImg = action('&Next Image', self.openNextImg, + shortcuts['open_next'], 'next', u'Open Next') + + openPrevImg = action('&Prev Image', self.openPrevImg, + shortcuts['open_prev'], 'prev', u'Open Prev') + save = action('&Save', self.saveFile, shortcuts['save'], 'save', 'Save labels to file', enabled=False) - saveAs = action('&Save As', self.saveFileAs, 'Ctrl+Shift+S', 'save-as', - 'Save labels to a different file', enabled=False) - close = action('&Close', self.closeFile, 'Ctrl+W', 'close', + saveAs = action('&Save As', self.saveFileAs, shortcuts['save_as'], + 'save-as', 'Save labels to a different file', + enabled=False) + close = action('&Close', self.closeFile, shortcuts['close'], 'close', 'Close current file') - color1 = action('Polygon &Line Color', self.chooseColor1, 'Ctrl+L', - 'color_line', 'Choose polygon line color') + color1 = action('Polygon &Line Color', self.chooseColor1, + shortcuts['color_line'], 'color_line', + 'Choose polygon line color') color2 = action('Polygon &Fill Color', self.chooseColor2, - 'Ctrl+Shift+L', 'color', 'Choose polygon fill color') - - createMode = action('Create\nPolygo&ns', self.setCreateMode, 'Ctrl+N', - 'objects', 'Start drawing polygons', enabled=False) - editMode = action('&Edit\nPolygons', self.setEditMode, 'Ctrl+J', - 'edit', 'Move and edit polygons', enabled=False) - - create = action('Create\nPolygo&n', self.createShape, 'Ctrl+N', - 'objects', 'Draw a new polygon', enabled=False) - delete = action('Delete\nPolygon', self.deleteSelectedShape, 'Delete', - 'cancel', 'Delete', enabled=False) - copy = action('&Duplicate\nPolygon', self.copySelectedShape, 'Ctrl+D', - 'copy', 'Create a duplicate of the selected polygon', + shortcuts['color'], 'color', + 'Choose polygon fill color') + + createMode = action('Create\nPolygo&ns', self.setCreateMode, + shortcuts['create_polygon'], 'objects', + 'Start drawing polygons', enabled=False) + editMode = action('&Edit\nPolygons', self.setEditMode, + shortcuts['edit_polygon'], 'edit', + 'Move and edit polygons', enabled=False) + + create = action('Create\nPolygo&n', self.createShape, + shortcuts['create_polygon'], 'objects', + 'Draw a new polygon', enabled=False) + delete = action('Delete\nPolygon', self.deleteSelectedShape, + shortcuts['delete_polygon'], 'cancel', + 'Delete', enabled=False) + copy = action('&Duplicate\nPolygon', self.copySelectedShape, + shortcuts['duplicate_polygon'], 'copy', + 'Create a duplicate of the selected polygon', enabled=False) undoLastPoint = action('Undo last point', self.canvas.undoLastPoint, - ['Ctrl+Z', 'Backspace'], 'undoLastPoint', + shortcuts['undo_last_point'], 'undoLastPoint', 'Undo last drawn point', enabled=False) advancedMode = action('&Advanced Mode', self.toggleAdvancedMode, @@ -265,37 +279,41 @@ def __init__(self, filename=None, output=None, store_data=True, hideAll = action('&Hide\nPolygons', functools.partial(self.togglePolygons, False), - 'Ctrl+H', 'eye', 'Hide all polygons', enabled=False) + icon='eye', tip='Hide all polygons', enabled=False) showAll = action('&Show\nPolygons', functools.partial(self.togglePolygons, True), - 'Ctrl+A', 'eye', 'Show all polygons', enabled=False) + icon='eye', tip='Show all polygons', enabled=False) - help = action('&Tutorial', self.tutorial, 'Ctrl+T', 'help', - 'Show screencast of introductory tutorial') + help = action('&Tutorial', self.tutorial, icon='help', + tip='Show screencast of introductory tutorial') zoom = QtWidgets.QWidgetAction(self) zoom.setDefaultWidget(self.zoomWidget) self.zoomWidget.setWhatsThis( "Zoom in or out of the image. Also accessible with" " %s and %s from the canvas." % - (fmtShortcut("Ctrl+[-+]"), fmtShortcut("Ctrl+Wheel"))) + (fmtShortcut('%s,%s' % (shortcuts['zoom_in'], + shortcuts['zoom_out'])), + fmtShortcut("Ctrl+Wheel"))) self.zoomWidget.setEnabled(False) zoomIn = action('Zoom &In', functools.partial(self.addZoom, 10), - 'Ctrl++', 'zoom-in', + shortcuts['zoom_in'], 'zoom-in', 'Increase zoom level', enabled=False) zoomOut = action('&Zoom Out', functools.partial(self.addZoom, -10), - 'Ctrl+-', 'zoom-out', + shortcuts['zoom_out'], 'zoom-out', 'Decrease zoom level', enabled=False) zoomOrg = action('&Original size', functools.partial(self.setZoom, 100), - 'Ctrl+0', 'zoom', 'Zoom to original size', - enabled=False) - fitWindow = action('&Fit Window', self.setFitWindow, 'Ctrl+F', - 'fit-window', 'Zoom follows window size', - checkable=True, enabled=False) - fitWidth = action('Fit &Width', self.setFitWidth, 'Ctrl+Shift+F', - 'fit-width', 'Zoom follows window width', + shortcuts['zoom_to_original'], 'zoom', + 'Zoom to original size', enabled=False) + fitWindow = action('&Fit Window', self.setFitWindow, + shortcuts['fit_window'], 'fit-window', + 'Zoom follows window size', checkable=True, + enabled=False) + fitWidth = action('Fit &Width', self.setFitWidth, + shortcuts['fit_width'], 'fit-width', + 'Zoom follows window width', checkable=True, enabled=False) # Group zoom controls into a list for easier toggling. zoomActions = (self.zoomWidget, zoomIn, zoomOut, zoomOrg, @@ -308,8 +326,8 @@ def __init__(self, filename=None, output=None, store_data=True, self.MANUAL_ZOOM: lambda: 1, } - edit = action('&Edit Label', self.editLabel, 'Ctrl+E', 'edit', - 'Modify the label of the selected polygon', + edit = action('&Edit Label', self.editLabel, shortcuts['edit_label'], + 'edit', 'Modify the label of the selected polygon', enabled=False) self.editButton.setDefaultAction(edit) @@ -322,7 +340,6 @@ def __init__(self, filename=None, output=None, store_data=True, labels = self.dock.toggleViewAction() labels.setText('Show/Hide Label Panel') - labels.setShortcut('Ctrl+Shift+L') # Lavel list context menu. labelMenu = QtWidgets.QMenu() @@ -457,6 +474,38 @@ def __init__(self, filename=None, output=None, store_data=True, # Support Functions + def getConfig(self): + # shortcuts for actions + home = os.path.expanduser('~') + config_file = os.path.join(home, '.labelmerc') + + # default config + config = default_config.copy() + + def update_dict(target_dict, new_dict): + for key, value in new_dict.items(): + if key not in target_dict: + print('Skipping unexpected key in config: {}'.format(key)) + continue + if isinstance(target_dict[key], dict) and \ + isinstance(value, dict): + update_dict(target_dict[key], value) + else: + target_dict[key] = value + + if os.path.exists(config_file): + user_config = yaml.load(open(config_file)) or {} + update_dict(config, user_config) + + # save config + try: + yaml.safe_dump(config, open(config_file, 'w'), + default_flow_style=False) + except Exception: + warnings.warn('Failed to save config: {}'.format(config_file)) + + return config + def noShapes(self): return not self.labelList.itemsToShapes diff --git a/labelme/config/__init__.py b/labelme/config/__init__.py new file mode 100644 index 000000000..ab94be188 --- /dev/null +++ b/labelme/config/__init__.py @@ -0,0 +1,9 @@ +import os.path as osp + +import yaml + + +here = osp.dirname(osp.abspath(__file__)) +config_file = osp.join(here, 'default_config.yaml') +default_config = yaml.load(open(config_file)) +del here, config_file diff --git a/labelme/config/default_config.yaml b/labelme/config/default_config.yaml new file mode 100644 index 000000000..86ccf445e --- /dev/null +++ b/labelme/config/default_config.yaml @@ -0,0 +1,25 @@ +shortcuts: + close: Ctrl+W + open: Ctrl+O + open_dir: Ctrl+U + quit: Ctrl+Q + save: Ctrl+S + save_as: Ctrl+Shift+S + + open_next: D + open_prev: A + + zoom_in: Ctrl++ + zoom_out: Ctrl+- + zoom_to_original: Ctrl+0 + fit_window: Ctrl+F + fit_width: Ctrl+Shift+F + + color_line: Ctrl+L + color: Ctrl+Shift+L + create_polygon: Ctrl+N + edit_polygon: Ctrl+J + delete_polygon: Delete + duplicate_polygon: Ctrl+D + undo_last_point: [Ctrl+Z, Backspace] + edit_label: Ctrl+E