Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
92de3bd
WIP: compare and copy annotations
matt-deboer Jan 27, 2019
a42e7a9
wip: autoannotator
matt-deboer Jan 28, 2019
ee9f460
submit images before/after separately
matt-deboer Jan 28, 2019
85c713d
pass through all configurations
matt-deboer Jan 28, 2019
98c1301
use custom log method
matt-deboer Jan 28, 2019
e113363
fix env var initialization
matt-deboer Jan 28, 2019
e55d676
Merge remote-tracking branch 'upstream/master' into autopropagate_ann…
matt-deboer Jan 28, 2019
af7911c
missing color_util import
matt-deboer Jan 28, 2019
ae1ecf2
formatting cleanup
matt-deboer Jan 28, 2019
448e06a
use imantics instead of color_util for consistency
matt-deboer Jan 28, 2019
865c83a
another reference to color_util
matt-deboer Jan 28, 2019
31462ad
sanitize segmentation before comparing
matt-deboer Jan 29, 2019
db7595b
cache the images for a dataset
matt-deboer Jan 29, 2019
c6ced44
increase the timeout to 180
matt-deboer Jan 29, 2019
b35a921
Merge remote-tracking branch 'upstream/master' into autopropagate_ann…
matt-deboer Jan 29, 2019
85db849
Merge remote-tracking branch 'upstream/master' into autopropagate_ann…
matt-deboer Jan 29, 2019
9d936f4
restore autoannotator latest
matt-deboer Jan 30, 2019
ec29dd1
restore additional requirements
matt-deboer Jan 30, 2019
0fb14d6
Merge branch 'autopropagate_annotations' of github.com:matt-deboer/co…
matt-deboer Jan 30, 2019
fd8e691
more descriptive about existing annotations
matt-deboer Jan 30, 2019
b8248ec
should be 'image_to'
matt-deboer Jan 30, 2019
37c9ca1
mark as annotated when copying annotations
matt-deboer Jan 30, 2019
6f6c9a6
submit autoannotator annotations as a batch
matt-deboer Jan 30, 2019
e0df0ca
adjust defaults based on experimental results
matt-deboer Jan 30, 2019
b4e423c
formatting
matt-deboer Jan 30, 2019
7a40a8e
convert individual segments to np array
matt-deboer Jan 30, 2019
31c48d3
more verbose logging
matt-deboer Jan 30, 2019
51fc8ff
boundary handling
matt-deboer Jan 30, 2019
e4e722e
fixes for multiple annotations
matt-deboer Jan 30, 2019
c9fd726
close the loop on polygons to avoid coco error
matt-deboer Jan 30, 2019
53c607e
whitespace formatting
matt-deboer Jan 30, 2019
741a5f3
ignore 0-width lines in multipolygon masks
matt-deboer Jan 30, 2019
02bd4f3
code cleanup; remove excess logging
matt-deboer Jan 30, 2019
a382d78
cleanup comment
matt-deboer Jan 30, 2019
7228a1e
don't reuse mask base
matt-deboer Jan 30, 2019
596a70d
Merge remote-tracking branch 'upstream/master' into autopropagate_ann…
matt-deboer Jan 30, 2019
f8b7adc
add wait_for_next, wait_for_prev to assure annotations are complete b…
matt-deboer Jan 30, 2019
8f9d31b
wait without error; if timeout occurs just move on
matt-deboer Jan 30, 2019
5a45f83
wait up to 3 seconds total
matt-deboer Jan 30, 2019
eda3ed4
fix accounting of mismatched and replaced
matt-deboer Jan 30, 2019
c4c3efc
add exception logging for tasks
matt-deboer Jan 31, 2019
cf42458
cache loaded images also
matt-deboer Jan 31, 2019
cc9fdde
print exception messages
matt-deboer Jan 31, 2019
e1c7db1
better error logging for exceptions
matt-deboer Jan 31, 2019
ebced6f
final correction to exception logging
matt-deboer Jan 31, 2019
4856fa3
fix for getting/using cached cvimg
matt-deboer Jan 31, 2019
dfd8c8c
don't cache every image; use lru_cache for less memory consumption
matt-deboer Jan 31, 2019
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
10 changes: 10 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from .models import *
from .authentication import login_manager
from .util import query_util, color_util
from .util.autoannotator import Autoannotator

import threading
import requests
Expand Down Expand Up @@ -63,6 +64,15 @@ def create_app():
if Config.LOAD_IMAGES_ON_START:
ImageModel.load_images(Config.DATASET_DIRECTORY)

if Config.AUTOANNOTATOR_ENABLED:
Autoannotator.start(
max_workers=Config.AUTOANNOTATOR_MAX_WORKERS,
max_queue_size=Config.AUTOANNOTATOR_QUEUE_SIZE,
max_mismatched=Config.AUTOANNOTATOR_MAX_MISMATCHED,
diff_threshold=Config.AUTOANNOTATOR_DIFF_THRESHOLD,
verbose=Config.AUTOANNOTATOR_VERBOSE,
logger=app.logger)


@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
Expand Down
15 changes: 15 additions & 0 deletions app/api/annotator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from ..util import query_util
from ..util import coco_util
from ..util.autoannotator import Autoannotator
from ..util import annotation_util
from ..models import *


Expand Down Expand Up @@ -37,6 +39,10 @@ def post(self):
current_user.update(preferences=data.get('user', {}))

annotated = False

if Autoannotator.enabled:
autoannotator_ids = list()

# Iterate every category passed in the data
for category in data.get('categories', []):
category_id = category.get('id')
Expand Down Expand Up @@ -79,6 +85,11 @@ def post(self):
segmentation, area, bbox = coco_util.\
paperjs_to_coco(width, height, paperjs_object)

if Autoannotator.enabled:
if not annotation_util.segmentation_equal(
segmentation, db_annotation.segmentation):
autoannotator_ids.append(annotation_id)

db_annotation.update(
set__segmentation=segmentation,
set__area=area,
Expand All @@ -89,6 +100,10 @@ def post(self):
if area > 0:
annotated = True

if autoannotator_ids:
Autoannotator.submit(image_id, autoannotator_ids,
wait_for_next=True, wait_for_prev=True)

image_model.update(
set__metadata=image.get('metadata', {}),
set__annotated=annotated,
Expand Down
6 changes: 3 additions & 3 deletions app/api/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from ..util.pagination_util import Pagination
from ..util import query_util, coco_util
from ..models import *

import imantics as im

import datetime
import json
Expand Down Expand Up @@ -312,7 +312,7 @@ def post(self, dataset_id):
errors.append({'category': category_name,
'message': 'Creating category ' + category_name + '.'})

new_category = CategoryModel(name=category_name, color=color_util.random_color_hex())
new_category = CategoryModel(name=category_name, color=im.Color.random().hex)
new_category.save()
categories_id[category_id] = new_category.id
print("Category not found! (Creating new one)", flush=True)
Expand Down Expand Up @@ -384,7 +384,7 @@ def post(self, dataset_id):
annotation.category_id = category_model_id
# annotation.iscrowd = is_crowd
annotation.segmentation = segmentation
annotation.color = color_util.random_color_hex()
annotation.color = im.Color.random().hex
annotation.save()

image_model.update(set__annotated=True)
Expand Down
12 changes: 12 additions & 0 deletions app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ class Config:
INITIALIZE_FROM_FILE = os.getenv("INITIALIZE_FROM_FILE")
LOAD_IMAGES_ON_START = os.getenv("LOAD_IMAGES_ON_START", False)

# Autoannotator options
AUTOANNOTATOR_ENABLED = os.getenv("AUTOANNOTATOR_ENABLED", False)
AUTOANNOTATOR_VERBOSE = os.getenv("AUTOANNOTATOR_VERBOSE", False)
AUTOANNOTATOR_MAX_WORKERS = int(
os.getenv("AUTOANNOTATOR_MAX_WORKERS", 4))
AUTOANNOTATOR_QUEUE_SIZE = int(
os.getenv("AUTOANNOTATOR_QUEUE_SIZE", 32))
AUTOANNOTATOR_MAX_MISMATCHED = int(
os.getenv("AUTOANNOTATOR_MAX_MISMATCHED", 5))
AUTOANNOTATOR_DIFF_THRESHOLD = float(
os.getenv("AUTOANNOTATOR_DIFF_THRESHOLD", 0.0035))

# User Options
LOGIN_DISABLED = os.getenv('LOGIN_DISABLED', False)
ALLOW_REGISTRATION = True
Expand Down
3 changes: 3 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ def copy_annotations(self, annotations):

clone.save(copy=True)

if not self.annotated:
self.update(set__annotated=True)

return annotations.count()

def __call__(self):
Expand Down
63 changes: 63 additions & 0 deletions app/util/annotation_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import cv2
import numpy as np


def extract_cropped_patch(img, mask, bbox):
"""
Creates a copy of img whereever the pixels of mask are non-zero,
then crops the copy by the provided bbox
"""
patch = cv2.bitwise_and(img, img, mask=mask)
x, y, w, h = bbox
return patch[y:y + h, x:x + w]


def rect_union(a, b):
x = min(a[0], b[0])
y = min(a[1], b[1])
w = max(a[0] + a[2], b[0] + b[2]) - x
h = max(a[1] + a[3], b[1] + b[3]) - y
return (x, y, w, h)


def bbox_for_contours(contours):
bbox = (0, 0, 0, 0)
for cnt in contours:
bbox = rect_union(bbox, cv2.boundingRect(cnt))
return bbox


def segmentation_to_contours(segmentation):
contours = list()
for poly in segmentation:
if len(poly) % 2 != 0:
raise ValueError(
"Each polygon should have even number of elements")
polygon = []
for i in range(0, len(poly), 2):
polygon.append([poly[i], poly[i + 1]])
contours.append(np.array(polygon, dtype=np.int32))
return contours


def segmentation_equal(seg_a, seg_b):
"""
Compares 2 segmentations for equality
"""
if len(seg_a) != len(seg_b):
return False

for i, _ in enumerate(seg_a):
# remove excess points used for closing the loop
if len(seg_a[i]) >= 4:
if seg_a[i][0] == seg_a[i][-2] and seg_a[i][1] == seg_a[i][-1]:
seg_a[i] = seg_a[i][:-2]
if len(seg_b[i]) >= 4:
if seg_b[i][0] == seg_b[i][-2] and seg_b[i][1] == seg_b[i][-1]:
seg_b[i] = seg_b[i][:-2]
if len(seg_a[i]) != len(seg_b[i]):
return False
for j in range(len(seg_a[i])):
if seg_a[i][j] != seg_b[i][j]:
return False
return True
Loading