Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable user configure GUI with args and a yaml file #144

Merged
merged 1 commit into from
Apr 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 42 additions & 40 deletions labelme/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,23 +121,27 @@ def shapes(self):
class MainWindow(QtWidgets.QMainWindow, WindowMixin):
FIT_WINDOW, FIT_WIDTH, MANUAL_ZOOM = 0, 1, 2

def __init__(self, filename=None, output=None, store_data=True,
labels=None, sort_labels=True, auto_save=False,
validate_label=None, config=None):
super(MainWindow, self).__init__()
self.setWindowTitle(__appname__)

def __init__(self, config=None, filename=None, output=None):
# see labelme/config/default_config.yaml for valid configuration
if config is None:
config = get_config()
self._config = config

super(MainWindow, self).__init__()
self.setWindowTitle(__appname__)

# Whether we need to save or not.
self.dirty = False

self._noSelectionSlot = False

# Main widgets and related state.
self.labelDialog = LabelDialog(parent=self, labels=labels,
sort_labels=sort_labels)
self.labelDialog = LabelDialog(
parent=self,
labels=self._config['labels'],
sort_labels=self._config['sort_labels'],
show_text_field=self._config['show_label_text_field'],
)

self.labelList = LabelQListWidget()
self.lastOpenDir = None
Expand All @@ -164,8 +168,8 @@ def __init__(self, filename=None, output=None, store_data=True,
self.uniqLabelList.setToolTip(
"Select label to start annotating for it. "
"Press 'Esc' to deselect.")
if labels:
self.uniqLabelList.addItems(labels)
if self._config['labels']:
self.uniqLabelList.addItems(self._config['labels'])
self.uniqLabelList.sortItems()
self.labelsdock = QtWidgets.QDockWidget(u'Label List', self)
self.labelsdock.setObjectName(u'Label List')
Expand Down Expand Up @@ -220,7 +224,7 @@ def __init__(self, filename=None, output=None, store_data=True,

# Actions
action = functools.partial(newAction, self)
shortcuts = config['shortcuts']
shortcuts = self._config['shortcuts']
quit = action('&Quit', self.close, shortcuts['quit'], 'quit',
'Quit application')
open_ = action('&Open', self.openFile, shortcuts['open'], 'open',
Expand Down Expand Up @@ -400,18 +404,12 @@ def __init__(self, filename=None, output=None, store_data=True,
# Application state.
self.image = QtGui.QImage()
self.imagePath = None
if auto_save and output is not None:
if self._config['auto_save'] and output is not None:
warnings.warn('If `auto_save` argument is True, `output` argument '
'is ignored and output filename is automatically '
'set as IMAGE_BASENAME.json.')
self.labeling_once = output is not None
self.output = output
self._auto_save = auto_save
self._store_data = store_data
if validate_label not in [None, 'exact', 'instance']:
raise ValueError('Unexpected `validate_label`: {}'
.format(validate_label))
self._validate_label = validate_label
self.recentFiles = []
self.maxRecent = 7
self.lineColor = None
Expand Down Expand Up @@ -477,7 +475,7 @@ def populateModeActions(self):
addActions(self.menus.edit, actions + self.actions.editMenu)

def setDirty(self):
if self._auto_save:
if self._config['auto_save']:
label_file = os.path.splitext(self.imagePath)[0] + '.json'
self.saveLabels(label_file)
return
Expand Down Expand Up @@ -587,15 +585,15 @@ def popLabelListMenu(self, point):

def validateLabel(self, label):
# no validation
if self._validate_label is None:
if self._config['validate_label'] is None:
return True

for i in range(self.uniqLabelList.count()):
l = self.uniqLabelList.item(i).text()
if self._validate_label in ['exact', 'instance']:
if self._config['validate_label'] in ['exact', 'instance']:
if l == label:
return True
if self._validate_label == 'instance':
if self._config['validate_label'] == 'instance':
m = re.match(r'^{}-[0-9]*$'.format(l), label)
if m:
return True
Expand All @@ -611,7 +609,7 @@ def editLabel(self, item=None):
if not self.validateLabel(text):
self.errorMessage('Invalid label',
"Invalid label '{}' with validation type '{}'"
.format(text, self._validate_label))
.format(text, self._config['validate_label']))
return
item.setText(text)
self.setDirty()
Expand Down Expand Up @@ -702,7 +700,7 @@ def format_shape(s):
try:
imagePath = os.path.relpath(
self.imagePath, os.path.dirname(filename))
imageData = self.imageData if self._store_data else None
imageData = self.imageData if self._config['store_data'] else None
lf.save(filename, shapes, imagePath, imageData,
self.lineColor.getRgb(), self.fillColor.getRgb(),
self.otherData)
Expand Down Expand Up @@ -750,7 +748,7 @@ def newShape(self):
if text is not None and not self.validateLabel(text):
self.errorMessage('Invalid label',
"Invalid label '{}' with validation type '{}'"
.format(text, self._validate_label))
.format(text, self._config['validate_label']))
text = None
if text is None:
self.canvas.undoLastLine()
Expand Down Expand Up @@ -1200,21 +1198,24 @@ def read(filename, default=None):


def main():
"""Standard boilerplate Qt application code."""
parser = argparse.ArgumentParser()
parser.add_argument('--version', '-V', action='store_true',
help='show version')
parser.add_argument('filename', nargs='?', help='image or label filename')
parser.add_argument('--output', '-O', '-o', help='output label name')
parser.add_argument('--config', dest='config_file', help='config file')
# config
parser.add_argument('--nodata', dest='store_data', action='store_false',
help='stop storing image data to JSON file')
parser.add_argument('--autosave', action='store_true', help='auto save')
parser.add_argument('--autosave', dest='auto_save', action='store_true',
help='auto save')
parser.add_argument('--labels',
help='comma separated list of labels OR file '
'containing one label per line')
parser.add_argument('--nosortlabels', dest='sort_labels',
action='store_false', help='stop sorting labels')
parser.add_argument('--validatelabel', choices=['exact', 'instance'],
parser.add_argument('--validatelabel', dest='validate_label',
choices=['exact', 'instance'],
help='label validation types')
args = parser.parse_args()

Expand All @@ -1223,8 +1224,10 @@ def main():
sys.exit(0)

if args.labels is None:
if args.validatelabel is not None:
logger.error('--labels must be specified with --validatelabel')
if args.validate_label is not None:
logger.error('--labels must be specified with --validatelabel or '
'validate_label: true in the config file '
'(ex. ~/.labelmerc).')
sys.exit(1)
else:
if os.path.isfile(args.labels):
Expand All @@ -1233,18 +1236,17 @@ def main():
else:
args.labels = [l for l in args.labels.split(',') if l]

config_from_args = args.__dict__
config_from_args.pop('version')
filename = config_from_args.pop('filename')
output = config_from_args.pop('output')
config_file = config_from_args.pop('config_file')
config = get_config(config_from_args, config_file)

app = QtWidgets.QApplication(sys.argv)
app.setApplicationName(__appname__)
app.setWindowIcon(newIcon("icon"))
win = MainWindow(
filename=args.filename,
output=args.output,
store_data=args.store_data,
labels=args.labels,
sort_labels=args.sort_labels,
auto_save=args.autosave,
validate_label=args.validatelabel,
)
app.setWindowIcon(newIcon('icon'))
win = MainWindow(config=config, filename=filename, output=output)
win.show()
win.raise_()
sys.exit(app.exec_())
Expand Down
45 changes: 31 additions & 14 deletions labelme/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,59 @@
here = osp.dirname(osp.abspath(__file__))


def update_dict(target_dict, new_dict):
def update_dict(target_dict, new_dict, validate_item=None):
for key, value in new_dict.items():
if validate_item:
validate_item(key, value)
if key not in target_dict:
logger.warn('Skipping unexpected key in config: {}'
.format(key))
continue
if isinstance(target_dict[key], dict) and \
isinstance(value, dict):
update_dict(target_dict[key], value)
update_dict(target_dict[key], value, validate_item=validate_item)
else:
target_dict[key] = value


# -----------------------------------------------------------------------------


def get_default_config():
config_file = osp.join(here, 'default_config.yaml')
config = yaml.load(open(config_file))
return config


def get_config():
def validate_config_item(key, value):
if key == 'validate_label' and value not in [None, 'exact', 'instance']:
raise ValueError('Unexpected value `{}` for key `{}`'
.format(value, key))


def get_config(config_from_args=None, config_file=None):
# default config
config = get_default_config()

# shortcuts for actions
home = os.path.expanduser('~')
config_file = os.path.join(home, '.labelmerc')
if config_from_args is not None:
update_dict(config, config_from_args,
validate_item=validate_config_item)

save_config_file = False
if config_file is None:
home = os.path.expanduser('~')
config_file = os.path.join(home, '.labelmerc')
save_config_file = True

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:
logger.warn('Failed to save config: {}'.format(config_file))
update_dict(config, user_config, validate_item=validate_config_item)

if save_config_file:
try:
yaml.safe_dump(config, open(config_file, 'w'),
default_flow_style=False)
except Exception:
logger.warn('Failed to save config: {}'.format(config_file))

return config
8 changes: 8 additions & 0 deletions labelme/config/default_config.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
auto_save: false
store_data: true

labels: null
sort_labels: true
validate_label: null
show_label_text_field: true

shortcuts:
close: Ctrl+W
open: Ctrl+O
Expand Down
5 changes: 3 additions & 2 deletions labelme/labelDialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ def keyPressEvent(self, e):
class LabelDialog(QtWidgets.QDialog):

def __init__(self, text="Enter object label", parent=None, labels=None,
sort_labels=True):
sort_labels=True, show_text_field=True):
super(LabelDialog, self).__init__(parent)
self.edit = LabelQLineEdit()
self.edit.setPlaceholderText(text)
self.edit.setValidator(labelValidator())
self.edit.editingFinished.connect(self.postProcess)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.edit)
if show_text_field:
layout.addWidget(self.edit)
# buttons
self.buttonBox = bb = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel,
Expand Down