# 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