提交 5c980844 编写于 作者: K Kentaro Wada

Format code with black

上级 19433fca
[flake8]
exclude = .anaconda3/*
ignore = W504
ignore = E203, E741, W503, W504
......@@ -56,7 +56,7 @@ jobs:
env:
PYTHON_VERSION: ${{ matrix.PYTHON_VERSION }}
run: |
conda install -y python=$PYTHON_VERSION
conda install -q -y python=$PYTHON_VERSION
which python
python --version
pip --version
......@@ -66,17 +66,17 @@ jobs:
run: |
if [ "${{ matrix.PYTEST_QT_API }}" = "pyside2" ]; then
if [ "${{ matrix.PYTHON_VERSION }}" = "2.7" ]; then
conda install -y 'pyside2!=5.12.4' -c conda-forge
conda install -q -y 'pyside2!=5.12.4' -c conda-forge
else
conda install -y pyside2 -c conda-forge
conda install -q -y pyside2 -c conda-forge
fi
elif [ "${{ matrix.PYTEST_QT_API }}" = "pyqt4v2" ]; then
conda install -y pyqt=4 -c conda-forge
conda install -q -y pyqt=4 -c conda-forge
else # pyqt5
conda install -y pyqt=5
conda install -q -y pyqt=5
fi
if [ "${{ matrix.os }}" != "windows-latest" ]; then
conda install -y help2man
conda install -q -y help2man
fi
pip install hacking pytest pytest-qt
......@@ -91,6 +91,13 @@ jobs:
run: |
flake8 .
- name: Black
shell: bash -l {0}
if: matrix.os != 'windows-latest' && matrix.python-version != '2.7'
run: |
pip install black
black --check .
- name: Test with pytest
shell: bash -l {0}
if: matrix.os != 'windows-latest'
......
......@@ -10,11 +10,12 @@ import sys
import imgviz
import labelme
try:
import lxml.builder
import lxml.etree
except ImportError:
print('Please install lxml:\n\n pip install lxml\n')
print("Please install lxml:\n\n pip install lxml\n")
sys.exit(1)
......@@ -22,23 +23,23 @@ def main():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument('input_dir', help='input annotated directory')
parser.add_argument('output_dir', help='output dataset directory')
parser.add_argument('--labels', help='labels file', required=True)
parser.add_argument("input_dir", help="input annotated directory")
parser.add_argument("output_dir", help="output dataset directory")
parser.add_argument("--labels", help="labels file", required=True)
parser.add_argument(
'--noviz', help='no visualization', action='store_true'
"--noviz", help="no visualization", action="store_true"
)
args = parser.parse_args()
if osp.exists(args.output_dir):
print('Output directory already exists:', args.output_dir)
print("Output directory already exists:", args.output_dir)
sys.exit(1)
os.makedirs(args.output_dir)
os.makedirs(osp.join(args.output_dir, 'JPEGImages'))
os.makedirs(osp.join(args.output_dir, 'Annotations'))
os.makedirs(osp.join(args.output_dir, "JPEGImages"))
os.makedirs(osp.join(args.output_dir, "Annotations"))
if not args.noviz:
os.makedirs(osp.join(args.output_dir, 'AnnotationsVisualization'))
print('Creating dataset:', args.output_dir)
os.makedirs(osp.join(args.output_dir, "AnnotationsVisualization"))
print("Creating dataset:", args.output_dir)
class_names = []
class_name_to_id = {}
......@@ -47,31 +48,30 @@ def main():
class_name = line.strip()
class_name_to_id[class_name] = class_id
if class_id == -1:
assert class_name == '__ignore__'
assert class_name == "__ignore__"
continue
elif class_id == 0:
assert class_name == '_background_'
assert class_name == "_background_"
class_names.append(class_name)
class_names = tuple(class_names)
print('class_names:', class_names)
out_class_names_file = osp.join(args.output_dir, 'class_names.txt')
with open(out_class_names_file, 'w') as f:
f.writelines('\n'.join(class_names))
print('Saved class_names:', out_class_names_file)
print("class_names:", class_names)
out_class_names_file = osp.join(args.output_dir, "class_names.txt")
with open(out_class_names_file, "w") as f:
f.writelines("\n".join(class_names))
print("Saved class_names:", out_class_names_file)
for filename in glob.glob(osp.join(args.input_dir, '*.json')):
print('Generating dataset from:', filename)
for filename in glob.glob(osp.join(args.input_dir, "*.json")):
print("Generating dataset from:", filename)
label_file = labelme.LabelFile(filename=filename)
base = osp.splitext(osp.basename(filename))[0]
out_img_file = osp.join(
args.output_dir, 'JPEGImages', base + '.jpg')
out_xml_file = osp.join(
args.output_dir, 'Annotations', base + '.xml')
out_img_file = osp.join(args.output_dir, "JPEGImages", base + ".jpg")
out_xml_file = osp.join(args.output_dir, "Annotations", base + ".xml")
if not args.noviz:
out_viz_file = osp.join(
args.output_dir, 'AnnotationsVisualization', base + '.jpg')
args.output_dir, "AnnotationsVisualization", base + ".jpg"
)
img = labelme.utils.img_data_to_arr(label_file.imageData)
imgviz.io.imsave(out_img_file, img)
......@@ -79,10 +79,10 @@ def main():
maker = lxml.builder.ElementMaker()
xml = maker.annotation(
maker.folder(),
maker.filename(base + '.jpg'),
maker.database(), # e.g., The VOC2007 Database
maker.filename(base + ".jpg"),
maker.database(), # e.g., The VOC2007 Database
maker.annotation(), # e.g., Pascal VOC2007
maker.image(), # e.g., flickr
maker.image(), # e.g., flickr
maker.size(
maker.height(str(img.shape[0])),
maker.width(str(img.shape[1])),
......@@ -94,15 +94,17 @@ def main():
bboxes = []
labels = []
for shape in label_file.shapes:
if shape['shape_type'] != 'rectangle':
print('Skipping shape: label={label}, shape_type={shape_type}'
.format(**shape))
if shape["shape_type"] != "rectangle":
print(
"Skipping shape: label={label}, "
"shape_type={shape_type}".format(**shape)
)
continue
class_name = shape['label']
class_name = shape["label"]
class_id = class_names.index(class_name)
(xmin, ymin), (xmax, ymax) = shape['points']
(xmin, ymin), (xmax, ymax) = shape["points"]
# swap if min is larger than max.
xmin, xmax = sorted([xmin, xmax])
ymin, ymax = sorted([ymin, ymax])
......@@ -112,7 +114,7 @@ def main():
xml.append(
maker.object(
maker.name(shape['label']),
maker.name(shape["label"]),
maker.pose(),
maker.truncated(),
maker.difficult(),
......@@ -136,9 +138,9 @@ def main():
)
imgviz.io.imsave(out_viz_file, viz)
with open(out_xml_file, 'wb') as f:
with open(out_xml_file, "wb") as f:
f.write(lxml.etree.tostring(xml, pretty_print=True))
if __name__ == '__main__':
if __name__ == "__main__":
main()
......@@ -18,7 +18,7 @@ import labelme
try:
import pycocotools.mask
except ImportError:
print('Please install pycocotools:\n\n pip install pycocotools\n')
print("Please install pycocotools:\n\n pip install pycocotools\n")
sys.exit(1)
......@@ -26,17 +26,17 @@ def main():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument('input_dir', help='input annotated directory')
parser.add_argument('output_dir', help='output dataset directory')
parser.add_argument('--labels', help='labels file', required=True)
parser.add_argument("input_dir", help="input annotated directory")
parser.add_argument("output_dir", help="output dataset directory")
parser.add_argument("--labels", help="labels file", required=True)
args = parser.parse_args()
if osp.exists(args.output_dir):
print('Output directory already exists:', args.output_dir)
print("Output directory already exists:", args.output_dir)
sys.exit(1)
os.makedirs(args.output_dir)
os.makedirs(osp.join(args.output_dir, 'JPEGImages'))
print('Creating dataset:', args.output_dir)
os.makedirs(osp.join(args.output_dir, "JPEGImages"))
print("Creating dataset:", args.output_dir)
now = datetime.datetime.now()
......@@ -47,17 +47,13 @@ def main():
version=None,
year=now.year,
contributor=None,
date_created=now.strftime('%Y-%m-%d %H:%M:%S.%f'),
date_created=now.strftime("%Y-%m-%d %H:%M:%S.%f"),
),
licenses=[dict(
url=None,
id=0,
name=None,
)],
licenses=[dict(url=None, id=0, name=None,)],
images=[
# license, url, file_name, height, width, date_captured, id
],
type='instances',
type="instances",
annotations=[
# segmentation, area, iscrowd, image_id, bbox, category_id, id
],
......@@ -71,46 +67,44 @@ def main():
class_id = i - 1 # starts with -1
class_name = line.strip()
if class_id == -1:
assert class_name == '__ignore__'
assert class_name == "__ignore__"
continue
class_name_to_id[class_name] = class_id
data['categories'].append(dict(
supercategory=None,
id=class_id,
name=class_name,
))
out_ann_file = osp.join(args.output_dir, 'annotations.json')
label_files = glob.glob(osp.join(args.input_dir, '*.json'))
data["categories"].append(
dict(supercategory=None, id=class_id, name=class_name,)
)
out_ann_file = osp.join(args.output_dir, "annotations.json")
label_files = glob.glob(osp.join(args.input_dir, "*.json"))
for image_id, filename in enumerate(label_files):
print('Generating dataset from:', filename)
print("Generating dataset from:", filename)
label_file = labelme.LabelFile(filename=filename)
base = osp.splitext(osp.basename(filename))[0]
out_img_file = osp.join(
args.output_dir, 'JPEGImages', base + '.jpg'
)
out_img_file = osp.join(args.output_dir, "JPEGImages", base + ".jpg")
img = labelme.utils.img_data_to_arr(label_file.imageData)
PIL.Image.fromarray(img).convert("RGB").save(out_img_file)
data['images'].append(dict(
license=0,
url=None,
file_name=osp.relpath(out_img_file, osp.dirname(out_ann_file)),
height=img.shape[0],
width=img.shape[1],
date_captured=None,
id=image_id,
))
masks = {} # for area
data["images"].append(
dict(
license=0,
url=None,
file_name=osp.relpath(out_img_file, osp.dirname(out_ann_file)),
height=img.shape[0],
width=img.shape[1],
date_captured=None,
id=image_id,
)
)
masks = {} # for area
segmentations = collections.defaultdict(list) # for segmentation
for shape in label_file.shapes:
points = shape['points']
label = shape['label']
group_id = shape.get('group_id')
shape_type = shape.get('shape_type', 'polygon')
points = shape["points"]
label = shape["label"]
group_id = shape.get("group_id")
shape_type = shape.get("shape_type", "polygon")
mask = labelme.utils.shape_to_mask(
img.shape[:2], points, shape_type
)
......@@ -125,7 +119,7 @@ def main():
else:
masks[instance] = mask
if shape_type == 'rectangle':
if shape_type == "rectangle":
(x1, y1), (x2, y2) = points
x1, x2 = sorted([x1, x2])
y1, y2 = sorted([y1, y2])
......@@ -147,19 +141,21 @@ def main():
area = float(pycocotools.mask.area(mask))
bbox = pycocotools.mask.toBbox(mask).flatten().tolist()
data['annotations'].append(dict(
id=len(data['annotations']),
image_id=image_id,
category_id=cls_id,
segmentation=segmentations[instance],
area=area,
bbox=bbox,
iscrowd=0,
))
with open(out_ann_file, 'w') as f:
data["annotations"].append(
dict(
id=len(data["annotations"]),
image_id=image_id,
category_id=cls_id,
segmentation=segmentations[instance],
area=area,
bbox=bbox,
iscrowd=0,
)
)
with open(out_ann_file, "w") as f:
json.dump(data, f)
if __name__ == '__main__':
if __name__ == "__main__":
main()
......@@ -18,32 +18,32 @@ def main():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument('input_dir', help='input annotated directory')
parser.add_argument('output_dir', help='output dataset directory')
parser.add_argument('--labels', help='labels file', required=True)
parser.add_argument("input_dir", help="input annotated directory")
parser.add_argument("output_dir", help="output dataset directory")
parser.add_argument("--labels", help="labels file", required=True)
parser.add_argument(
'--noviz', help='no visualization', action='store_true'
"--noviz", help="no visualization", action="store_true"
)
args = parser.parse_args()
if osp.exists(args.output_dir):
print('Output directory already exists:', args.output_dir)
print("Output directory already exists:", args.output_dir)
sys.exit(1)
os.makedirs(args.output_dir)
os.makedirs(osp.join(args.output_dir, 'JPEGImages'))
os.makedirs(osp.join(args.output_dir, 'SegmentationClass'))
os.makedirs(osp.join(args.output_dir, 'SegmentationClassPNG'))
os.makedirs(osp.join(args.output_dir, "JPEGImages"))
os.makedirs(osp.join(args.output_dir, "SegmentationClass"))
os.makedirs(osp.join(args.output_dir, "SegmentationClassPNG"))
if not args.noviz:
os.makedirs(
osp.join(args.output_dir, 'SegmentationClassVisualization')
osp.join(args.output_dir, "SegmentationClassVisualization")
)
os.makedirs(osp.join(args.output_dir, 'SegmentationObject'))
os.makedirs(osp.join(args.output_dir, 'SegmentationObjectPNG'))
os.makedirs(osp.join(args.output_dir, "SegmentationObject"))
os.makedirs(osp.join(args.output_dir, "SegmentationObjectPNG"))
if not args.noviz:
os.makedirs(
osp.join(args.output_dir, 'SegmentationObjectVisualization')
osp.join(args.output_dir, "SegmentationObjectVisualization")
)
print('Creating dataset:', args.output_dir)
print("Creating dataset:", args.output_dir)
class_names = []
class_name_to_id = {}
......@@ -52,45 +52,48 @@ def main():
class_name = line.strip()
class_name_to_id[class_name] = class_id
if class_id == -1:
assert class_name == '__ignore__'
assert class_name == "__ignore__"
continue
elif class_id == 0:
assert class_name == '_background_'
assert class_name == "_background_"
class_names.append(class_name)
class_names = tuple(class_names)
print('class_names:', class_names)
out_class_names_file = osp.join(args.output_dir, 'class_names.txt')
with open(out_class_names_file, 'w') as f:
f.writelines('\n'.join(class_names))
print('Saved class_names:', out_class_names_file)
print("class_names:", class_names)
out_class_names_file = osp.join(args.output_dir, "class_names.txt")
with open(out_class_names_file, "w") as f:
f.writelines("\n".join(class_names))
print("Saved class_names:", out_class_names_file)
for filename in glob.glob(osp.join(args.input_dir, '*.json')):
print('Generating dataset from:', filename)
for filename in glob.glob(osp.join(args.input_dir, "*.json")):
print("Generating dataset from:", filename)
label_file = labelme.LabelFile(filename=filename)
base = osp.splitext(osp.basename(filename))[0]
out_img_file = osp.join(
args.output_dir, 'JPEGImages', base + '.jpg')
out_img_file = osp.join(args.output_dir, "JPEGImages", base + ".jpg")
out_cls_file = osp.join(
args.output_dir, 'SegmentationClass', base + '.npy')
args.output_dir, "SegmentationClass", base + ".npy"
)
out_clsp_file = osp.join(
args.output_dir, 'SegmentationClassPNG', base + '.png')
args.output_dir, "SegmentationClassPNG", base + ".png"
)
if not args.noviz:
out_clsv_file = osp.join(
args.output_dir,
'SegmentationClassVisualization',
base + '.jpg',
"SegmentationClassVisualization",
base + ".jpg",
)
out_ins_file = osp.join(
args.output_dir, 'SegmentationObject', base + '.npy')
args.output_dir, "SegmentationObject", base + ".npy"
)
out_insp_file = osp.join(
args.output_dir, 'SegmentationObjectPNG', base + '.png')
args.output_dir, "SegmentationObjectPNG", base + ".png"
)
if not args.noviz:
out_insv_file = osp.join(
args.output_dir,
'SegmentationObjectVisualization',
base + '.jpg',
"SegmentationObjectVisualization",
base + ".jpg",
)
img = labelme.utils.img_data_to_arr(label_file.imageData)
......@@ -112,7 +115,7 @@ def main():
img=imgviz.rgb2gray(img),
label_names=class_names,
font_size=15,
loc='rb',
loc="rb",
)
imgviz.io.imsave(out_clsv_file, clsv)
......@@ -127,10 +130,10 @@ def main():
img=imgviz.rgb2gray(img),
label_names=instance_names,
font_size=15,
loc='rb',
loc="rb",
)
imgviz.io.imsave(out_insv_file, insv)
if __name__ == '__main__':
if __name__ == "__main__":
main()
......@@ -18,26 +18,26 @@ def main():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument('input_dir', help='input annotated directory')
parser.add_argument('output_dir', help='output dataset directory')
parser.add_argument('--labels', help='labels file', required=True)
parser.add_argument("input_dir", help="input annotated directory")
parser.add_argument("output_dir", help="output dataset directory")
parser.add_argument("--labels", help="labels file", required=True)
parser.add_argument(
'--noviz', help='no visualization', action='store_true'
"--noviz", help="no visualization", action="store_true"
)
args = parser.parse_args()
if osp.exists(args.output_dir):
print('Output directory already exists:', args.output_dir)
print("Output directory already exists:", args.output_dir)
sys.exit(1)
os.makedirs(args.output_dir)
os.makedirs(osp.join(args.output_dir, 'JPEGImages'))
os.makedirs(osp.join(args.output_dir, 'SegmentationClass'))
os.makedirs(osp.join(args.output_dir, 'SegmentationClassPNG'))
os.makedirs(osp.join(args.output_dir, "JPEGImages"))
os.makedirs(osp.join(args.output_dir, "SegmentationClass"))
os.makedirs(osp.join(args.output_dir, "SegmentationClassPNG"))
if not args.noviz:
os.makedirs(
osp.join(args.output_dir, 'SegmentationClassVisualization')
osp.join(args.output_dir, "SegmentationClassVisualization")
)
print('Creating dataset:', args.output_dir)
print("Creating dataset:", args.output_dir)
class_names = []
class_name_to_id = {}
......@@ -46,38 +46,39 @@ def main():
class_name = line.strip()
class_name_to_id[class_name] = class_id
if class_id == -1:
assert class_name == '__ignore__'
assert class_name == "__ignore__"
continue
elif class_id == 0:
assert class_name == '_background_'
assert class_name == "_background_"
class_names.append(class_name)
class_names = tuple(class_names)
print('class_names:', class_names)
out_class_names_file = osp.join(args.output_dir, 'class_names.txt')
with open(out_class_names_file, 'w') as f:
f.writelines('\n'.join(class_names))
print('Saved class_names:', out_class_names_file)
print("class_names:", class_names)
out_class_names_file = osp.join(args.output_dir, "class_names.txt")
with open(out_class_names_file, "w") as f:
f.writelines("\n".join(class_names))
print("Saved class_names:", out_class_names_file)
for filename in glob.glob(osp.join(args.input_dir, '*.json')):
print('Generating dataset from:', filename)
for filename in glob.glob(osp.join(args.input_dir, "*.json")):
print("Generating dataset from:", filename)
label_file = labelme.LabelFile(filename=filename)
base = osp.splitext(osp.basename(filename))[0]
out_img_file = osp.join(
args.output_dir, 'JPEGImages', base + '.jpg')
out_img_file = osp.join(args.output_dir, "JPEGImages", base + ".jpg")
out_lbl_file = osp.join(
args.output_dir, 'SegmentationClass', base + '.npy')
args.output_dir, "SegmentationClass", base + ".npy"
)
out_png_file = osp.join(
args.output_dir, 'SegmentationClassPNG', base + '.png')
args.output_dir, "SegmentationClassPNG", base + ".png"
)
if not args.noviz:
out_viz_file = osp.join(
args.output_dir,
'SegmentationClassVisualization',
base + '.jpg',
"SegmentationClassVisualization",
base + ".jpg",
)
with open(out_img_file, 'wb') as f:
with open(out_img_file, "wb") as f:
f.write(label_file.imageData)
img = labelme.utils.img_data_to_arr(label_file.imageData)
......@@ -96,10 +97,10 @@ def main():
img=imgviz.rgb2gray(img),
font_size=15,
label_names=class_names,
loc='rb',
loc="rb",
)
imgviz.io.imsave(out_viz_file, viz)
if __name__ == '__main__':
if __name__ == "__main__":
main()
......@@ -12,26 +12,26 @@ here = osp.dirname(osp.abspath(__file__))
def main():
label_png = osp.join(here, 'apc2016_obj3_json/label.png')
print('Loading:', label_png)
label_png = osp.join(here, "apc2016_obj3_json/label.png")
print("Loading:", label_png)
print()
lbl = np.asarray(PIL.Image.open(label_png))
labels = np.unique(lbl)
label_names_txt = osp.join(here, 'apc2016_obj3_json/label_names.txt')
label_names_txt = osp.join(here, "apc2016_obj3_json/label_names.txt")
label_names = [name.strip() for name in open(label_names_txt)]
print('# of labels:', len(labels))
print('# of label_names:', len(label_names))
print("# of labels:", len(labels))
print("# of label_names:", len(label_names))
if len(labels) != len(label_names):
print('Number of unique labels and label_names must be same.')
print("Number of unique labels and label_names must be same.")
quit(1)
print()
print('label: label_name')
print("label: label_name")
for label, label_name in zip(labels, label_names):
print('%d: %s' % (label, label_name))
print("%d: %s" % (label, label_name))
if __name__ == '__main__':
if __name__ == "__main__":
main()
......@@ -6,20 +6,20 @@ import sys
from qtpy import QT_VERSION
__appname__ = 'labelme'
__appname__ = "labelme"
# Semantic Versioning 2.0.0: https://semver.org/
# 1. MAJOR version when you make incompatible API changes;
# 2. MINOR version when you add functionality in a backwards-compatible manner;
# 3. PATCH version when you make backwards-compatible bug fixes.
__version__ = '4.4.0'
__version__ = "4.4.0"
QT4 = QT_VERSION[0] == '4'
QT5 = QT_VERSION[0] == '5'
QT4 = QT_VERSION[0] == "4"
QT5 = QT_VERSION[0] == "5"
del QT_VERSION
PY2 = sys.version[0] == '2'
PY3 = sys.version[0] == '3'
PY2 = sys.version[0] == "2"
PY3 = sys.version[0] == "3"
del sys
from labelme.label_file import LabelFile
......
......@@ -20,140 +20,142 @@ from labelme.utils import newIcon
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
'--version', '-V', action='store_true', help='show version'
"--version", "-V", action="store_true", help="show version"
)
parser.add_argument(
'--reset-config', action='store_true', help='reset qt config'
"--reset-config", action="store_true", help="reset qt config"
)
parser.add_argument(
'--logger-level',
default='info',
choices=['debug', 'info', 'warning', 'fatal', 'error'],
help='logger level',
"--logger-level",
default="info",
choices=["debug", "info", "warning", "fatal", "error"],
help="logger level",
)
parser.add_argument('filename', nargs='?', help='image or label filename')
parser.add_argument("filename", nargs="?", help="image or label filename")
parser.add_argument(
'--output',
'-O',
'-o',
help='output file or directory (if it ends with .json it is '
'recognized as file, else as directory)'
"--output",
"-O",
"-o",
help="output file or directory (if it ends with .json it is "
"recognized as file, else as directory)",
)
default_config_file = os.path.join(os.path.expanduser('~'), '.labelmerc')
default_config_file = os.path.join(os.path.expanduser("~"), ".labelmerc")
parser.add_argument(
'--config',
dest='config',
help='config file or yaml-format string (default: {})'.format(
"--config",
dest="config",
help="config file or yaml-format string (default: {})".format(
default_config_file
),
default=default_config_file,
)
# config for the gui
parser.add_argument(
'--nodata',
dest='store_data',
action='store_false',
help='stop storing image data to JSON file',
"--nodata",
dest="store_data",
action="store_false",
help="stop storing image data to JSON file",
default=argparse.SUPPRESS,
)
parser.add_argument(
'--autosave',
dest='auto_save',
action='store_true',
help='auto save',
"--autosave",
dest="auto_save",
action="store_true",
help="auto save",
default=argparse.SUPPRESS,
)
parser.add_argument(
'--nosortlabels',
dest='sort_labels',
action='store_false',
help='stop sorting labels',
"--nosortlabels",
dest="sort_labels",
action="store_false",
help="stop sorting labels",
default=argparse.SUPPRESS,
)
parser.add_argument(
'--flags',
help='comma separated list of flags OR file containing flags',
"--flags",
help="comma separated list of flags OR file containing flags",
default=argparse.SUPPRESS,
)
parser.add_argument(
'--labelflags',
dest='label_flags',
help=r'yaml string of label specific flags OR file containing json '
r'string of label specific flags (ex. {person-\d+: [male, tall], '
r'dog-\d+: [black, brown, white], .*: [occluded]})', # NOQA
"--labelflags",
dest="label_flags",
help=r"yaml string of label specific flags OR file containing json "
r"string of label specific flags (ex. {person-\d+: [male, tall], "
r"dog-\d+: [black, brown, white], .*: [occluded]})", # NOQA
default=argparse.SUPPRESS,
)
parser.add_argument(
'--labels',
help='comma separated list of labels OR file containing labels',
"--labels",
help="comma separated list of labels OR file containing labels",
default=argparse.SUPPRESS,
)
parser.add_argument(
'--validatelabel',
dest='validate_label',
choices=['exact'],
help='label validation types',
"--validatelabel",
dest="validate_label",
choices=["exact"],
help="label validation types",
default=argparse.SUPPRESS,
)
parser.add_argument(
'--keep-prev',
action='store_true',
help='keep annotation of previous frame',
"--keep-prev",
action="store_true",
help="keep annotation of previous frame",
default=argparse.SUPPRESS,
)
parser.add_argument(
'--epsilon',
"--epsilon",
type=float,
help='epsilon to find nearest vertex on canvas',
help="epsilon to find nearest vertex on canvas",
default=argparse.SUPPRESS,
)
args = parser.parse_args()
if args.version:
print('{0} {1}'.format(__appname__, __version__))
print("{0} {1}".format(__appname__, __version__))
sys.exit(0)
logger.setLevel(getattr(logging, args.logger_level.upper()))
if hasattr(args, 'flags'):
if hasattr(args, "flags"):
if os.path.isfile(args.flags):
with codecs.open(args.flags, 'r', encoding='utf-8') as f:
with codecs.open(args.flags, "r", encoding="utf-8") as f:
args.flags = [line.strip() for line in f if line.strip()]
else:
args.flags = [line for line in args.flags.split(',') if line]
args.flags = [line for line in args.flags.split(",") if line]
if hasattr(args, 'labels'):
if hasattr(args, "labels"):
if os.path.isfile(args.labels):
with codecs.open(args.labels, 'r', encoding='utf-8') as f:
with codecs.open(args.labels, "r", encoding="utf-8") as f:
args.labels = [line.strip() for line in f if line.strip()]
else:
args.labels = [line for line in args.labels.split(',') if line]
args.labels = [line for line in args.labels.split(",") if line]
if hasattr(args, 'label_flags'):
if hasattr(args, "label_flags"):
if os.path.isfile(args.label_flags):
with codecs.open(args.label_flags, 'r', encoding='utf-8') as f:
with codecs.open(args.label_flags, "r", encoding="utf-8") as f:
args.label_flags = yaml.safe_load(f)
else:
args.label_flags = yaml.safe_load(args.label_flags)
config_from_args = args.__dict__
config_from_args.pop('version')
reset_config = config_from_args.pop('reset_config')
filename = config_from_args.pop('filename')
output = config_from_args.pop('output')
config_file_or_yaml = config_from_args.pop('config')
config_from_args.pop("version")
reset_config = config_from_args.pop("reset_config")
filename = config_from_args.pop("filename")
output = config_from_args.pop("output")
config_file_or_yaml = config_from_args.pop("config")
config = get_config(config_file_or_yaml, config_from_args)
if not config['labels'] and config['validate_label']:
logger.error('--labels must be specified with --validatelabel or '
'validate_label: true in the config file '
'(ex. ~/.labelmerc).')
if not config["labels"] and config["validate_label"]:
logger.error(
"--labels must be specified with --validatelabel or "
"validate_label: true in the config file "
"(ex. ~/.labelmerc)."
)
sys.exit(1)
output_file = None
output_dir = None
if output is not None:
if output.endswith('.json'):
if output.endswith(".json"):
output_file = output
else:
output_dir = output
......@@ -161,11 +163,11 @@ def main():
translator = QtCore.QTranslator()
translator.load(
QtCore.QLocale.system().name(),
osp.dirname(osp.abspath(__file__)) + '/translate'
osp.dirname(osp.abspath(__file__)) + "/translate",
)
app = QtWidgets.QApplication(sys.argv)
app.setApplicationName(__appname__)
app.setWindowIcon(newIcon('icon'))
app.setWindowIcon(newIcon("icon"))
app.installTranslator(translator)
win = MainWindow(
config=config,
......@@ -175,7 +177,7 @@ def main():
)
if reset_config:
logger.info('Resetting Qt config: %s' % win.settings.fileName())
logger.info("Resetting Qt config: %s" % win.settings.fileName())
win.settings.clear()
sys.exit(0)
......@@ -185,5 +187,5 @@ def main():
# this main block is required to generate executable by pyinstaller
if __name__ == '__main__':
if __name__ == "__main__":
main()
此差异已折叠。
......@@ -15,15 +15,15 @@ PY2 = sys.version_info[0] == 2
def main():
parser = argparse.ArgumentParser()
parser.add_argument('json_file')
parser.add_argument("json_file")
args = parser.parse_args()
label_file = LabelFile(args.json_file)
img = utils.img_data_to_arr(label_file.imageData)
label_name_to_value = {'_background_': 0}
for shape in sorted(label_file.shapes, key=lambda x: x['label']):
label_name = shape['label']
label_name_to_value = {"_background_": 0}
for shape in sorted(label_file.shapes, key=lambda x: x["label"]):
label_name = shape["label"]
if label_name in label_name_to_value:
label_value = label_name_to_value[label_name]
else:
......@@ -41,7 +41,7 @@ def main():
img=imgviz.asgray(img),
label_names=label_names,
font_size=30,
loc='rb',
loc="rb",
)
plt.subplot(121)
......@@ -51,5 +51,5 @@ def main():
plt.show()
if __name__ == '__main__':
if __name__ == "__main__":
main()
......@@ -10,19 +10,20 @@ from labelme.logger import logger
def main():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('label_png', help='label PNG file')
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument("label_png", help="label PNG file")
args = parser.parse_args()
lbl = np.asarray(PIL.Image.open(args.label_png))
logger.info('label shape: {}'.format(lbl.shape))
logger.info('unique label values: {}'.format(np.unique(lbl)))
logger.info("label shape: {}".format(lbl.shape))
logger.info("unique label values: {}".format(np.unique(lbl)))
lbl_viz = imgviz.label2rgb(lbl)
plt.imshow(lbl_viz)
plt.show()
if __name__ == '__main__':
if __name__ == "__main__":
main()
......@@ -12,20 +12,24 @@ from labelme import utils
def main():
logger.warning('This script is aimed to demonstrate how to convert the '
'JSON file to a single image dataset.')
logger.warning("It won't handle multiple JSON files to generate a "
"real-use dataset.")
logger.warning(
"This script is aimed to demonstrate how to convert the "
"JSON file to a single image dataset."
)
logger.warning(
"It won't handle multiple JSON files to generate a "
"real-use dataset."
)
parser = argparse.ArgumentParser()
parser.add_argument('json_file')
parser.add_argument('-o', '--out', default=None)
parser.add_argument("json_file")
parser.add_argument("-o", "--out", default=None)
args = parser.parse_args()
json_file = args.json_file
if args.out is None:
out_dir = osp.basename(json_file).replace('.', '_')
out_dir = osp.basename(json_file).replace(".", "_")
out_dir = osp.join(osp.dirname(json_file), out_dir)
else:
out_dir = args.out
......@@ -33,25 +37,25 @@ def main():
os.mkdir(out_dir)
data = json.load(open(json_file))
imageData = data.get('imageData')
imageData = data.get("imageData")
if not imageData:
imagePath = os.path.join(os.path.dirname(json_file), data['imagePath'])
with open(imagePath, 'rb') as f:
imagePath = os.path.join(os.path.dirname(json_file), data["imagePath"])
with open(imagePath, "rb") as f:
imageData = f.read()
imageData = base64.b64encode(imageData).decode('utf-8')
imageData = base64.b64encode(imageData).decode("utf-8")
img = utils.img_b64_to_arr(imageData)
label_name_to_value = {'_background_': 0}
for shape in sorted(data['shapes'], key=lambda x: x['label']):
label_name = shape['label']
label_name_to_value = {"_background_": 0}
for shape in sorted(data["shapes"], key=lambda x: x["label"]):
label_name = shape["label"]
if label_name in label_name_to_value:
label_value = label_name_to_value[label_name]
else:
label_value = len(label_name_to_value)
label_name_to_value[label_name] = label_value
lbl, _ = utils.shapes_to_label(
img.shape, data['shapes'], label_name_to_value
img.shape, data["shapes"], label_name_to_value
)
label_names = [None] * (max(label_name_to_value.values()) + 1)
......@@ -59,19 +63,19 @@ def main():
label_names[value] = name
lbl_viz = imgviz.label2rgb(
label=lbl, img=imgviz.asgray(img), label_names=label_names, loc='rb'
label=lbl, img=imgviz.asgray(img), label_names=label_names, loc="rb"
)
PIL.Image.fromarray(img).save(osp.join(out_dir, 'img.png'))
utils.lblsave(osp.join(out_dir, 'label.png'), lbl)
PIL.Image.fromarray(lbl_viz).save(osp.join(out_dir, 'label_viz.png'))
PIL.Image.fromarray(img).save(osp.join(out_dir, "img.png"))
utils.lblsave(osp.join(out_dir, "label.png"), lbl)
PIL.Image.fromarray(lbl_viz).save(osp.join(out_dir, "label_viz.png"))
with open(osp.join(out_dir, 'label_names.txt'), 'w') as f:
with open(osp.join(out_dir, "label_names.txt"), "w") as f:
for lbl_name in label_names:
f.write(lbl_name + '\n')
f.write(lbl_name + "\n")
logger.info('Saved to: {}'.format(out_dir))
logger.info("Saved to: {}".format(out_dir))
if __name__ == '__main__':
if __name__ == "__main__":
main()
......@@ -14,57 +14,55 @@ import sys
def get_ip():
dist = platform.platform().split('-')[0]
if dist == 'Linux':
return ''
elif dist == 'Darwin':
cmd = 'ifconfig en0'
dist = platform.platform().split("-")[0]
if dist == "Linux":
return ""
elif dist == "Darwin":
cmd = "ifconfig en0"
output = subprocess.check_output(shlex.split(cmd))
if str != bytes: # Python3
output = output.decode('utf-8')
output = output.decode("utf-8")
for row in output.splitlines():
cols = row.strip().split(' ')
if cols[0] == 'inet':
cols = row.strip().split(" ")
if cols[0] == "inet":
ip = cols[1]
return ip
else:
raise RuntimeError('No ip is found.')
raise RuntimeError("No ip is found.")
else:
raise RuntimeError('Unsupported platform.')
raise RuntimeError("Unsupported platform.")
def labelme_on_docker(in_file, out_file):
ip = get_ip()
cmd = 'xhost + %s' % ip
cmd = "xhost + %s" % ip
subprocess.check_output(shlex.split(cmd))
if out_file:
out_file = osp.abspath(out_file)
if osp.exists(out_file):
raise RuntimeError('File exists: %s' % out_file)
raise RuntimeError("File exists: %s" % out_file)
else:
open(osp.abspath(out_file), 'w')
open(osp.abspath(out_file), "w")
cmd = 'docker run -it --rm' \
' -e DISPLAY={0}:0' \
' -e QT_X11_NO_MITSHM=1' \
' -v /tmp/.X11-unix:/tmp/.X11-unix' \
' -v {1}:{2}' \
' -w /home/developer'
in_file_a = osp.abspath(in_file)
in_file_b = osp.join('/home/developer', osp.basename(in_file))
cmd = cmd.format(
ip,
in_file_a,
in_file_b,
cmd = (
"docker run -it --rm"
" -e DISPLAY={0}:0"
" -e QT_X11_NO_MITSHM=1"
" -v /tmp/.X11-unix:/tmp/.X11-unix"
" -v {1}:{2}"
" -w /home/developer"
)
in_file_a = osp.abspath(in_file)
in_file_b = osp.join("/home/developer", osp.basename(in_file))
cmd = cmd.format(ip, in_file_a, in_file_b,)
if out_file:
out_file_a = osp.abspath(out_file)
out_file_b = osp.join('/home/developer', osp.basename(out_file))
cmd += ' -v {0}:{1}'.format(out_file_a, out_file_b)
cmd += ' wkentaro/labelme labelme {0}'.format(in_file_b)
out_file_b = osp.join("/home/developer", osp.basename(out_file))
cmd += " -v {0}:{1}".format(out_file_a, out_file_b)
cmd += " wkentaro/labelme labelme {0}".format(in_file_b)
if out_file:
cmd += ' -O {0}'.format(out_file_b)
cmd += " -O {0}".format(out_file_b)
subprocess.call(shlex.split(cmd))
if out_file:
......@@ -72,29 +70,29 @@ def labelme_on_docker(in_file, out_file):
json.load(open(out_file))
return out_file
except Exception:
if open(out_file).read() == '':
if open(out_file).read() == "":
os.remove(out_file)
raise RuntimeError('Annotation is cancelled.')
raise RuntimeError("Annotation is cancelled.")
def main():
parser = argparse.ArgumentParser()
parser.add_argument('in_file', help='Input file or directory.')
parser.add_argument('-O', '--output')
parser.add_argument("in_file", help="Input file or directory.")
parser.add_argument("-O", "--output")
args = parser.parse_args()
if not distutils.spawn.find_executable('docker'):
print('Please install docker', file=sys.stderr)
if not distutils.spawn.find_executable("docker"):
print("Please install docker", file=sys.stderr)
sys.exit(1)
try:
out_file = labelme_on_docker(args.in_file, args.output)
if out_file:
print('Saved to: %s' % out_file)
print("Saved to: %s" % out_file)
except RuntimeError as e:
sys.stderr.write(e.__str__() + '\n')
sys.stderr.write(e.__str__() + "\n")
sys.exit(1)
if __name__ == '__main__':
if __name__ == "__main__":
main()
......@@ -14,11 +14,9 @@ def update_dict(target_dict, new_dict, validate_item=None):
if validate_item:
validate_item(key, value)
if key not in target_dict:
logger.warn('Skipping unexpected key in config: {}'
.format(key))
logger.warn("Skipping unexpected key in config: {}".format(key))
continue
if isinstance(target_dict[key], dict) and \
isinstance(value, dict):
if isinstance(target_dict[key], dict) and isinstance(value, dict):
update_dict(target_dict[key], value, validate_item=validate_item)
else:
target_dict[key] = value
......@@ -28,33 +26,33 @@ def update_dict(target_dict, new_dict, validate_item=None):
def get_default_config():
config_file = osp.join(here, 'default_config.yaml')
config_file = osp.join(here, "default_config.yaml")
with open(config_file) as f:
config = yaml.safe_load(f)
# save default config to ~/.labelmerc
user_config_file = osp.join(osp.expanduser('~'), '.labelmerc')
user_config_file = osp.join(osp.expanduser("~"), ".labelmerc")
if not osp.exists(user_config_file):
try:
shutil.copy(config_file, user_config_file)
except Exception:
logger.warn('Failed to save config: {}'.format(user_config_file))
logger.warn("Failed to save config: {}".format(user_config_file))
return config
def validate_config_item(key, value):
if key == 'validate_label' and value not in [None, 'exact']:
if key == "validate_label" and value not in [None, "exact"]:
raise ValueError(
"Unexpected value for config key 'validate_label': {}"
.format(value)
"Unexpected value for config key 'validate_label': {}".format(
value
)
)
if key == 'shape_color' and value not in [None, 'auto', 'manual']:
if key == "shape_color" and value not in [None, "auto", "manual"]:
raise ValueError(
"Unexpected value for config key 'shape_color': {}"
.format(value)
"Unexpected value for config key 'shape_color': {}".format(value)
)
if key == 'labels' and value is not None and len(value) != len(set(value)):
if key == "labels" and value is not None and len(value) != len(set(value)):
raise ValueError(
"Duplicates are detected for config key 'labels': {}".format(value)
)
......@@ -70,15 +68,17 @@ def get_config(config_file_or_yaml=None, config_from_args=None):
if not isinstance(config_from_yaml, dict):
with open(config_from_yaml) as f:
logger.info(
'Loading config file from: {}'.format(config_from_yaml)
"Loading config file from: {}".format(config_from_yaml)
)
config_from_yaml = yaml.safe_load(f)
update_dict(config, config_from_yaml,
validate_item=validate_config_item)
update_dict(
config, config_from_yaml, validate_item=validate_config_item
)
# 3. command line argument or specified config file
if config_from_args is not None:
update_dict(config, config_from_args,
validate_item=validate_config_item)
update_dict(
config, config_from_args, validate_item=validate_config_item
)
return config
......@@ -21,7 +21,7 @@ class LabelFileError(Exception):
class LabelFile(object):
suffix = '.json'
suffix = ".json"
def __init__(self, filename=None):
self.shapes = []
......@@ -36,7 +36,7 @@ class LabelFile(object):
try:
image_pil = PIL.Image.open(filename)
except IOError:
logger.error('Failed opening image file: {}'.format(filename))
logger.error("Failed opening image file: {}".format(filename))
return
# apply orientation to image according to exif
......@@ -45,77 +45,78 @@ class LabelFile(object):
with io.BytesIO() as f:
ext = osp.splitext(filename)[1].lower()
if PY2 and QT4:
format = 'PNG'
elif ext in ['.jpg', '.jpeg']:
format = 'JPEG'
format = "PNG"
elif ext in [".jpg", ".jpeg"]:
format = "JPEG"
else:
format = 'PNG'
format = "PNG"
image_pil.save(f, format=format)
f.seek(0)
return f.read()
def load(self, filename):
keys = [
'version',
'imageData',
'imagePath',
'shapes', # polygonal annotations
'flags', # image level flags
'imageHeight',
'imageWidth',
"version",
"imageData",
"imagePath",
"shapes", # polygonal annotations
"flags", # image level flags
"imageHeight",
"imageWidth",
]
shape_keys = [
'label',
'points',
'group_id',
'shape_type',
'flags',
"label",
"points",
"group_id",
"shape_type",
"flags",
]
try:
with open(filename, 'rb' if PY2 else 'r') as f:
with open(filename, "rb" if PY2 else "r") as f:
data = json.load(f)
version = data.get('version')
version = data.get("version")
if version is None:
logger.warn(
'Loading JSON file ({}) of unknown version'
.format(filename)
"Loading JSON file ({}) of unknown version".format(
filename
)
)
elif version.split('.')[0] != __version__.split('.')[0]:
elif version.split(".")[0] != __version__.split(".")[0]:
logger.warn(
'This JSON file ({}) may be incompatible with '
'current labelme. version in file: {}, '
'current version: {}'.format(
"This JSON file ({}) may be incompatible with "
"current labelme. version in file: {}, "
"current version: {}".format(
filename, version, __version__
)
)
if data['imageData'] is not None:
imageData = base64.b64decode(data['imageData'])
if data["imageData"] is not None:
imageData = base64.b64decode(data["imageData"])
if PY2 and QT4:
imageData = utils.img_data_to_png_data(imageData)
else:
# relative path from label file to relative path from cwd
imagePath = osp.join(osp.dirname(filename), data['imagePath'])
imagePath = osp.join(osp.dirname(filename), data["imagePath"])
imageData = self.load_image_file(imagePath)
flags = data.get('flags') or {}
imagePath = data['imagePath']
flags = data.get("flags") or {}
imagePath = data["imagePath"]
self._check_image_height_and_width(
base64.b64encode(imageData).decode('utf-8'),
data.get('imageHeight'),
data.get('imageWidth'),
base64.b64encode(imageData).decode("utf-8"),
data.get("imageHeight"),
data.get("imageWidth"),
)
shapes = [
dict(
label=s['label'],
points=s['points'],
shape_type=s.get('shape_type', 'polygon'),
flags=s.get('flags', {}),
group_id=s.get('group_id'),
label=s["label"],
points=s["points"],
shape_type=s.get("shape_type", "polygon"),
flags=s.get("flags", {}),
group_id=s.get("group_id"),
other_data={
k: v for k, v in s.items() if k not in shape_keys
}
},
)
for s in data['shapes']
for s in data["shapes"]
]
except Exception as e:
raise LabelFileError(e)
......@@ -138,14 +139,14 @@ class LabelFile(object):
img_arr = utils.img_b64_to_arr(imageData)
if imageHeight is not None and img_arr.shape[0] != imageHeight:
logger.error(
'imageHeight does not match with imageData or imagePath, '
'so getting imageHeight from actual image.'
"imageHeight does not match with imageData or imagePath, "
"so getting imageHeight from actual image."
)
imageHeight = img_arr.shape[0]
if imageWidth is not None and img_arr.shape[1] != imageWidth:
logger.error(
'imageWidth does not match with imageData or imagePath, '
'so getting imageWidth from actual image.'
"imageWidth does not match with imageData or imagePath, "
"so getting imageWidth from actual image."
)
imageWidth = img_arr.shape[1]
return imageHeight, imageWidth
......@@ -162,7 +163,7 @@ class LabelFile(object):
flags=None,
):
if imageData is not None:
imageData = base64.b64encode(imageData).decode('utf-8')
imageData = base64.b64encode(imageData).decode("utf-8")
imageHeight, imageWidth = self._check_image_height_and_width(
imageData, imageHeight, imageWidth
)
......@@ -183,7 +184,7 @@ class LabelFile(object):
assert key not in data
data[key] = value
try:
with open(filename, 'wb' if PY2 else 'w') as f:
with open(filename, "wb" if PY2 else "w") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
self.filename = filename
except Exception as e:
......
......@@ -7,16 +7,15 @@ from . import __appname__
COLORS = {
'WARNING': 'yellow',
'INFO': 'white',
'DEBUG': 'blue',
'CRITICAL': 'red',
'ERROR': 'red',
"WARNING": "yellow",
"INFO": "white",
"DEBUG": "blue",
"CRITICAL": "red",
"ERROR": "red",
}
class ColoredFormatter(logging.Formatter):
def __init__(self, fmt, use_color=True):
logging.Formatter.__init__(self, fmt)
self.use_color = use_color
......@@ -27,27 +26,25 @@ class ColoredFormatter(logging.Formatter):
def colored(text):
return termcolor.colored(
text,
color=COLORS[levelname],
attrs={'bold': True},
text, color=COLORS[levelname], attrs={"bold": True},
)
record.levelname2 = colored('{:<7}'.format(record.levelname))
record.levelname2 = colored("{:<7}".format(record.levelname))
record.message2 = colored(record.msg)
asctime2 = datetime.datetime.fromtimestamp(record.created)
record.asctime2 = termcolor.colored(asctime2, color='green')
record.asctime2 = termcolor.colored(asctime2, color="green")
record.module2 = termcolor.colored(record.module, color='cyan')
record.funcName2 = termcolor.colored(record.funcName, color='cyan')
record.lineno2 = termcolor.colored(record.lineno, color='cyan')
record.module2 = termcolor.colored(record.module, color="cyan")
record.funcName2 = termcolor.colored(record.funcName, color="cyan")
record.lineno2 = termcolor.colored(record.lineno, color="cyan")
return logging.Formatter.format(self, record)
class ColoredLogger(logging.Logger):
FORMAT = (
'[%(levelname2)s] %(module2)s:%(funcName2)s:%(lineno2)s - %(message2)s'
"[%(levelname2)s] %(module2)s:%(funcName2)s:%(lineno2)s - %(message2)s"
)
def __init__(self, name):
......
......@@ -12,11 +12,11 @@ import labelme.utils
R, G, B = SHAPE_COLOR = 0, 255, 0 # green
DEFAULT_LINE_COLOR = QtGui.QColor(R, G, B, 128) # bf hovering
DEFAULT_FILL_COLOR = QtGui.QColor(R, G, B, 128) # hovering
DEFAULT_SELECT_LINE_COLOR = QtGui.QColor(255, 255, 255) # selected
DEFAULT_SELECT_FILL_COLOR = QtGui.QColor(R, G, B, 155) # selected
DEFAULT_VERTEX_FILL_COLOR = QtGui.QColor(R, G, B, 255) # hovering
DEFAULT_LINE_COLOR = QtGui.QColor(R, G, B, 128) # bf hovering
DEFAULT_FILL_COLOR = QtGui.QColor(R, G, B, 128) # hovering
DEFAULT_SELECT_LINE_COLOR = QtGui.QColor(255, 255, 255) # selected
DEFAULT_SELECT_FILL_COLOR = QtGui.QColor(R, G, B, 155) # selected
DEFAULT_VERTEX_FILL_COLOR = QtGui.QColor(R, G, B, 255) # hovering
DEFAULT_HVERTEX_FILL_COLOR = QtGui.QColor(255, 255, 255, 255) # hovering
......@@ -37,8 +37,14 @@ class Shape(object):
point_size = 8
scale = 1.0
def __init__(self, label=None, line_color=None, shape_type=None,
flags=None, group_id=None):
def __init__(
self,
label=None,
line_color=None,
shape_type=None,
flags=None,
group_id=None,
):
self.label = label
self.group_id = group_id
self.points = []
......@@ -72,10 +78,16 @@ class Shape(object):
@shape_type.setter
def shape_type(self, value):
if value is None:
value = 'polygon'
if value not in ['polygon', 'rectangle', 'point',
'line', 'circle', 'linestrip']:
raise ValueError('Unexpected shape_type: {}'.format(value))
value = "polygon"
if value not in [
"polygon",
"rectangle",
"point",
"line",
"circle",
"linestrip",
]:
raise ValueError("Unexpected shape_type: {}".format(value))
self._shape_type = value
def close(self):
......@@ -88,7 +100,7 @@ class Shape(object):
self.points.append(point)
def canAddPoint(self):
return self.shape_type in ['polygon', 'linestrip']
return self.shape_type in ["polygon", "linestrip"]
def popPoint(self):
if self.points:
......@@ -114,8 +126,9 @@ class Shape(object):
def paint(self, painter):
if self.points:
color = self.select_line_color \
if self.selected else self.line_color
color = (
self.select_line_color if self.selected else self.line_color
)
pen = QtGui.QPen(color)
# Try using integer sizes for smoother drawing(?)
pen.setWidth(max(1, int(round(2.0 / self.scale))))
......@@ -124,7 +137,7 @@ class Shape(object):
line_path = QtGui.QPainterPath()
vrtx_path = QtGui.QPainterPath()
if self.shape_type == 'rectangle':
if self.shape_type == "rectangle":
assert len(self.points) in [1, 2]
if len(self.points) == 2:
rectangle = self.getRectFromLine(*self.points)
......@@ -160,8 +173,11 @@ class Shape(object):
painter.drawPath(vrtx_path)
painter.fillPath(vrtx_path, self._vertex_fill_color)
if self.fill:
color = self.select_fill_color \
if self.selected else self.fill_color
color = (
self.select_fill_color
if self.selected
else self.fill_color
)
painter.fillPath(line_path, color)
def drawVertex(self, path, i):
......@@ -183,7 +199,7 @@ class Shape(object):
assert False, "unsupported vertex shape"
def nearestVertex(self, point, epsilon):
min_distance = float('inf')
min_distance = float("inf")
min_i = None
for i, p in enumerate(self.points):
dist = labelme.utils.distance(p - point)
......@@ -193,7 +209,7 @@ class Shape(object):
return min_i
def nearestEdge(self, point, epsilon):
min_distance = float('inf')
min_distance = float("inf")
post_i = None
for i in range(len(self.points)):
line = [self.points[i - 1], self.points[i]]
......@@ -217,7 +233,7 @@ class Shape(object):
return rectangle
def makePath(self):
if self.shape_type == 'rectangle':
if self.shape_type == "rectangle":
path = QtGui.QPainterPath()
if len(self.points) == 2:
rectangle = self.getRectFromLine(*self.points)
......
......@@ -10,24 +10,24 @@ def assert_labelfile_sanity(filename):
data = json.load(open(filename))
assert 'imagePath' in data
imageData = data.get('imageData', None)
assert "imagePath" in data
imageData = data.get("imageData", None)
if imageData is None:
parent_dir = osp.dirname(filename)
img_file = osp.join(parent_dir, data['imagePath'])
img_file = osp.join(parent_dir, data["imagePath"])
assert osp.exists(img_file)
img = imgviz.io.imread(img_file)
else:
img = labelme.utils.img_b64_to_arr(imageData)
H, W = img.shape[:2]
assert H == data['imageHeight']
assert W == data['imageWidth']
assert H == data["imageHeight"]
assert W == data["imageWidth"]
assert 'shapes' in data
for shape in data['shapes']:
assert 'label' in shape
assert 'points' in shape
for x, y in shape['points']:
assert "shapes" in data
for shape in data["shapes"]:
assert "label" in shape
assert "points" in shape
for x, y in shape["points"]:
assert 0 <= x <= W
assert 0 <= y <= H
......@@ -7,17 +7,17 @@ import PIL.Image
def lblsave(filename, lbl):
import imgviz
if osp.splitext(filename)[1] != '.png':
filename += '.png'
if osp.splitext(filename)[1] != ".png":
filename += ".png"
# Assume label ranses [-1, 254] for int32,
# and [0, 255] for uint8 as VOC.
if lbl.min() >= -1 and lbl.max() < 255:
lbl_pil = PIL.Image.fromarray(lbl.astype(np.uint8), mode='P')
lbl_pil = PIL.Image.fromarray(lbl.astype(np.uint8), mode="P")
colormap = imgviz.label_colormap()
lbl_pil.putpalette(colormap.flatten())
lbl_pil.save(filename)
else:
raise ValueError(
'[%s] Cannot save the pixel-wise class label as PNG. '
'Please consider using the .npy format.' % filename
"[%s] Cannot save the pixel-wise class label as PNG. "
"Please consider using the .npy format." % filename
)
......@@ -23,9 +23,9 @@ def img_b64_to_arr(img_b64):
def img_arr_to_b64(img_arr):
img_pil = PIL.Image.fromarray(img_arr)
f = io.BytesIO()
img_pil.save(f, format='PNG')
img_pil.save(f, format="PNG")
img_bin = f.getvalue()
if hasattr(base64, 'encodebytes'):
if hasattr(base64, "encodebytes"):
img_b64 = base64.encodebytes(img_bin)
else:
img_b64 = base64.encodestring(img_bin)
......@@ -38,7 +38,7 @@ def img_data_to_png_data(img_data):
img = PIL.Image.open(f)
with io.BytesIO() as f:
img.save(f, 'PNG')
img.save(f, "PNG")
f.seek(0)
return f.read()
......@@ -58,7 +58,7 @@ def apply_exif_orientation(image):
if k in PIL.ExifTags.TAGS
}
orientation = exif.get('Orientation', None)
orientation = exif.get("Orientation", None)
if orientation == 1:
# do nothing
......
......@@ -12,8 +12,8 @@ here = osp.dirname(osp.abspath(__file__))
def newIcon(icon):
icons_dir = osp.join(here, '../icons')
return QtGui.QIcon(osp.join(':/', icons_dir, '%s.png' % icon))
icons_dir = osp.join(here, "../icons")
return QtGui.QIcon(osp.join(":/", icons_dir, "%s.png" % icon))
def newButton(text, icon=None, slot=None):
......@@ -25,12 +25,21 @@ def newButton(text, icon=None, slot=None):
return b
def newAction(parent, text, slot=None, shortcut=None, icon=None,
tip=None, checkable=False, enabled=True, checked=False):
def newAction(
parent,
text,
slot=None,
shortcut=None,
icon=None,
tip=None,
checkable=False,
enabled=True,
checked=False,
):
"""Create a new action and assign callbacks, shortcuts, etc."""
a = QtWidgets.QAction(text, parent)
if icon is not None:
a.setIconText(text.replace(' ', '\n'))
a.setIconText(text.replace(" ", "\n"))
a.setIcon(newIcon(icon))
if shortcut is not None:
if isinstance(shortcut, (list, tuple)):
......@@ -60,7 +69,7 @@ def addActions(widget, actions):
def labelValidator():
return QtGui.QRegExpValidator(QtCore.QRegExp(r'^[^ \t].+'), None)
return QtGui.QRegExpValidator(QtCore.QRegExp(r"^[^ \t].+"), None)
class struct(object):
......@@ -85,5 +94,5 @@ def distancetoline(point, line):
def fmtShortcut(text):
mod, key = text.split('+', 1)
return '<b>%s</b>+<b>%s</b>' % (mod, key)
mod, key = text.split("+", 1)
return "<b>%s</b>+<b>%s</b>" % (mod, key)
......@@ -16,32 +16,33 @@ def polygons_to_mask(img_shape, polygons, shape_type=None):
return shape_to_mask(img_shape, points=polygons, shape_type=shape_type)
def shape_to_mask(img_shape, points, shape_type=None,
line_width=10, point_size=5):
def shape_to_mask(
img_shape, points, shape_type=None, line_width=10, point_size=5
):
mask = np.zeros(img_shape[:2], dtype=np.uint8)
mask = PIL.Image.fromarray(mask)
draw = PIL.ImageDraw.Draw(mask)
xy = [tuple(point) for point in points]
if shape_type == 'circle':
assert len(xy) == 2, 'Shape of shape_type=circle must have 2 points'
if shape_type == "circle":
assert len(xy) == 2, "Shape of shape_type=circle must have 2 points"
(cx, cy), (px, py) = xy
d = math.sqrt((cx - px) ** 2 + (cy - py) ** 2)
draw.ellipse([cx - d, cy - d, cx + d, cy + d], outline=1, fill=1)
elif shape_type == 'rectangle':
assert len(xy) == 2, 'Shape of shape_type=rectangle must have 2 points'
elif shape_type == "rectangle":
assert len(xy) == 2, "Shape of shape_type=rectangle must have 2 points"
draw.rectangle(xy, outline=1, fill=1)
elif shape_type == 'line':
assert len(xy) == 2, 'Shape of shape_type=line must have 2 points'
elif shape_type == "line":
assert len(xy) == 2, "Shape of shape_type=line must have 2 points"
draw.line(xy=xy, fill=1, width=line_width)
elif shape_type == 'linestrip':
elif shape_type == "linestrip":
draw.line(xy=xy, fill=1, width=line_width)
elif shape_type == 'point':
assert len(xy) == 1, 'Shape of shape_type=point must have 1 points'
elif shape_type == "point":
assert len(xy) == 1, "Shape of shape_type=point must have 1 points"
cx, cy = xy[0]
r = point_size
draw.ellipse([cx - r, cy - r, cx + r, cy + r], outline=1, fill=1)
else:
assert len(xy) > 2, 'Polygon must have points more than 2'
assert len(xy) > 2, "Polygon must have points more than 2"
draw.polygon(xy=xy, outline=1, fill=1)
mask = np.array(mask, dtype=bool)
return mask
......@@ -52,12 +53,12 @@ def shapes_to_label(img_shape, shapes, label_name_to_value):
ins = np.zeros_like(cls)
instances = []
for shape in shapes:
points = shape['points']
label = shape['label']
group_id = shape.get('group_id')
points = shape["points"]
label = shape["label"]
group_id = shape.get("group_id")
if group_id is None:
group_id = uuid.uuid1()
shape_type = shape.get('shape_type', None)
shape_type = shape.get("shape_type", None)
cls_name = label
instance = (cls_name, group_id)
......@@ -75,12 +76,14 @@ def shapes_to_label(img_shape, shapes, label_name_to_value):
def labelme_shapes_to_label(img_shape, shapes):
logger.warn('labelme_shapes_to_label is deprecated, so please use '
'shapes_to_label.')
logger.warn(
"labelme_shapes_to_label is deprecated, so please use "
"shapes_to_label."
)
label_name_to_value = {'_background_': 0}
label_name_to_value = {"_background_": 0}
for shape in shapes:
label_name = shape['label']
label_name = shape["label"]
if label_name in label_name_to_value:
label_value = label_name_to_value[label_name]
else:
......@@ -94,13 +97,11 @@ def labelme_shapes_to_label(img_shape, shapes):
def masks_to_bboxes(masks):
if masks.ndim != 3:
raise ValueError(
'masks.ndim must be 3, but it is {}'
.format(masks.ndim)
"masks.ndim must be 3, but it is {}".format(masks.ndim)
)
if masks.dtype != bool:
raise ValueError(
'masks.dtype must be bool type, but it is {}'
.format(masks.dtype)
"masks.dtype must be bool type, but it is {}".format(masks.dtype)
)
bboxes = []
for mask in masks:
......
......@@ -10,31 +10,31 @@ class BrightnessContrastDialog(QtWidgets.QDialog):
def __init__(self, filename, callback, parent=None):
super(BrightnessContrastDialog, self).__init__(parent)
self.setModal(True)
self.setWindowTitle('Brightness/Contrast')
self.setWindowTitle("Brightness/Contrast")
self.slider_brightness = self._create_slider()
self.slider_contrast = self._create_slider()
formLayout = QtWidgets.QFormLayout()
formLayout.addRow(self.tr('Brightness'), self.slider_brightness)
formLayout.addRow(self.tr('Contrast'), self.slider_contrast)
formLayout.addRow(self.tr("Brightness"), self.slider_brightness)
formLayout.addRow(self.tr("Contrast"), self.slider_contrast)
self.setLayout(formLayout)
self.img = Image.open(filename).convert('RGBA')
self.img = Image.open(filename).convert("RGBA")
self.callback = callback
def onNewValue(self, value):
brightness = self.slider_brightness.value() / 100.
contrast = self.slider_contrast.value() / 100.
brightness = self.slider_brightness.value() / 100.0
contrast = self.slider_contrast.value() / 100.0
img = self.img
img = ImageEnhance.Brightness(img).enhance(brightness)
img = ImageEnhance.Contrast(img).enhance(contrast)
bytes = img.tobytes('raw', 'RGBA')
qimage = QtGui.QImage(bytes,
img.size[0], img.size[1],
QtGui.QImage.Format_RGB32).rgbSwapped()
bytes = img.tobytes("raw", "RGBA")
qimage = QtGui.QImage(
bytes, img.size[0], img.size[1], QtGui.QImage.Format_RGB32
).rgbSwapped()
self.callback(qimage)
def _create_slider(self):
......
......@@ -32,17 +32,18 @@ class Canvas(QtWidgets.QWidget):
CREATE, EDIT = 0, 1
# polygon, rectangle, line, or point
_createMode = 'polygon'
_createMode = "polygon"
_fill_drawing = False
def __init__(self, *args, **kwargs):
self.epsilon = kwargs.pop('epsilon', 10.0)
self.double_click = kwargs.pop('double_click', 'close')
if self.double_click not in [None, 'close']:
self.epsilon = kwargs.pop("epsilon", 10.0)
self.double_click = kwargs.pop("double_click", "close")
if self.double_click not in [None, "close"]:
raise ValueError(
'Unexpected value for double_click event: {}'
.format(self.double_click)
"Unexpected value for double_click event: {}".format(
self.double_click
)
)
super(Canvas, self).__init__(*args, **kwargs)
# Initialise local state.
......@@ -95,9 +96,15 @@ class Canvas(QtWidgets.QWidget):
@createMode.setter
def createMode(self, value):
if value not in ['polygon', 'rectangle', 'circle',
'line', 'point', 'linestrip']:
raise ValueError('Unsupported createMode: %s' % value)
if value not in [
"polygon",
"rectangle",
"circle",
"line",
"point",
"linestrip",
]:
raise ValueError("Unsupported createMode: %s" % value)
self._createMode = value
def storeShapes(self):
......@@ -187,26 +194,29 @@ class Canvas(QtWidgets.QWidget):
# Don't allow the user to draw outside the pixmap.
# Project the point to the pixmap's edges.
pos = self.intersectionPoint(self.current[-1], pos)
elif len(self.current) > 1 and self.createMode == 'polygon' and\
self.closeEnough(pos, self.current[0]):
elif (
len(self.current) > 1
and self.createMode == "polygon"
and self.closeEnough(pos, self.current[0])
):
# Attract line to starting point and
# colorise to alert the user.
pos = self.current[0]
self.overrideCursor(CURSOR_POINT)
self.current.highlightVertex(0, Shape.NEAR_VERTEX)
if self.createMode in ['polygon', 'linestrip']:
if self.createMode in ["polygon", "linestrip"]:
self.line[0] = self.current[-1]
self.line[1] = pos
elif self.createMode == 'rectangle':
elif self.createMode == "rectangle":
self.line.points = [self.current[0], pos]
self.line.close()
elif self.createMode == 'circle':
elif self.createMode == "circle":
self.line.points = [self.current[0], pos]
self.line.shape_type = "circle"
elif self.createMode == 'line':
elif self.createMode == "line":
self.line.points = [self.current[0], pos]
self.line.close()
elif self.createMode == 'point':
elif self.createMode == "point":
self.line.points = [self.current[0]]
self.line.close()
self.repaint()
......@@ -220,8 +230,9 @@ class Canvas(QtWidgets.QWidget):
self.boundedMoveShapes(self.selectedShapesCopy, pos)
self.repaint()
elif self.selectedShapes:
self.selectedShapesCopy = \
[s.copy() for s in self.selectedShapes]
self.selectedShapesCopy = [
s.copy() for s in self.selectedShapes
]
self.repaint()
return
......@@ -268,7 +279,8 @@ class Canvas(QtWidgets.QWidget):
self.prevhShape = self.hShape = shape
self.prevhEdge = self.hEdge = index_edge
self.setToolTip(
self.tr("Click & drag to move shape '%s'") % shape.label)
self.tr("Click & drag to move shape '%s'") % shape.label
)
self.setStatusTip(self.toolTip())
self.overrideCursor(CURSOR_GRAB)
self.update()
......@@ -313,16 +325,16 @@ class Canvas(QtWidgets.QWidget):
if self.drawing():
if self.current:
# Add point to existing shape.
if self.createMode == 'polygon':
if self.createMode == "polygon":
self.current.addPoint(self.line[1])
self.line[0] = self.current[-1]
if self.current.isClosed():
self.finalise()
elif self.createMode in ['rectangle', 'circle', 'line']:
elif self.createMode in ["rectangle", "circle", "line"]:
assert len(self.current.points) == 1
self.current.points = self.line.points
self.finalise()
elif self.createMode == 'linestrip':
elif self.createMode == "linestrip":
self.current.addPoint(self.line[1])
self.line[0] = self.current[-1]
if int(ev.modifiers()) == QtCore.Qt.ControlModifier:
......@@ -331,22 +343,22 @@ class Canvas(QtWidgets.QWidget):
# Create new shape.
self.current = Shape(shape_type=self.createMode)
self.current.addPoint(pos)
if self.createMode == 'point':
if self.createMode == "point":
self.finalise()
else:
if self.createMode == 'circle':
self.current.shape_type = 'circle'
if self.createMode == "circle":
self.current.shape_type = "circle"
self.line.points = [pos, pos]
self.setHiding()
self.drawingPolygon.emit(True)
self.update()
else:
group_mode = (int(ev.modifiers()) == QtCore.Qt.ControlModifier)
group_mode = int(ev.modifiers()) == QtCore.Qt.ControlModifier
self.selectShapePoint(pos, multiple_selection_mode=group_mode)
self.prevPoint = pos
self.repaint()
elif ev.button() == QtCore.Qt.RightButton and self.editing():
group_mode = (int(ev.modifiers()) == QtCore.Qt.ControlModifier)
group_mode = int(ev.modifiers()) == QtCore.Qt.ControlModifier
self.selectShapePoint(pos, multiple_selection_mode=group_mode)
self.prevPoint = pos
self.repaint()
......@@ -355,8 +367,10 @@ class Canvas(QtWidgets.QWidget):
if ev.button() == QtCore.Qt.RightButton:
menu = self.menus[len(self.selectedShapesCopy) > 0]
self.restoreCursor()
if not menu.exec_(self.mapToGlobal(ev.pos())) \
and self.selectedShapesCopy:
if (
not menu.exec_(self.mapToGlobal(ev.pos()))
and self.selectedShapesCopy
):
# Cancel the move by deleting the shadow copy.
self.selectedShapesCopy = []
self.repaint()
......@@ -365,8 +379,10 @@ class Canvas(QtWidgets.QWidget):
if self.movingShape and self.hShape:
index = self.shapes.index(self.hShape)
if (self.shapesBackups[-1][index].points !=
self.shapes[index].points):
if (
self.shapesBackups[-1][index].points
!= self.shapes[index].points
):
self.storeShapes()
self.shapeMoved.emit()
......@@ -405,8 +421,11 @@ class Canvas(QtWidgets.QWidget):
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.double_click == 'close' and self.canCloseShape() and
len(self.current) > 3):
if (
self.double_click == "close"
and self.canCloseShape()
and len(self.current) > 3
):
self.current.popPoint()
self.finalise()
......@@ -428,7 +447,8 @@ class Canvas(QtWidgets.QWidget):
if multiple_selection_mode:
if shape not in self.selectedShapes:
self.selectionChanged.emit(
self.selectedShapes + [shape])
self.selectedShapes + [shape]
)
else:
self.selectionChanged.emit([shape])
return
......@@ -457,8 +477,10 @@ class Canvas(QtWidgets.QWidget):
pos -= QtCore.QPoint(min(0, o1.x()), min(0, o1.y()))
o2 = pos + self.offsets[1]
if self.outOfPixmap(o2):
pos += QtCore.QPoint(min(0, self.pixmap.width() - o2.x()),
min(0, self.pixmap.height() - o2.y()))
pos += QtCore.QPoint(
min(0, self.pixmap.width() - o2.x()),
min(0, self.pixmap.height() - o2.y()),
)
# XXX: 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
......@@ -522,8 +544,9 @@ class Canvas(QtWidgets.QWidget):
p.drawPixmap(0, 0, self.pixmap)
Shape.scale = self.scale
for shape in self.shapes:
if (shape.selected or not self._hideBackround) and \
self.isVisible(shape):
if (shape.selected or not self._hideBackround) and self.isVisible(
shape
):
shape.fill = shape.selected or shape == self.hShape
shape.paint(p)
if self.current:
......@@ -533,8 +556,12 @@ class Canvas(QtWidgets.QWidget):
for s in self.selectedShapesCopy:
s.paint(p)
if (self.fillDrawing() and self.createMode == 'polygon' and
self.current is not None and len(self.current.points) >= 2):
if (
self.fillDrawing()
and self.createMode == "polygon"
and self.current is not None
and len(self.current.points) >= 2
):
drawing_shape = self.current.copy()
drawing_shape.addPoint(self.line[1])
drawing_shape.fill = True
......@@ -582,10 +609,12 @@ class Canvas(QtWidgets.QWidget):
# and find the one intersecting the current line segment.
# http://paulbourke.net/geometry/lineline2d/
size = self.pixmap.size()
points = [(0, 0),
(size.width() - 1, 0),
(size.width() - 1, size.height() - 1),
(0, size.height() - 1)]
points = [
(0, 0),
(size.width() - 1, 0),
(size.width() - 1, size.height() - 1),
(0, size.height() - 1),
]
# x1, y1 should be in the pixmap, x2, y2 should be out of the pixmap
x1 = min(max(p1.x(), 0), size.width() - 1)
y1 = min(max(p1.y(), 0), size.height() - 1)
......@@ -663,7 +692,8 @@ class Canvas(QtWidgets.QWidget):
ev.delta(),
QtCore.Qt.Horizontal
if (QtCore.Qt.ShiftModifier == int(mods))
else QtCore.Qt.Vertical)
else QtCore.Qt.Vertical,
)
else:
self.scrollRequest.emit(ev.delta(), QtCore.Qt.Horizontal)
ev.accept()
......@@ -689,11 +719,11 @@ class Canvas(QtWidgets.QWidget):
assert self.shapes
self.current = self.shapes.pop()
self.current.setOpen()
if self.createMode in ['polygon', 'linestrip']:
if self.createMode in ["polygon", "linestrip"]:
self.line.points = [self.current[-1], self.current[0]]
elif self.createMode in ['rectangle', 'line', 'circle']:
elif self.createMode in ["rectangle", "line", "circle"]:
self.current.points = self.current.points[0:1]
elif self.createMode == 'point':
elif self.createMode == "point":
self.current = None
self.drawingPolygon.emit(True)
......
......@@ -2,7 +2,6 @@ from qtpy import QtWidgets
class ColorDialog(QtWidgets.QColorDialog):
def __init__(self, parent=None):
super(ColorDialog, self).__init__(parent)
self.setOption(QtWidgets.QColorDialog.ShowAlphaChannel)
......@@ -25,6 +24,8 @@ class ColorDialog(QtWidgets.QColorDialog):
return self.currentColor() if self.exec_() else None
def checkRestore(self, button):
if self.bb.buttonRole(button) & \
QtWidgets.QDialogButtonBox.ResetRole and self.default:
if (
self.bb.buttonRole(button) & QtWidgets.QDialogButtonBox.ResetRole
and self.default
):
self.setCurrentColor(self.default)
......@@ -3,7 +3,6 @@ from qtpy import QtWidgets
class EscapableQListWidget(QtWidgets.QListWidget):
def keyPressEvent(self, event):
super(EscapableQListWidget, self).keyPressEvent(event)
if event.key() == Qt.Key_Escape:
......
......@@ -9,7 +9,7 @@ from labelme.logger import logger
import labelme.utils
QT5 = QT_VERSION[0] == '5'
QT5 = QT_VERSION[0] == "5"
# TODO(unknown):
......@@ -17,7 +17,6 @@ QT5 = QT_VERSION[0] == '5'
class LabelQLineEdit(QtWidgets.QLineEdit):
def setListWidget(self, list_widget):
self.list_widget = list_widget
......@@ -29,12 +28,19 @@ class LabelQLineEdit(QtWidgets.QLineEdit):
class LabelDialog(QtWidgets.QDialog):
def __init__(self, text="Enter object label", parent=None, labels=None,
sort_labels=True, show_text_field=True,
completion='startswith', fit_to_content=None, flags=None):
def __init__(
self,
text="Enter object label",
parent=None,
labels=None,
sort_labels=True,
show_text_field=True,
completion="startswith",
fit_to_content=None,
flags=None,
):
if fit_to_content is None:
fit_to_content = {'row': False, 'column': True}
fit_to_content = {"row": False, "column": True}
self._fit_to_content = fit_to_content
super(LabelDialog, self).__init__(parent)
......@@ -45,9 +51,9 @@ class LabelDialog(QtWidgets.QDialog):
if flags:
self.edit.textChanged.connect(self.updateFlags)
self.edit_group_id = QtWidgets.QLineEdit()
self.edit_group_id.setPlaceholderText('Group ID')
self.edit_group_id.setPlaceholderText("Group ID")
self.edit_group_id.setValidator(
QtGui.QRegExpValidator(QtCore.QRegExp(r'\d*'), None)
QtGui.QRegExpValidator(QtCore.QRegExp(r"\d*"), None)
)
layout = QtWidgets.QVBoxLayout()
if show_text_field:
......@@ -61,18 +67,18 @@ class LabelDialog(QtWidgets.QDialog):
QtCore.Qt.Horizontal,
self,
)
bb.button(bb.Ok).setIcon(labelme.utils.newIcon('done'))
bb.button(bb.Cancel).setIcon(labelme.utils.newIcon('undo'))
bb.button(bb.Ok).setIcon(labelme.utils.newIcon("done"))
bb.button(bb.Cancel).setIcon(labelme.utils.newIcon("undo"))
bb.accepted.connect(self.validate)
bb.rejected.connect(self.reject)
layout.addWidget(bb)
# label_list
self.labelList = QtWidgets.QListWidget()
if self._fit_to_content['row']:
if self._fit_to_content["row"]:
self.labelList.setHorizontalScrollBarPolicy(
QtCore.Qt.ScrollBarAlwaysOff
)
if self._fit_to_content['column']:
if self._fit_to_content["column"]:
self.labelList.setVerticalScrollBarPolicy(
QtCore.Qt.ScrollBarAlwaysOff
)
......@@ -83,7 +89,8 @@ class LabelDialog(QtWidgets.QDialog):
self.labelList.sortItems()
else:
self.labelList.setDragDropMode(
QtWidgets.QAbstractItemView.InternalMove)
QtWidgets.QAbstractItemView.InternalMove
)
self.labelList.currentItemChanged.connect(self.labelSelected)
self.labelList.itemDoubleClicked.connect(self.labelDoubleClicked)
self.edit.setListWidget(self.labelList)
......@@ -99,21 +106,21 @@ class LabelDialog(QtWidgets.QDialog):
self.setLayout(layout)
# completion
completer = QtWidgets.QCompleter()
if not QT5 and completion != 'startswith':
if not QT5 and completion != "startswith":
logger.warn(
"completion other than 'startswith' is only "
"supported with Qt5. Using 'startswith'"
)
completion = 'startswith'
if completion == 'startswith':
completion = "startswith"
if completion == "startswith":
completer.setCompletionMode(QtWidgets.QCompleter.InlineCompletion)
# Default settings.
# completer.setFilterMode(QtCore.Qt.MatchStartsWith)
elif completion == 'contains':
elif completion == "contains":
completer.setCompletionMode(QtWidgets.QCompleter.PopupCompletion)
completer.setFilterMode(QtCore.Qt.MatchContains)
else:
raise ValueError('Unsupported completion: {}'.format(completion))
raise ValueError("Unsupported completion: {}".format(completion))
completer.setModel(self.labelList.model())
self.edit.setCompleter(completer)
......@@ -129,7 +136,7 @@ class LabelDialog(QtWidgets.QDialog):
def validate(self):
text = self.edit.text()
if hasattr(text, 'strip'):
if hasattr(text, "strip"):
text = text.strip()
else:
text = text.trimmed()
......@@ -141,7 +148,7 @@ class LabelDialog(QtWidgets.QDialog):
def postProcess(self):
text = self.edit.text()
if hasattr(text, 'strip'):
if hasattr(text, "strip"):
text = text.strip()
else:
text = text.trimmed()
......@@ -164,7 +171,7 @@ class LabelDialog(QtWidgets.QDialog):
self.flagsLayout.removeWidget(item)
item.setParent(None)
def resetFlags(self, label=''):
def resetFlags(self, label=""):
flags = {}
for pattern, keys in self._flags.items():
if re.match(pattern, label):
......@@ -194,11 +201,11 @@ class LabelDialog(QtWidgets.QDialog):
return None
def popUp(self, text=None, move=True, flags=None, group_id=None):
if self._fit_to_content['row']:
if self._fit_to_content["row"]:
self.labelList.setMinimumHeight(
self.labelList.sizeHintForRow(0) * self.labelList.count() + 2
)
if self._fit_to_content['column']:
if self._fit_to_content["column"]:
self.labelList.setMinimumWidth(
self.labelList.sizeHintForColumn(0) + 2
)
......
......@@ -3,7 +3,6 @@ from qtpy import QtWidgets
class ToolBar(QtWidgets.QToolBar):
def __init__(self, title):
super(ToolBar, self).__init__(title)
layout = self.layout()
......
......@@ -7,7 +7,6 @@ from .escapable_qlist_widget import EscapableQListWidget
class UniqueLabelQListWidget(EscapableQListWidget):
def mousePressEvent(self, event):
super(UniqueLabelQListWidget, self).mousePressEvent(event)
if not self.indexAt(event.pos()).isValid():
......@@ -29,11 +28,12 @@ class UniqueLabelQListWidget(EscapableQListWidget):
def setItemLabel(self, item, label, color=None):
qlabel = QtWidgets.QLabel()
if color is None:
qlabel.setText('{}'.format(label))
qlabel.setText("{}".format(label))
else:
qlabel.setText(
'{} <font color="#{:02x}{:02x}{:02x}">●</font>'
.format(label, *color)
'{} <font color="#{:02x}{:02x}{:02x}">●</font>'.format(
label, *color
)
)
qlabel.setAlignment(Qt.AlignBottom)
......
......@@ -4,14 +4,13 @@ from qtpy import QtWidgets
class ZoomWidget(QtWidgets.QSpinBox):
def __init__(self, value=100):
super(ZoomWidget, self).__init__()
self.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
self.setRange(1, 1000)
self.setSuffix(' %')
self.setSuffix(" %")
self.setValue(value)
self.setToolTip('Zoom Level')
self.setToolTip("Zoom Level")
self.setStatusTip(self.toolTip())
self.setAlignment(QtCore.Qt.AlignCenter)
......
[tool.black]
line-length = 79
exclude = '''
(
^/\..*
| ^/docs/
| ^/build/
| ^/github2pypi/
)
'''
......@@ -5,15 +5,21 @@ import re
from setuptools import find_packages
from setuptools import setup
import shlex
import site
import subprocess
import sys
# XXX: with pyproject.toml and without --no-use-pep517,
# sitepackages are not inserted to sys.path by default.
sys.path.extend(site.getsitepackages())
def get_version():
filename = 'labelme/__init__.py'
filename = "labelme/__init__.py"
with open(filename) as f:
match = re.search(
r'''^__version__ = ['"]([^'"]*)['"]''', f.read(), re.M
r"""^__version__ = ['"]([^'"]*)['"]""", f.read(), re.M
)
if not match:
raise RuntimeError("{} doesn't contain __version__".format(filename))
......@@ -27,13 +33,13 @@ def get_install_requires():
assert PY3 or PY2
install_requires = [
'imgviz>=0.11.0',
'matplotlib',
'numpy',
'Pillow>=2.8.0',
'PyYAML',
'qtpy',
'termcolor',
"imgviz>=0.11.0",
"matplotlib",
"numpy",
"Pillow>=2.8.0",
"PyYAML",
"qtpy",
"termcolor",
]
# Find python binding for qt with priority:
......@@ -43,45 +49,49 @@ def get_install_requires():
try:
import PyQt5 # NOQA
QT_BINDING = 'pyqt5'
QT_BINDING = "pyqt5"
except ImportError:
pass
if QT_BINDING is None:
try:
import PySide2 # NOQA
QT_BINDING = 'pyside2'
QT_BINDING = "pyside2"
except ImportError:
pass
if QT_BINDING is None:
try:
import PyQt4 # NOQA
QT_BINDING = 'pyqt4'
QT_BINDING = "pyqt4"
except ImportError:
if PY2:
print(
'Please install PyQt5, PySide2 or PyQt4 for Python2.\n'
'Note that PyQt5 can be installed via pip for Python3.',
"Please install PyQt5, PySide2 or PyQt4 for Python2.\n"
"Note that PyQt5 can be installed via pip for Python3.",
file=sys.stderr,
)
sys.exit(1)
assert PY3
# PyQt5 can be installed via pip for Python3
install_requires.append('PyQt5')
QT_BINDING = 'pyqt5'
install_requires.append("PyQt5")
QT_BINDING = "pyqt5"
del QT_BINDING
return install_requires
def get_long_description():
with open('README.md') as f:
with open("README.md") as f:
long_description = f.read()
try:
import github2pypi
return github2pypi.replace_url(
slug='wkentaro/labelme', content=long_description
slug="wkentaro/labelme", content=long_description
)
except Exception:
return long_description
......@@ -90,63 +100,63 @@ def get_long_description():
def main():
version = get_version()
if sys.argv[1] == 'release':
if not distutils.spawn.find_executable('twine'):
if sys.argv[1] == "release":
if not distutils.spawn.find_executable("twine"):
print(
'Please install twine:\n\n\tpip install twine\n',
"Please install twine:\n\n\tpip install twine\n",
file=sys.stderr,
)
sys.exit(1)
commands = [
'python tests/docs_tests/man_tests/test_labelme_1.py',
'git tag v{:s}'.format(version),
'git push origin master --tag',
'python setup.py sdist',
'twine upload dist/labelme-{:s}.tar.gz'.format(version),
"python tests/docs_tests/man_tests/test_labelme_1.py",
"git tag v{:s}".format(version),
"git push origin master --tag",
"python setup.py sdist",
"twine upload dist/labelme-{:s}.tar.gz".format(version),
]
for cmd in commands:
subprocess.check_call(shlex.split(cmd))
sys.exit(0)
setup(
name='labelme',
name="labelme",
version=version,
packages=find_packages(exclude=['github2pypi']),
description='Image Polygonal Annotation with Python',
packages=find_packages(exclude=["github2pypi"]),
description="Image Polygonal Annotation with Python",
long_description=get_long_description(),
long_description_content_type='text/markdown',
author='Kentaro Wada',
author_email='www.kentaro.wada@gmail.com',
url='https://github.com/wkentaro/labelme',
long_description_content_type="text/markdown",
author="Kentaro Wada",
author_email="www.kentaro.wada@gmail.com",
url="https://github.com/wkentaro/labelme",
install_requires=get_install_requires(),
license='GPLv3',
keywords='Image Annotation, Machine Learning',
license="GPLv3",
keywords="Image Annotation, Machine Learning",
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Natural Language :: English',
'Programming Language :: Python',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Natural Language :: English",
"Programming Language :: Python",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
],
package_data={'labelme': ['icons/*', 'config/*.yaml']},
package_data={"labelme": ["icons/*", "config/*.yaml"]},
entry_points={
'console_scripts': [
'labelme=labelme.__main__:main',
'labelme_draw_json=labelme.cli.draw_json:main',
'labelme_draw_label_png=labelme.cli.draw_label_png:main',
'labelme_json_to_dataset=labelme.cli.json_to_dataset:main',
'labelme_on_docker=labelme.cli.on_docker:main',
"console_scripts": [
"labelme=labelme.__main__:main",
"labelme_draw_json=labelme.cli.draw_json:main",
"labelme_draw_label_png=labelme.cli.draw_label_png:main",
"labelme_json_to_dataset=labelme.cli.json_to_dataset:main",
"labelme_on_docker=labelme.cli.on_docker:main",
],
},
data_files=[('share/man/man1', ['docs/man/labelme.1'])],
data_files=[("share/man/man1", ["docs/man/labelme.1"])],
)
if __name__ == '__main__':
if __name__ == "__main__":
main()
......@@ -11,17 +11,17 @@ import sys
here = osp.dirname(osp.abspath(__file__))
cmd = 'help2man labelme'
cmd = "help2man labelme"
man_expected = subprocess.check_output(shlex.split(cmd)).decode().splitlines()
man_file = osp.realpath(osp.join(here, '../../../docs/man/labelme.1'))
man_file = osp.realpath(osp.join(here, "../../../docs/man/labelme.1"))
with open(man_file) as f:
man_actual = f.read().splitlines()
patterns_exclude = [
r'^\.TH .*',
r'^.*/\.labelmerc\)$',
r'^\.\\.*',
r"^\.TH .*",
r"^.*/\.labelmerc\)$",
r"^\.\\.*",
]
PASS = 1
......@@ -31,13 +31,13 @@ for line_expected, line_actual in zip(man_expected, man_actual):
break
else:
if line_expected != line_actual:
print(repr('> {}'.format(line_expected)), file=sys.stderr)
print(repr('< {}'.format(line_actual)), file=sys.stderr)
print(repr("> {}".format(line_expected)), file=sys.stderr)
print(repr("< {}".format(line_actual)), file=sys.stderr)
PASS = 0
if not PASS:
print(
'Please run:\n\n\thelp2man labelme > {}\n'.format(man_file),
"Please run:\n\n\thelp2man labelme > {}\n".format(man_file),
file=sys.stderr,
)
assert PASS
......@@ -8,14 +8,14 @@ import labelme.testing
here = osp.dirname(osp.abspath(__file__))
data_dir = osp.join(here, 'data')
data_dir = osp.join(here, "data")
def _win_show_and_wait_imageData(qtbot, win):
win.show()
def check_imageData():
assert hasattr(win, 'imageData')
assert hasattr(win, "imageData")
assert win.imageData is not None
qtbot.waitUntil(check_imageData) # wait for loadFile
......@@ -29,7 +29,7 @@ def test_MainWindow_open(qtbot):
def test_MainWindow_open_img(qtbot):
img_file = osp.join(data_dir, 'raw/2011_000003.jpg')
img_file = osp.join(data_dir, "raw/2011_000003.jpg")
win = labelme.app.MainWindow(filename=img_file)
qtbot.addWidget(win)
_win_show_and_wait_imageData(qtbot, win)
......@@ -38,8 +38,8 @@ def test_MainWindow_open_img(qtbot):
def test_MainWindow_open_json(qtbot):
json_files = [
osp.join(data_dir, 'annotated_with_data/apc2016_obj3.json'),
osp.join(data_dir, 'annotated/2011_000003.json'),
osp.join(data_dir, "annotated_with_data/apc2016_obj3.json"),
osp.join(data_dir, "annotated/2011_000003.json"),
]
for json_file in json_files:
labelme.testing.assert_labelfile_sanity(json_file)
......@@ -51,7 +51,7 @@ def test_MainWindow_open_json(qtbot):
def test_MainWindow_open_dir(qtbot):
directory = osp.join(data_dir, 'raw')
directory = osp.join(data_dir, "raw")
win = labelme.app.MainWindow(filename=directory)
qtbot.addWidget(win)
_win_show_and_wait_imageData(qtbot, win)
......@@ -70,33 +70,33 @@ def test_MainWindow_openPrevImg(qtbot):
def test_MainWindow_annotate_jpg(qtbot):
tmp_dir = tempfile.mkdtemp()
input_file = osp.join(data_dir, 'raw/2011_000003.jpg')
out_file = osp.join(tmp_dir, '2011_000003.json')
input_file = osp.join(data_dir, "raw/2011_000003.jpg")
out_file = osp.join(tmp_dir, "2011_000003.json")
config = labelme.config.get_default_config()
win = labelme.app.MainWindow(
config=config,
filename=input_file,
output_file=out_file,
config=config, filename=input_file, output_file=out_file,
)
qtbot.addWidget(win)
_win_show_and_wait_imageData(qtbot, win)
label = 'whole'
label = "whole"
points = [
(100, 100),
(100, 238),
(400, 238),
(400, 100),
]
shapes = [dict(
label=label,
group_id=None,
points=points,
shape_type='polygon',
flags={},
other_data={}
)]
shapes = [
dict(
label=label,
group_id=None,
points=points,
shape_type="polygon",
flags={},
other_data={},
)
]
win.loadLabels(shapes)
win.saveFile()
......
......@@ -16,7 +16,7 @@ def test_img_b64_to_arr():
def test_img_arr_to_b64():
img_file = osp.join(data_dir, 'annotated_with_data/apc2016_obj3.jpg')
img_file = osp.join(data_dir, "annotated_with_data/apc2016_obj3.jpg")
img_arr = np.asarray(PIL.Image.open(img_file))
img_b64 = image_module.img_arr_to_b64(img_arr)
img_arr2 = image_module.img_b64_to_arr(img_b64)
......@@ -24,8 +24,8 @@ def test_img_arr_to_b64():
def test_img_data_to_png_data():
img_file = osp.join(data_dir, 'annotated_with_data/apc2016_obj3.jpg')
with open(img_file, 'rb') as f:
img_file = osp.join(data_dir, "annotated_with_data/apc2016_obj3.jpg")
with open(img_file, "rb") as f:
img_data = f.read()
png_data = image_module.img_data_to_png_data(img_data)
assert isinstance(png_data, bytes)
......@@ -6,18 +6,19 @@ from labelme.utils import shape as shape_module
def test_shapes_to_label():
img, data = get_img_and_data()
label_name_to_value = {}
for shape in data['shapes']:
label_name = shape['label']
for shape in data["shapes"]:
label_name = shape["label"]
label_value = len(label_name_to_value)
label_name_to_value[label_name] = label_value
cls, _ = shape_module.shapes_to_label(
img.shape, data['shapes'], label_name_to_value)
img.shape, data["shapes"], label_name_to_value
)
assert cls.shape == img.shape[:2]
def test_shape_to_mask():
img, data = get_img_and_data()
for shape in data['shapes']:
points = shape['points']
for shape in data["shapes"]:
points = shape["points"]
mask = shape_module.shape_to_mask(img.shape[:2], points)
assert mask.shape == img.shape[:2]
......@@ -6,13 +6,13 @@ from labelme.utils import shape as shape_module
here = osp.dirname(osp.abspath(__file__))
data_dir = osp.join(here, '../data')
data_dir = osp.join(here, "../data")
def get_img_and_data():
json_file = osp.join(data_dir, 'annotated_with_data/apc2016_obj3.json')
json_file = osp.join(data_dir, "annotated_with_data/apc2016_obj3.json")
data = json.load(open(json_file))
img_b64 = data['imageData']
img_b64 = data["imageData"]
img = image_module.img_b64_to_arr(img_b64)
return img, data
......@@ -20,9 +20,9 @@ def get_img_and_data():
def get_img_and_lbl():
img, data = get_img_and_data()
label_name_to_value = {'__background__': 0}
for shape in data['shapes']:
label_name = shape['label']
label_name_to_value = {"__background__": 0}
for shape in data["shapes"]:
label_name = shape["label"]
label_value = len(label_name_to_value)
label_name_to_value[label_name] = label_value
......@@ -32,6 +32,6 @@ def get_img_and_lbl():
label_names[label_value] = label_name
lbl, _ = shape_module.shapes_to_label(
img.shape, data['shapes'], label_name_to_value
img.shape, data["shapes"], label_name_to_value
)
return img, lbl, label_names
......@@ -7,21 +7,17 @@ from labelme.widgets import LabelQLineEdit
def test_LabelQLineEdit(qtbot):
list_widget = QtWidgets.QListWidget()
list_widget.addItems([
'cat',
'dog',
'person',
])
list_widget.addItems(["cat", "dog", "person"])
widget = LabelQLineEdit()
widget.setListWidget(list_widget)
qtbot.addWidget(widget)
# key press to navigate in label list
item = widget.list_widget.findItems('cat', QtCore.Qt.MatchExactly)[0]
item = widget.list_widget.findItems("cat", QtCore.Qt.MatchExactly)[0]
widget.list_widget.setCurrentItem(item)
assert widget.list_widget.currentItem().text() == 'cat'
assert widget.list_widget.currentItem().text() == "cat"
qtbot.keyPress(widget, QtCore.Qt.Key_Down)
assert widget.list_widget.currentItem().text() == 'dog'
assert widget.list_widget.currentItem().text() == "dog"
# key press to enter label
qtbot.keyPress(widget, QtCore.Qt.Key_P)
......@@ -30,37 +26,39 @@ def test_LabelQLineEdit(qtbot):
qtbot.keyPress(widget, QtCore.Qt.Key_S)
qtbot.keyPress(widget, QtCore.Qt.Key_O)
qtbot.keyPress(widget, QtCore.Qt.Key_N)
assert widget.text() == 'person'
assert widget.text() == "person"
def test_LabelDialog_addLabelHistory(qtbot):
labels = ['cat', 'dog', 'person']
labels = ["cat", "dog", "person"]
widget = LabelDialog(labels=labels, sort_labels=True)
qtbot.addWidget(widget)
widget.addLabelHistory('bicycle')
widget.addLabelHistory("bicycle")
assert widget.labelList.count() == 4
widget.addLabelHistory('bicycle')
widget.addLabelHistory("bicycle")
assert widget.labelList.count() == 4
item = widget.labelList.item(0)
assert item.text() == 'bicycle'
assert item.text() == "bicycle"
def test_LabelDialog_popUp(qtbot):
labels = ['cat', 'dog', 'person']
labels = ["cat", "dog", "person"]
widget = LabelDialog(labels=labels, sort_labels=True)
qtbot.addWidget(widget)
# popUp(text='cat')
def interact():
qtbot.keyClick(widget.edit, QtCore.Qt.Key_P) # enter 'p' for 'person' # NOQA
qtbot.keyClick(
widget.edit, QtCore.Qt.Key_P
) # enter 'p' for 'person' # NOQA
qtbot.keyClick(widget.edit, QtCore.Qt.Key_Enter) # NOQA
qtbot.keyClick(widget.edit, QtCore.Qt.Key_Enter) # NOQA
QtCore.QTimer.singleShot(500, interact)
label, flags, group_id = widget.popUp('cat')
assert label == 'person'
label, flags, group_id = widget.popUp("cat")
assert label == "person"
assert flags == {}
assert group_id is None
......@@ -72,19 +70,21 @@ def test_LabelDialog_popUp(qtbot):
QtCore.QTimer.singleShot(500, interact)
label, flags, group_id = widget.popUp()
assert label == 'person'
assert label == "person"
assert flags == {}
assert group_id is None
# popUp() + key_Up
def interact():
qtbot.keyClick(widget.edit, QtCore.Qt.Key_Up) # 'person' -> 'dog' # NOQA
qtbot.keyClick(
widget.edit, QtCore.Qt.Key_Up
) # 'person' -> 'dog' # NOQA
qtbot.keyClick(widget.edit, QtCore.Qt.Key_Enter) # NOQA
qtbot.keyClick(widget.edit, QtCore.Qt.Key_Enter) # NOQA
QtCore.QTimer.singleShot(500, interact)
label, flags, group_id = widget.popUp()
assert label == 'dog'
assert label == "dog"
assert flags == {}
assert group_id is None
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册