# default_exp im2im_annotator
Image to image annotator¶
The image to image annotator (Im2ImAnnotator) allows the users to navigate through multiple images and select multiple options in any one of them. The current notebook develops this annotator.
State¶
The data shared across the annotator are:
The
annotationsattribute represents all annotations options that could be selected and the user’s answers;The
disp_numberattribute represents the number of options to be displayed;The
question_valueattribute represents the label to be shown above the selectable options;The
n_rowsandn_colsdisplays the number of options to be shows per rows and columns respectively;The
image_pathattibute it’s the image been currently annotated;The
im_widthandim_heightdisplays the image size;The
label_widthandlabel_heightdisplays the selectable options size;
View¶
For the view an internal component called ImCanvas was developed and used to display the current image been annotated.
# export
if is_building_docs():
class ImCanvas(Image):
def __init__(
self,
width: int = 150,
height: int = 150,
has_border: bool = False,
fit_canvas: bool = False
):
super().__init__(width=width, height=height)
image = PILImage.new('RGB', (100, 100), (255, 255, 255))
b = io.BytesIO()
image.save(b, format='PNG')
self.value = b.getvalue()
def _draw_image(self, image_path: str):
self.value = Image.from_file(image_path).value
def _clear_image(self):
pass
def observe_client_ready(self, cb=None):
pass
else:
class ImCanvas(HBox): # type: ignore
def __init__(
self,
width: int = 150,
height: int = 150,
has_border: bool = False,
fit_canvas: bool = False
):
self.has_border = has_border
self.fit_canvas = fit_canvas
self._canvas = Canvas(width=width, height=height)
super().__init__([self._canvas])
def _draw_image(self, image_path: str):
img_render_strategy = ImageRenderer(
clear=True,
has_border=self.has_border,
fit_canvas=self.fit_canvas
)
self._image_scale = img_render_strategy.render(
self._canvas,
image_path
)
def _clear_image(self):
self._canvas.clear()
# needed to support voila
# https://ipycanvas.readthedocs.io/en/latest/advanced.html#ipycanvas-in-voila
def observe_client_ready(self, cb=None):
self._canvas.on_client_ready(cb)
The Im2ImAnnotatorGUI uses the ImCanvas developed before and the component CaptureGrid that displays the selectable options on the view.
grid = Grid(
width=50,
height=50,
n_rows=2,
n_cols=3
)
im2im_state_dict = {
'im_height': 200,
'im_width': 200,
'grid': grid
}
output = OutputImageLabel()
state_to_widget = LabelStoreCaster(output)
app_state = AppWidgetState()
im2im_state = Im2ImState(**im2im_state_dict) # type: ignore
im2im_ = Im2ImAnnotatorGUI(
state_to_widget=state_to_widget,
app_state=app_state,
im2im_state=im2im_state
)
im2im_._im2im_state.image_path = '../data/projects/im2im1/pics/Grass1.png'
im2im_
Controller¶
# remove if the results folder exists this allows
# the next command to construct the annotation path
! rm -rf ../data/projects/im2im1/results
# export
class Im2ImAnnotator(Annotator):
"""
Represents image-to-image annotator.
Gives an ability to itarate through image dataset,
map images with labels for classification,
export final annotations in json format
"""
def __init__(
self,
project_path: Path,
input_item: InputImage,
output_item: Union[OutputImageLabel, OutputLabel],
annotation_file_path,
n_rows=None,
n_cols=None,
label_autosize=False,
question=None,
has_border=False
):
assert input_item, "WARNING: Provide valid Input"
assert output_item, "WARNING: Provide valid Output"
self.project_path = project_path
self.input_item = input_item
self.output_item = output_item
app_state = AppWidgetState(uuid=str(id(self)))
super().__init__(app_state)
grid = Grid(
width=output_item.width,
height=output_item.height,
n_rows=n_rows,
n_cols=n_cols
)
self.im2im_state = Im2ImState(
uuid=str(id(self)),
grid=grid,
annotations=LabelStore(),
im_height=input_item.height,
im_width=input_item.width
)
self.storage = JsonLabelStorage(
im_dir=project_path / input_item.dir,
label_dir=self._get_label_dir(),
annotation_file_path=annotation_file_path
)
self.controller = Im2ImAnnotatorController(
app_state=self.app_state,
im2im_state=self.im2im_state,
storage=self.storage,
input_item=input_item,
output_item=output_item,
question=question,
)
self.state_to_widget = LabelStoreCaster(output_item)
self.view = Im2ImAnnotatorGUI(
app_state=self.app_state,
im2im_state=self.im2im_state,
state_to_widget=self.state_to_widget,
label_autosize=label_autosize,
on_navi_clicked=self.controller.idx_changed,
on_save_btn_clicked=self.controller.save_annotations,
on_grid_box_clicked=self.controller.handle_grid_click,
has_border=has_border,
fit_canvas=input_item.fit_canvas
)
self.app_state.subscribe(self._on_annotation_step_change, 'annotation_step')
# draw current image and bbox only when client is ready
self.view.on_client_ready(self.controller.handle_client_ready)
def _on_annotation_step_change(self, annotation_step: AnnotatorStep):
disabled_buttons = annotation_step == AnnotatorStep.EXPLORE
if disabled_buttons:
self.view._grid_box.clear()
self.state_to_widget.widgets_disabled = disabled_buttons
self.view._save_btn.disabled = disabled_buttons
# forces annotator to have img loaded
self.controller._update_im()
self.controller._update_state()
self.view.load_menu(self.im2im_state.annotations)
def _get_label_dir(self) -> Union[Iterable[str], Path]:
if isinstance(self.output_item, OutputImageLabel):
return self.project_path / self.output_item.dir
elif isinstance(self.output_item, OutputLabel):
return self.output_item.class_labels
else:
raise ValueError(
"output_item should have type OutputLabel or OutputImageLabel"
)
def __repr__(self):
display(self.view)
return ""
def to_dict(self, only_annotated=True):
return self.controller.to_dict(only_annotated)
! rm -rf ..data/projects/im2im1/results
proj_path = validate_project_path('../data/projects/im2im1')
anno_file_path = construct_annotation_path(
file_name='../data/projects/im2im1/results/annotation.json')
in_p = InputImage(image_dir='pics', image_width=300, image_height=300)
out_p = OutputImageLabel(label_dir='class_images', label_width=150, label_height=50)
im2im = Im2ImAnnotator(
project_path=proj_path,
input_item=in_p,
output_item=out_p,
annotation_file_path=anno_file_path,
n_cols=2,
question="HelloWorld"
)
im2im
@pytest.fixture
def im2im_class_labels_fixture():
! rm -rf ../data/projects/im2im1/results/annotation.json
proj_path = validate_project_path('../data/projects/im2im1')
anno_file_path = construct_annotation_path(
file_name='../data/projects/im2im1/results/annotation.json')
in_p = InputImage(image_dir='pics', image_width=300, image_height=300)
out_p = OutputLabel(class_labels=('horse', 'airplane', 'dog'))
im2im = Im2ImAnnotator(
project_path=proj_path,
input_item=in_p,
output_item=out_p,
annotation_file_path=anno_file_path,
n_cols=2,
question="Testing classes"
)
# force fixture to already load its children
im2im.controller.idx_changed(0)
assert len(im2im.view._grid_box.children) > 0
return im2im