|
| 1 | +import wx |
| 2 | +from azure.ai.inkrecognizer import ApplicationKind, InkPointUnit, InkStrokeKind |
| 3 | +from azure.ai.inkrecognizer import InkRecognizerClient |
| 4 | +from collections import namedtuple |
| 5 | + |
| 6 | + |
| 7 | +# Ink Recognizer Client Config |
| 8 | +URL = "https://api.cognitive.microsoft.com/inkrecognizer" |
| 9 | +CREDENTIAL = "FakeCredential" # Put Azure credential instance here |
| 10 | + |
| 11 | + |
| 12 | +# Recognition Config |
| 13 | +# This tell Ink Recognizer Service that the sample is in en-US. |
| 14 | +# Default value is "en-US". |
| 15 | +# If "language" in a stroke is specified, this will be overlaped in that stroke. |
| 16 | +LANGUAGE_RECOGNITION_LOCALE = "en-US" |
| 17 | +# This tell Ink Recognizer Service that domain of the application is writing, i.e. all strokes are writing. |
| 18 | +# Default value is ApplicationKind.MIXED, which means let Ink Recognizer Service detect kind of strokes. |
| 19 | +# If "kind" in a stroke is specified, this will be overlaped in that stroke. |
| 20 | +APPLICATION_KIND = ApplicationKind.WRITING |
| 21 | + |
| 22 | + |
| 23 | +# This ratio map the number of pixel for x and y axis coordinates on canvas into number of mm |
| 24 | +# In InK Recognizer Server, every coordinate in InkPoint will multiply this number |
| 25 | +# You may also want to mutliply /divide this value before sending request and after receiving response |
| 26 | +app = wx.App(False) |
| 27 | +mm_on_canvas = float(wx.GetDisplaySizeMM()[1]) |
| 28 | +pixel_on_canvas = float(wx.GetDisplaySize()[1]) |
| 29 | +UNIT_MULTIPLE = mm_on_canvas / pixel_on_canvas |
| 30 | + |
| 31 | + |
| 32 | +# UI config |
| 33 | +canvas_width = 800 |
| 34 | +canvas_height = 600 |
| 35 | +linewidth = 3 |
| 36 | + |
| 37 | + |
| 38 | +# Stroke Implementations |
| 39 | +# Shows simple implementation of InkPoint and InkStroke |
| 40 | +InkPoint = namedtuple("InkPoint", "x y") |
| 41 | + |
| 42 | + |
| 43 | +class InkStroke(): |
| 44 | + def __init__(self, |
| 45 | + ink_stroke_id, |
| 46 | + ink_points, |
| 47 | + stroke_kind=InkStrokeKind.UNKNOWN, |
| 48 | + stroke_language=""): |
| 49 | + self.id = ink_stroke_id |
| 50 | + self.points = ink_points |
| 51 | + self.kind = stroke_kind |
| 52 | + self.language = stroke_language |
| 53 | + |
| 54 | + |
| 55 | +# Sample wrapper for InkRecognizerClient that shows how to |
| 56 | +# (1) Convert stroke unit from pixel to mm |
| 57 | +# (2) Set language recognition locale |
| 58 | +# (3) Indexing a key word from recognition results |
| 59 | +# (4) Set application kind if user know expected type of ink content |
| 60 | +class RecognitionManager: |
| 61 | + def __init__(self): |
| 62 | + self._client = InkRecognizerClient( |
| 63 | + URL, CREDENTIAL, |
| 64 | + ink_point_unit=InkPointUnit.MM, |
| 65 | + # Convert stroke unit from pixel to mm by specify unit_multiple |
| 66 | + # You can also multiply the number when creating InkPoints |
| 67 | + unit_multiple=UNIT_MULTIPLE, |
| 68 | + # Set language recognition locale |
| 69 | + language=LANGUAGE_RECOGNITION_LOCALE, |
| 70 | + # Pre-set recognition type |
| 71 | + application_kind=APPLICATION_KIND |
| 72 | + ) |
| 73 | + self._reset_ink() |
| 74 | + |
| 75 | + def _reset_ink(self): |
| 76 | + self._stroke_list = [] |
| 77 | + self._reset_stroke() |
| 78 | + self._root = None |
| 79 | + |
| 80 | + def _reset_stroke(self): |
| 81 | + self._curr_stroke_points = [] |
| 82 | + |
| 83 | + def add_point(self, x, y): |
| 84 | + self._curr_stroke_points.append( |
| 85 | + InkPoint(x, y)) |
| 86 | + |
| 87 | + def stroke_start(self): |
| 88 | + return |
| 89 | + |
| 90 | + def stroke_end(self): |
| 91 | + stroke = InkStroke(len(self._stroke_list), |
| 92 | + self._curr_stroke_points) |
| 93 | + self._stroke_list.append(stroke) |
| 94 | + self._reset_stroke() |
| 95 | + |
| 96 | + def get_stroke_list(self): |
| 97 | + return self._stroke_list |
| 98 | + |
| 99 | + def get_curr_points(self): |
| 100 | + return self._curr_stroke_points |
| 101 | + |
| 102 | + def recognize(self, call_back): |
| 103 | + self._root = self._client.recognize_ink(self._stroke_list) |
| 104 | + result_text = [] |
| 105 | + for word in self._root.ink_words: |
| 106 | + result_text.append(word.recognized_text) |
| 107 | + for shape in self._root.ink_drawings: |
| 108 | + result_text.append(shape.recognized_shape.value) |
| 109 | + result_text = "\n".join(result_text) |
| 110 | + call_back(result_text) |
| 111 | + |
| 112 | + def search(self, word, call_back): |
| 113 | + if self._root is not None: |
| 114 | + # Indexing a key word from recognition results |
| 115 | + words = self._root.find_word(word) |
| 116 | + call_back(len(words)) |
| 117 | + else: |
| 118 | + call_back(0) |
| 119 | + |
| 120 | + |
| 121 | +# Sample canvas |
| 122 | +class Canvas(wx.Panel): |
| 123 | + def __init__(self, parent): |
| 124 | + super(Canvas, self).__init__(parent) |
| 125 | + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) |
| 126 | + self.Bind(wx.EVT_SIZE, self.on_size) |
| 127 | + self.Bind(wx.EVT_PAINT, self.on_paint) |
| 128 | + self.Bind(wx.EVT_LEFT_DOWN, self.on_click) |
| 129 | + self.Bind(wx.EVT_MOTION, self.on_drag) |
| 130 | + self.Bind(wx.EVT_LEFT_UP, self.on_release) |
| 131 | + |
| 132 | + self._recognition_manager = RecognitionManager() |
| 133 | + |
| 134 | + def on_size(self, event): |
| 135 | + event.Skip() |
| 136 | + self.Refresh() |
| 137 | + |
| 138 | + def on_paint(self, event): |
| 139 | + self.dc = wx.PaintDC(self) |
| 140 | + self.dc.SetPen(wx.Pen(wx.BLACK, linewidth)) |
| 141 | + self.dc.Clear() |
| 142 | + for stroke in self._recognition_manager.get_stroke_list(): |
| 143 | + points = stroke.points |
| 144 | + for i in range(len(points) - 1): |
| 145 | + self.dc.DrawLine(points[i].x, points[i].y, points[i+1].x, points[i+1].y) |
| 146 | + |
| 147 | + points = self._recognition_manager.get_curr_points() |
| 148 | + for i in range(len(points) - 1): |
| 149 | + self.dc.DrawLine(points[i].x, points[i].y, points[i+1].x, points[i+1].y) |
| 150 | + |
| 151 | + def on_click(self, event): |
| 152 | + self._recognition_manager.stroke_start() |
| 153 | + |
| 154 | + def on_drag(self, event): |
| 155 | + if event.Dragging(): |
| 156 | + self._recognition_manager.add_point(event.X, event.y) |
| 157 | + self.Refresh() |
| 158 | + |
| 159 | + def on_release(self, event): |
| 160 | + self._recognition_manager.stroke_end() |
| 161 | + self.Refresh() |
| 162 | + |
| 163 | + def recognize(self, event, call_back): |
| 164 | + self._recognition_manager.recognize(call_back) |
| 165 | + |
| 166 | + def clear(self, event): |
| 167 | + self._recognition_manager._reset_ink() |
| 168 | + self.Refresh() |
| 169 | + |
| 170 | + def search(self, event, word, call_back): |
| 171 | + self._recognition_manager.search(word, call_back) |
| 172 | + |
| 173 | + |
| 174 | +# Sample wxpython app |
| 175 | +class InkRecognizerDemo(wx.Frame): |
| 176 | + def __init__(self): |
| 177 | + super(InkRecognizerDemo, self).__init__(None) |
| 178 | + self.SetTitle('Ink Recognition Demo') |
| 179 | + self.SetClientSize((canvas_width, canvas_height)) |
| 180 | + self.Center() |
| 181 | + self.view = Canvas(self) |
| 182 | + self.search_button = wx.Button(self.view, wx.ID_ANY, 'Search', (0, canvas_height - 90)) |
| 183 | + self.recognize_button = wx.Button(self.view, wx.ID_ANY, 'Recognize', (0, canvas_height - 60)) |
| 184 | + self.clear_button = wx.Button(self.view, wx.ID_ANY, 'Clear', (0, canvas_height - 30)) |
| 185 | + |
| 186 | + self.search_text = wx.TextCtrl(self.view, wx.ID_ANY, "", (0, canvas_height - 120)) |
| 187 | + func_search = lambda event: self.view.search(event, self.search_text.GetLineText(0), call_back=self.show_search_result) |
| 188 | + self.search_button.Bind(wx.EVT_BUTTON, func_search) |
| 189 | + func_recognize = lambda event: self.view.recognize(event, call_back=self.show_result) |
| 190 | + self.recognize_button.Bind(wx.EVT_BUTTON, func_recognize) |
| 191 | + self.clear_button.Bind(wx.EVT_BUTTON, self.view.clear) |
| 192 | + |
| 193 | + |
| 194 | + def show_result(self, result): |
| 195 | + dlg = wx.MessageDialog(self, result, "Recognition Result") |
| 196 | + dlg.ShowModal() |
| 197 | + |
| 198 | + def show_search_result(self, num_words): |
| 199 | + dlg = wx.MessageDialog(self, "Find %s words" % num_words, "Recognition Result") |
| 200 | + dlg.ShowModal() |
| 201 | + |
| 202 | + |
| 203 | +def main(): |
| 204 | + frame = InkRecognizerDemo() |
| 205 | + frame.Show() |
| 206 | + app.MainLoop() |
| 207 | + |
| 208 | + |
| 209 | +if __name__ == '__main__': |
| 210 | + main() |
0 commit comments