[Example] Add feature extraction part
authorJihoon Lee <jhoon.it.lee@samsung.com>
Tue, 28 Jul 2020 00:59:42 +0000 (09:59 +0900)
committerJijoong Moon <jijoong.moon@samsung.com>
Tue, 4 Aug 2020 11:38:05 +0000 (20:38 +0900)
**Changes proposed in this PR:**
- Add feature extraction using `ml-pipeline-api` (saved to training.dat)
- Handle exit canvas / proceed / reset button
- Add path parsing utils
- Add logger macro

**Self evaluation:**
1. Build test: [X]Passed [ ]Failed [ ]Skipped
2. Run test: [X]Passed [ ]Failed [ ]Skipped

Signed-off-by: Jihoon Lee <jhoon.it.lee@samsung.com>
Applications/Tizen_native/CustomShortcut/README.md
Applications/Tizen_native/CustomShortcut/inc/data.h
Applications/Tizen_native/CustomShortcut/res/edje/main.edc
Applications/Tizen_native/CustomShortcut/src/data.c
Applications/Tizen_native/CustomShortcut/src/main.c
Applications/Tizen_native/CustomShortcut/src/view.c

index ba5ba1c..b0293ee 100644 (file)
@@ -4,7 +4,9 @@
 
 For example, user can draw `^^` few times to train a model to label the drawing as 😊.
 
-Built and tested at `tizen studio 3.7` with `wearable-5.5 profile`
+Built and tested at `tizen studio 3.7` with `wearable-6.0 profile`
+
+tested on `SM-R800` (it should run in other wearable but it is not guaranteed)
 
 ## How to Build and Run
 
index e07e2cc..fa1f907 100644 (file)
@@ -1,4 +1,7 @@
+// SPDX-License-Identifier: Apache-2.0-only
 /**
+ * Copyright (C) 2020 Jihoon Lee <jhoon.it.lee@samsung.com>
+ *
  * @file data.h
  * @date 15 May 2020
  * @brief TIZEN Native Example App dataentry with NNTrainer/CAPI.
@@ -15,7 +18,9 @@
 #include <cairo/cairo-evas-gl.h>
 #include <dlog.h>
 #include <efl_extension.h>
+#include <nnstreamer.h>
 #include <nntrainer.h>
+#include <pthread.h>
 #include <tizen.h>
 
 #ifdef LOG_TAG
@@ -42,6 +47,8 @@ typedef struct appdata {
   Elm_Object_Item *home;
   Evas_Object *layout;
 
+  char edj_path[PATH_MAX];
+
   /**< drawing related */
   Evas_Object *canvas;   /**< image object that cairo surface flushes to */
   unsigned char *pixels; /**< actual pixel data */
@@ -55,7 +62,12 @@ typedef struct appdata {
   DRAW_MODE mode;              /**< drawing mode of current canvas */
   int tries;                   /**< tells how many data has been labeled */
 
-  char edj_path[PATH_MAX];
+  /**< ML related */
+  ml_pipeline_h pipeline;       /**< handle of feature extractor */
+  ml_pipeline_sink_h pipe_sink; /**< sink of pipeline */
+  char pipe_dst[PATH_MAX];      /**< destination path where to save */
+  pthread_mutex_t pipe_lock; /**< ensures that only one pipe runs at a time */
+  pthread_cond_t pipe_cond;  /**< pipe condition to block at a point */
 } appdata_s;
 
 /**
@@ -69,7 +81,28 @@ typedef struct appdata {
  * @param[out] length of data len
  * @retval 0 if no error
  */
-int parse_route(const char *source, char **route, char **data);
+int data_parse_route(const char *source, char **route, char **data);
+
+/**
+ * @brief get full resource path for given file.
+ * @param[in] file relative file path from resource path
+ * @param[out] full_path path of the output file
+ * @param[in] shared true if resource is in shared/res
+ * @retval APP_ERROR_NONE if no error
+ */
+int data_get_resource_path(const char *file, char *full_path, bool shared);
+
+/**
+ * @brief extract data feature from given model.
+ * @param[in] ad appdata
+ * @param[in] dst state the name of the data set
+ * @param[in] append decide whether to append to the exisiting file
+ *
+ * This function runs a mobilnetv2 last layer detached and saves an output
+ * vector. input for this model is png file drawn to the canvas(stored in
+ * appdata) output can be pased to nntrainer and used.
+ */
+int data_extract_feature(appdata_s *ad, const char *dst, bool append);
 
 /**
  * @brief nntrainer sanity test. contructing model and destroy. This will be
@@ -82,23 +115,23 @@ void nntrainer_test();
 #endif
 
 #if !defined(_D)
-#define _D(fmt, arg...)                                                    \
+#define LOG_D(fmt, arg...)                                                 \
   dlog_print(DLOG_DEBUG, LOG_TAG, "[%s:%d] " fmt "\n", __func__, __LINE__, \
              ##arg)
 #endif
 
 #if !defined(_I)
-#define _I(fmt, arg...) \
+#define LOG_I(fmt, arg...) \
   dlog_print(DLOG_INFO, LOG_TAG, "[%s:%d] " fmt "\n", __func__, __LINE__, ##arg)
 #endif
 
 #if !defined(_W)
-#define _W(fmt, arg...) \
+#define LOG_W(fmt, arg...) \
   dlog_print(DLOG_WARN, LOG_TAG, "[%s:%d] " fmt "\n", __func__, __LINE__, ##arg)
 #endif
 
 #if !defined(_E)
-#define _E(fmt, arg...)                                                    \
+#define LOG_E(fmt, arg...)                                                 \
   dlog_print(DLOG_DEBUG, LOG_TAG, "[%s:%d] " fmt "\n", __func__, __LINE__, \
              ##arg)
 #endif
index fe2d120..856daf3 100644 (file)
@@ -128,8 +128,8 @@ collections {
           rel2.relative: 1.0 0.8;
         }
       }
-      PART_BUTTON("draw/reset", "↩️", 0.2, 0.8, 0.45, 0.9)
-      PART_BUTTON("draw/proceed", "▶️", 0.55, 0.8, 0.8, 0.9)
+      PART_BUTTON("draw/reset", "↩️", 0.2, 0.83, 0.45, 0.9)
+      PART_BUTTON("draw/proceed", "▶️", 0.55, 0.83, 0.81, 0.9)
     }
     programs {
       PROGRAM_BUTTON("draw/reset", "draw/reset", "")
index 0725f83..9bfc1aa 100644 (file)
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: Apache-2.0-only
 /**
- * Copyright (C) 2020 Jijoong Moon <jijoong.moon@samsung.com>
+ * Copyright (C) 2020 Jihoon Lee <jhoon.it.lee@samsung.com>
  *
  * @file data.c
  * @date 21 Jul 2020
  *
  */
 #include "data.h"
-
+#include <stdio.h>
 #include <string.h>
 
-int parse_route(const char *source, char **route, char **data) {
+int data_parse_route(const char *source, char **route, char **data) {
   char *dst = strdup(source);
   const char sep = ':';
   char *i;
@@ -43,6 +43,197 @@ int parse_route(const char *source, char **route, char **data) {
   return APP_ERROR_NONE;
 }
 
+int data_get_resource_path(const char *file, char *full_path, bool shared) {
+  char *root_path;
+  if (shared) {
+    root_path = app_get_shared_resource_path();
+  } else {
+    root_path = app_get_resource_path();
+  }
+
+  if (root_path == NULL) {
+    LOG_E("failed to get resource path");
+    return APP_ERROR_INVALID_PARAMETER;
+  }
+
+  if (full_path == NULL) {
+    LOG_E("full_path is null");
+    free(root_path);
+    return APP_ERROR_INVALID_PARAMETER;
+  }
+
+  snprintf(full_path, PATH_MAX, "%s%s", root_path, file);
+  LOG_D("resource path: %s", full_path);
+  free(root_path);
+
+  return APP_ERROR_NONE;
+}
+
+int data_get_data_path(const char *file, char *full_path) {
+  char *root_path;
+
+  root_path = app_get_data_path();
+
+  if (root_path == NULL) {
+    LOG_E("failed to get data path");
+    return APP_ERROR_INVALID_PARAMETER;
+  }
+
+  if (full_path == NULL) {
+    LOG_E("full_path is null");
+    free(root_path);
+    return APP_ERROR_INVALID_PARAMETER;
+  }
+
+  snprintf(full_path, PATH_MAX, "%s%s", root_path, file);
+  LOG_D("data path: %s", full_path);
+  free(root_path);
+
+  return APP_ERROR_NONE;
+}
+
+static void _on_data_receive(ml_tensors_data_h data,
+                             const ml_tensors_info_h info, void *user_data) {
+  appdata_s *ad = (appdata_s *)user_data;
+
+  void *raw_data;
+  size_t data_size, write_result;
+  FILE *file;
+
+  pthread_mutex_lock(&ad->pipe_lock);
+
+  /// @note data_size written here will be overriden and it is intended.
+  int status = ml_tensors_data_get_tensor_data(data, 0, &raw_data, &data_size);
+  if (status != ML_ERROR_NONE) {
+    LOG_E("get tensor data failed %d", status);
+    status = ml_tensors_info_get_tensor_size(info, -1, &data_size);
+    goto CLEAN;
+  }
+
+  file = fopen(ad->pipe_dst, ad->tries == 1 ? "wb" : "wb+");
+
+  if (file == NULL) {
+    LOG_E("cannot open file");
+    goto CLEAN;
+  }
+
+  if (write_result = fwrite(raw_data, data_size, 1, file) < 0) {
+    LOG_E("write error happend");
+  }
+
+  if (write_result < data_size) {
+    LOG_E("data was not fully written to file");
+  }
+
+  if (fclose(file) < 0) {
+    LOG_E("there was error closing");
+    goto CLEAN;
+  }
+
+  LOG_D("using pipeline finished, destroying pipeline");
+
+CLEAN:
+  pthread_cond_signal(&ad->pipe_cond);
+  pthread_mutex_unlock(&ad->pipe_lock);
+}
+
+static int _run_nnpipeline(appdata_s *ad, const char *src, bool append) {
+  char pipe_description[5000];
+
+  char model_path[PATH_MAX];
+
+  int status = ML_ERROR_NONE;
+
+  data_get_resource_path("mobilenetv2.tflite", model_path, false);
+
+  status = pthread_mutex_lock(&ad->pipe_lock);
+  if (status != 0) {
+    LOG_E("acquiring lock failed status: %d", status);
+    return status;
+  }
+
+  LOG_D("pipe ready, starting pipeline");
+
+  sprintf(pipe_description,
+          "filesrc location=%s ! pngdec ! "
+          "videoconvert ! videoscale ! "
+          "video/x-raw,width=224,height=224,format=RGB ! "
+          "tensor_converter ! "
+          "tensor_transform mode=arithmetic option=%s ! "
+          "tensor_filter framework=tensorflow-lite model=%s ! "
+          "tensor_sink name=sink",
+          src, "typecast:float32,add:-127.5,div:127.5", model_path);
+
+  LOG_D("setting inference \n pipe: %s", pipe_description);
+  status = ml_pipeline_construct(pipe_description, NULL, NULL, &ad->pipeline);
+
+  if (status != ML_ERROR_NONE) {
+    LOG_E("something wrong constructing pipeline %d", status);
+    ml_pipeline_destroy(ad->pipeline);
+    pthread_mutex_unlock(&ad->pipe_lock);
+    return status;
+  }
+
+  status = ml_pipeline_sink_register(ad->pipeline, "sink", _on_data_receive,
+                                     (void *)ad, &ad->pipe_sink);
+  if (status != ML_ERROR_NONE) {
+    LOG_E("sink register failed %d", status);
+    goto CLEAN;
+  }
+
+  LOG_D("starting inference");
+  status = ml_pipeline_start(ad->pipeline);
+  if (status != ML_ERROR_NONE) {
+    LOG_E("failed to start pipeline %d", status);
+    goto CLEAN;
+  }
+
+  pthread_cond_wait(&ad->pipe_cond, &ad->pipe_lock);
+
+  LOG_D("stopping pipeline");
+  status = ml_pipeline_stop(ad->pipeline);
+  if (status != ML_ERROR_NONE) {
+    LOG_E("stopping pipeline failed");
+    goto CLEAN;
+  }
+
+  LOG_D("unregister pipeline");
+  if (status != ML_ERROR_NONE) {
+    LOG_E("unregistering sink failed");
+  }
+
+CLEAN:
+  LOG_D("destroying pipeline");
+  ml_pipeline_sink_unregister(ad->pipe_sink);
+  ml_pipeline_destroy(ad->pipeline);
+  ad->pipe_sink = NULL;
+  ad->pipeline = NULL;
+  pthread_mutex_unlock(&ad->pipe_lock);
+  return status;
+}
+
+int data_extract_feature(appdata_s *ad, const char *dst, bool append) {
+  char png_path[PATH_MAX];
+  cairo_status_t cr_stat = CAIRO_STATUS_SUCCESS;
+  int status = APP_ERROR_NONE;
+
+  data_get_data_path("temp.png", png_path);
+  LOG_D("start writing to png_path: %s ", png_path);
+  cr_stat = cairo_surface_write_to_png(ad->cr_surface, png_path);
+
+  if (cr_stat != CAIRO_STATUS_SUCCESS) {
+    LOG_E("failed to write cairo surface as a file reason: %d", cr_stat);
+    return APP_ERROR_INVALID_PARAMETER;
+  }
+
+  data_get_data_path(dst, ad->pipe_dst);
+
+  LOG_I("start inference to dataset: %s ", ad->pipe_dst);
+  status = _run_nnpipeline(ad, png_path, append);
+
+  return status;
+}
+
 void nntrainer_test() {
   ml_train_model_h model = NULL;
 
index d6a7a4b..bed1d33 100644 (file)
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: Apache-2.0-only
 /**
  * @file main.c
  * @date 14 May 2020
@@ -10,6 +11,7 @@
 #include "main.h"
 #include "data.h"
 #include "view.h"
+#include <pthread.h>
 #include <tizen.h>
 
 static bool app_create(void *data) {
@@ -19,14 +21,10 @@ static bool app_create(void *data) {
      If this function returns false, the application is terminated */
   appdata_s *ad = data;
 
-  char *res_path = app_get_resource_path();
-  if (res_path == NULL) {
-    dlog_print(DLOG_ERROR, LOG_TAG, "failed to get resource.");
-    return false;
-  }
+  pthread_mutex_init(&ad->pipe_lock, NULL);
+  pthread_cond_init(&ad->pipe_cond, NULL);
 
-  snprintf(ad->edj_path, sizeof(ad->edj_path), "%s%s", res_path, EDJ_PATH);
-  free(res_path);
+  data_get_resource_path(EDJ_PATH, ad->edj_path, false);
 
   view_init(ad);
 
@@ -51,6 +49,10 @@ static void app_resume(void *data) {
 }
 
 static void app_terminate(void *data) { /* Release all resources. */
+  appdata_s *ad = data;
+
+  pthread_mutex_destroy(&ad->pipe_lock);
+  pthread_cond_destroy(&ad->pipe_cond);
 }
 
 static void ui_app_lang_changed(app_event_info_h event_info, void *user_data) {
@@ -61,8 +63,7 @@ static void ui_app_lang_changed(app_event_info_h event_info, void *user_data) {
 
   ret = app_event_get_language(event_info, &language);
   if (ret != APP_ERROR_NONE) {
-    dlog_print(DLOG_ERROR, LOG_TAG,
-               "app_event_get_language() failed. Err = %d.", ret);
+    LOG_E("app_event_get_language() failed. Err = %d.", ret);
     return;
   }
 
index c1cbb95..fab2eee 100644 (file)
@@ -23,15 +23,18 @@ static void _on_back_pressed(void *data, Evas_Object *obj, void *event_info) {
   appdata_s *ad = data;
   Elm_Widget_Item *nf_it = elm_naviframe_top_item_get(obj);
 
+  ad->tries = 0;
+
   if (!nf_it) {
     /* app should not reach hear */
-    dlog_print(DLOG_ERROR, LOG_TAG, "naviframe is empty.");
+    LOG_E("naviframe is null");
+    dlog_print(DLOG_ERROR, LOG_TAG, "naviframe is e.");
     ui_app_exit();
     return;
   }
 
   if (nf_it == ad->home) {
-    dlog_print(DLOG_DEBUG, LOG_TAG, "navi empty");
+    LOG_D("naviframe is empty");
     elm_win_lower(ad->win);
     return;
   }
@@ -55,8 +58,7 @@ int view_init(appdata_s *ad) {
   win = elm_win_util_standard_add(PACKAGE, PACKAGE);
 
   if (win == NULL) {
-    dlog_print(DLOG_ERROR, LOG_TAG, "failed to get create window err = %d",
-               status);
+    LOG_E("failed to create window err = %d", status);
     return status;
   }
   elm_win_conformant_set(win, EINA_TRUE);
@@ -73,7 +75,7 @@ int view_init(appdata_s *ad) {
   // Adding conformant
   conform = elm_conformant_add(win);
   if (conform == NULL) {
-    dlog_print(DLOG_ERROR, LOG_TAG, "failed to create conformant object");
+    LOG_E("failed to create conformant object");
     evas_object_del(win);
     return APP_ERROR_INVALID_CONTEXT;
   }
@@ -87,7 +89,7 @@ int view_init(appdata_s *ad) {
   // Adding naviframe
   nf = elm_naviframe_add(conform);
   if (nf == NULL) {
-    dlog_print(DLOG_ERROR, LOG_TAG, "failed to create naviframe object");
+    LOG_E("failed to create naviframe object");
     evas_object_del(win);
     return APP_ERROR_INVALID_CONTEXT;
   }
@@ -116,18 +118,18 @@ int view_routes_to(appdata_s *ad, const char *group_name) {
   char *path, *path_data;
   int status;
 
-  status = parse_route(group_name, &path, &path_data);
+  status = data_parse_route(group_name, &path, &path_data);
   if (status) {
-    _E("something wrong with parsing %s", group_name);
+    LOG_E("something wrong with parsing %s", group_name);
     return status;
   }
 
-  _D("%s %s", path, path_data);
+  LOG_D("%s %s", path, path_data);
 
   ad->layout = _create_layout(ad->naviframe, ad->edj_path, path, NULL, NULL);
 
   if (ad->layout == NULL) {
-    _E("failed to create layout");
+    LOG_E("failed to create layout");
     status = APP_ERROR_INVALID_CONTEXT;
     evas_object_del(ad->win);
     goto CLEAN_UP;
@@ -137,6 +139,7 @@ int view_routes_to(appdata_s *ad, const char *group_name) {
                                       ad->layout, "empty");
 
   if (ad->nf_it == NULL) {
+    LOG_E("naviframe_item_push failed");
     status = APP_ERROR_INVALID_PARAMETER;
     goto CLEAN_UP;
   }
@@ -172,7 +175,7 @@ static Evas_Object *_create_layout(Evas_Object *parent, const char *edj_path,
   Evas_Object *layout = NULL;
 
   if (parent == NULL) {
-    dlog_print(DLOG_ERROR, LOG_TAG, "parent cannot be NULL");
+    LOG_E("parent cannot be NULL");
     return NULL;
   }
 
@@ -180,7 +183,7 @@ static Evas_Object *_create_layout(Evas_Object *parent, const char *edj_path,
   elm_layout_file_set(layout, edj_path, group_name);
 
   if (layout == NULL) {
-    dlog_print(DLOG_ERROR, LOG_TAG, "There was error making layout");
+    LOG_E("There was error making layout");
     evas_object_del(layout);
     return NULL;
   }
@@ -198,7 +201,7 @@ static void _on_draw_start(void *data, Evas *e, Evas_Object *obj,
                            void *event_info) {
   appdata_s *ad = (appdata_s *)data;
   Evas_Event_Mouse_Down *eemd = (Evas_Event_Mouse_Down *)event_info;
-  _D("x: %d, y: %d", eemd->canvas.x, eemd->canvas.y);
+  LOG_D("x: %d, y: %d", eemd->canvas.x, eemd->canvas.y);
 
   cairo_set_source_rgba(ad->cr, 1, 1, 1, 1);
   cairo_move_to(ad->cr, eemd->canvas.x - ad->x_offset,
@@ -210,7 +213,7 @@ static void _on_draw_move(void *data, Evas *e, Evas_Object *obj,
   appdata_s *ad = (appdata_s *)data;
   Evas_Event_Mouse_Move *eemm = (Evas_Event_Mouse_Move *)event_info;
 
-  _D("x: %d, y: %d", eemm->cur.canvas.x, eemm->cur.canvas.y);
+  LOG_D("x: %d, y: %d", eemm->cur.canvas.x, eemm->cur.canvas.y);
   cairo_line_to(ad->cr, eemm->cur.canvas.x - ad->x_offset,
                 eemm->cur.canvas.y - ad->y_offset);
 }
@@ -218,47 +221,94 @@ static void _on_draw_move(void *data, Evas *e, Evas_Object *obj,
 static void _on_draw_end(void *data, Evas *e, Evas_Object *obj,
                          void *event_info) {
   appdata_s *ad = (appdata_s *)data;
-  _D("draw end");
+  LOG_D("draw end");
   cairo_stroke(ad->cr);
   cairo_surface_flush(ad->cr_surface);
   evas_object_image_data_update_add(ad->canvas, 0, 0, ad->width, ad->height);
 }
 
-static Eina_Bool _on_canvas_exit(void *data, Elm_Object_Item *it) {
-  _D("exiting canvas");
+static void _on_canvas_exit(void *data, Evas *e, Evas_Object *obj,
+                            void *event_info) {
+  LOG_D("deleting canvas");
   appdata_s *ad = (appdata_s *)data;
-  /// @todo save file to loadable data format
 
   evas_object_del(ad->canvas);
-  cairo_surface_destroy(ad->cr_surface);
   cairo_destroy(ad->cr);
-  return EINA_TRUE;
+  if (cairo_status(ad->cr) != CAIRO_STATUS_SUCCESS) {
+    LOG_E("delete cairo failed");
+  }
+  cairo_surface_destroy(ad->cr_surface);
+  if (cairo_surface_status(ad->cr_surface) != CAIRO_STATUS_SUCCESS) {
+    LOG_E("delete cr_surface failed");
+  }
+}
+
+static void _canvas_erase_all(appdata_s *ad) {
+  cairo_set_source_rgba(ad->cr, 0.5, 0.5, 0.5, 0.5);
+  cairo_set_operator(ad->cr, CAIRO_OPERATOR_SOURCE);
+  cairo_paint(ad->cr);
+  cairo_surface_flush(ad->cr_surface);
+  evas_object_image_data_update_add(ad->canvas, 0, 0, ad->width, ad->height);
+}
+
+static void _on_draw_reset(void *data, Evas_Object *obj, const char *emission,
+                           const char *source) {
+  appdata_s *ad = (appdata_s *)data;
+  LOG_D("draw reset");
+  _canvas_erase_all(ad);
+}
+
+static void _on_draw_proceed(void *data, Evas_Object *obj, const char *emission,
+                             const char *source) {
+  appdata_s *ad = (appdata_s *)data;
+  int status = APP_ERROR_NONE;
+  LOG_D("draw proceed");
+
+  char buf[256];
+  ad->tries++;
+  if (ad->tries >= MAX_TRIES) {
+    ad->tries = 0;
+    elm_naviframe_item_pop(ad->naviframe);
+    view_routes_to(ad, "train_result");
+    return;
+  }
+
+  sprintf(buf, "draw your symbol [%d/%d]", ad->tries, MAX_TRIES);
+  elm_object_part_text_set(obj, "draw/title", buf);
+  LOG_D("starting extraction");
+  status = data_extract_feature(ad, "trainingSet.dat", true);
+
+  if (status != APP_ERROR_NONE) {
+    LOG_E("feature extraction failed");
+  }
+
+  _canvas_erase_all(ad);
 }
 
 static int _create_canvas(appdata_s *ad, const char *draw_mode) {
-  _D("init canvas");
+  LOG_D("init canvas");
   Eina_Bool status;
 
   Evas_Object *frame = elm_layout_add(ad->layout);
 
   status = elm_layout_content_set(ad->layout, "draw/canvas", frame);
   if (status == EINA_FALSE) {
-    _E("failed to get canvas object");
+    LOG_E("failed to get canvas object");
     return APP_ERROR_INVALID_PARAMETER;
   }
 
-  evas_object_move(frame, 72, 72);
-  evas_object_resize(frame, 216, 216);
+  evas_object_move(frame, 70, 70);
+  evas_object_resize(frame, 224, 224);
   evas_object_show(frame);
   Evas_Coord width, height, x, y;
 
   evas_object_geometry_get(frame, &x, &y, &width, &height);
-  _D("frame info, %d %d width: %d height: %d", x, y, width, height);
+  LOG_D("frame info, %d %d width: %d height: %d", x, y, width, height);
 
   Evas_Object *canvas =
     evas_object_image_filled_add(evas_object_evas_get(frame));
   if (canvas == NULL) {
-    _E("failed to initiate canvas");
+    LOG_E("failed to initiate canvas");
     return APP_ERROR_INVALID_PARAMETER;
   }
 
@@ -272,14 +322,14 @@ static int _create_canvas(appdata_s *ad, const char *draw_mode) {
 
   ad->pixels = (unsigned char *)evas_object_image_data_get(canvas, 1);
   if (ad->pixels == NULL) {
-    _E("cannot fetch pixels from image");
+    LOG_E("cannot fetch pixels from image");
     return APP_ERROR_INVALID_PARAMETER;
   }
 
   cairo_surface_t *cairo_surface = cairo_image_surface_create_for_data(
     ad->pixels, CAIRO_FORMAT_ARGB32, width, height, width * 4);
   if (cairo_surface_status(cairo_surface) != CAIRO_STATUS_SUCCESS) {
-    _E("cannot make cairo surface");
+    LOG_E("cannot make cairo surface");
     evas_object_del(canvas);
     cairo_surface_destroy(cairo_surface);
     return APP_ERROR_INVALID_PARAMETER;
@@ -287,7 +337,7 @@ static int _create_canvas(appdata_s *ad, const char *draw_mode) {
 
   cairo_t *cr = cairo_create(cairo_surface);
   if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) {
-    _E("Cannot initiate cairo surface");
+    LOG_E("Cannot initiate cairo surface");
     evas_object_del(canvas);
     cairo_surface_destroy(cairo_surface);
     cairo_destroy(cr);
@@ -310,8 +360,16 @@ static int _create_canvas(appdata_s *ad, const char *draw_mode) {
   evas_object_event_callback_add(canvas, EVAS_CALLBACK_MOUSE_MOVE,
                                  _on_draw_move, (void *)ad);
 
-  elm_naviframe_item_pop_cb_set(ad->nf_it, _on_canvas_exit, ad);
+  evas_object_event_callback_add(ad->layout, EVAS_CALLBACK_DEL, _on_canvas_exit,
+                                 (void *)ad);
+
+  elm_layout_signal_callback_add(ad->layout, "draw/reset", "", _on_draw_reset,
+                                 ad);
+
+  elm_layout_signal_callback_add(ad->layout, "draw/proceed", "",
+                                 _on_draw_proceed, ad);
 
+  ad->tries = 0;
   ad->canvas = canvas;
   ad->cr_surface = cairo_surface;
   ad->cr = cr;