# default_exp annotator
%load_ext autoreload
%autoreload 2
Annotator FactoryΒΆ
The current notebook will develop the annotator factory. Given an input and output, the factory will return the corresponding annotator. Once called the user can choose between the three actions available: explore, create or improve.
The next cell defines the actual factory implementation, expecting the pair of input/output. The following classes defines all supported annotators with correct input/output pairs for internal use.
The following cell uses the factory designed before and define the actions that can be used with the factory.
#export
class Annotator:
"""Ipyannotator uses a pair of input/output to configure its API"""
def __init__(self, input_item: Input, output_item: Output = NoOutput(),
settings: Settings = Settings()):
self.settings = settings
self.input_item = input_item
self.output_item = output_item
def explore(self, k=-1):
"""Visualize the existing annotated dataset.
To explore a part of dataset set `k` - number of classes to display;
By default explore `all` (k == -1)"""
subset = generate_subset_anno_json(project_path=self.settings.project_path,
project_file=self.settings.project_file,
number_of_labels=k)
anno_ = construct_annotation_path(project_path=self.settings.project_path,
file_name=subset, results_dir=None)
annotator = AnnotatorFactory(self.input_item, self.output_item).get_annotator()
self.output_item.drawing_enabled = False
annotator = annotator(project_path=self.settings.project_path,
input_item=self.input_item,
output_item=self.output_item,
annotation_file_path=anno_,
n_cols=self.settings.n_cols,
question="Classification <explore>",
has_border=True)
annotator.app_state.annotation_step = AnnotatorStep.EXPLORE
return annotator
def create(self):
"""Create new annotated dataset from scratch.
If the result path already exists a warning will be displayed to
avoid overriding datasets"""
anno_ = construct_annotation_path(project_path=self.settings.project_path,
file_name=None,
results_dir=self.settings.result_dir)
annotator = AnnotatorFactory(self.input_item, self.output_item).get_annotator()
self.output_item.drawing_enabled = True
annotator = annotator(project_path=self.settings.project_path,
input_item=self.input_item,
output_item=self.output_item,
annotation_file_path=anno_,
n_cols=self.settings.n_cols,
question="Classification <create>",
has_border=True)
annotator.app_state.annotation_step = AnnotatorStep.CREATE
return annotator
def improve(self):
"""Improve existing annotated dataset.
Every annotator has a particular way of improving its datasets.
Some annotators allows label deleting other changing its properties."""
# open labels from create step
create_step_annotations = Path(
self.settings.project_path) / self.settings.result_dir / 'annotations.json'
with open(create_step_annotations) as infile:
loaded_image_annotations = json.load(infile)
# @TODO?
if type(self.output_item) == OutputImageLabel:
#Construct multiple Capturers for each class
out = []
for class_name, class_anno in tqdm(
group_files_by_class(loaded_image_annotations).items()):
anno_ = construct_annotation_path(project_path=self.settings.project_path,
results_dir=(f'{self.settings.result_dir}'
f'/missed/{class_name[:-4]}'))
out.append(CaptureAnnotator(self.settings.project_path,
input_item=self.input_item,
output_item=OutputGridBox(),
annotation_file_path=anno_,
n_cols=3, n_rows=4,
question=(f'Check incorrect annotation'
f' for [{class_name[:-4]}] class'),
filter_files=class_anno))
elif type(self.output_item) == OutputVideoBbox:
self.output_item.drawing_enabled = False
out = BBoxVideoAnnotator(
project_path=self.settings.project_path,
input_item=self.input_item,
output_item=self.output_item,
annotation_file_path=create_step_annotations,
)
elif type(self.output_item) == OutputImageBbox:
out = None
# back to artificial bbox format ->
di = {
k: [
v['bbox'][0]['x'],
v['bbox'][0]['y'],
v['bbox'][0]['width'],
v['bbox'][0]['height']
] if v and v['bbox'] else [] for k, v in loaded_image_annotations.items()}
captured_path = Path(self.settings.project_path) / "captured"
# Save annotated images on disk
for im, bbx in tqdm(di.items()):
# use captured_path instead image_dir, keeping the folder structure
old_im_path = Path(im)
index = old_im_path.parts.index(self.input_item.dir) + 1
new_im_path = captured_path.joinpath(*old_im_path.parts[index:])
new_im_path.parent.mkdir(parents=True, exist_ok=True)
_image = io.imread(im)
if bbx:
rect = [bbx[1], bbx[0]]
rect_dimensions = [bbx[3], bbx[2]]
_image = draw_bbox(rect=rect, rect_dimensions=rect_dimensions,
im=_image, black=True)
io.imsave(str(new_im_path), _image, check_contrast=False)
# Construct Capturer
in_p = InputImage(image_dir="captured", image_width=150, image_height=150)
out_p = OutputGridBox()
anno_ = construct_annotation_path(self.settings.project_path,
results_dir=f'{self.settings.result_dir}/missed')
out = CaptureAnnotator(
self.settings.project_path, input_item=in_p, output_item=out_p,
annotation_file_path=anno_, n_cols=3,
question="Check images with incorrect or empty bbox annotation")
else:
raise Exception(f"Improve is not supported for {self.output_item}")
if isinstance(out, list):
def update_step(anno):
anno.app_state.annotation_step = AnnotatorStep.IMPROVE
return anno
out = [update_step(anno) for anno in out]
else:
out.app_state.annotation_step = AnnotatorStep.IMPROVE
return out