# 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
annotations
attribute represents all annotations options that could be selected and the user’s answers;The
disp_number
attribute represents the number of options to be displayed;The
question_value
attribute represents the label to be shown above the selectable options;The
n_rows
andn_cols
displays the number of options to be shows per rows and columns respectively;The
image_path
attibute it’s the image been currently annotated;The
im_width
andim_height
displays the image size;The
label_width
andlabel_height
displays 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