145 lines
5.7 KiB
Python
145 lines
5.7 KiB
Python
|
'''
|
||
|
Multistroke Recognition Database Demonstration
|
||
|
==============================================
|
||
|
|
||
|
This application records gestures and attempts to match them. You should
|
||
|
see a black drawing surface with some buttons across the bottom. As you
|
||
|
make a gesture on the drawing surface, the gesture will be added to
|
||
|
the history and a match will be attempted. If you go to the history tab,
|
||
|
name the gesture, and add it to the database, then similar gestures in the
|
||
|
future will be recognized. You can load and save databases of gestures
|
||
|
in .kg files.
|
||
|
|
||
|
This demonstration code spans many files, with this being the primary file.
|
||
|
The information pop-up ('No match') comes from the file helpers.py.
|
||
|
The history pane is managed in the file historymanager.py and described
|
||
|
in the file historymanager.kv. The database pane and storage is managed in
|
||
|
the file gesturedatabase.py and the described in the file gesturedatabase.kv.
|
||
|
The general logic of the sliders and buttons are in the file
|
||
|
settings.py and described in settings.kv. but the actual settings pane is
|
||
|
described in the file multistroke.kv and managed from this file.
|
||
|
|
||
|
'''
|
||
|
from kivy.app import App
|
||
|
from kivy.uix.gridlayout import GridLayout
|
||
|
from kivy.uix.gesturesurface import GestureSurface
|
||
|
from kivy.uix.screenmanager import ScreenManager, Screen, SlideTransition
|
||
|
from kivy.uix.label import Label
|
||
|
from kivy.multistroke import Recognizer
|
||
|
|
||
|
# Local libraries
|
||
|
from historymanager import GestureHistoryManager
|
||
|
from gesturedatabase import GestureDatabase
|
||
|
from settings import MultistrokeSettingsContainer
|
||
|
|
||
|
|
||
|
class MainMenu(GridLayout):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class MultistrokeAppSettings(MultistrokeSettingsContainer):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class MultistrokeApp(App):
|
||
|
|
||
|
def goto_database_screen(self, *l):
|
||
|
self.database.import_gdb()
|
||
|
self.manager.current = 'database'
|
||
|
|
||
|
def handle_gesture_cleanup(self, surface, g, *l):
|
||
|
if hasattr(g, '_result_label'):
|
||
|
surface.remove_widget(g._result_label)
|
||
|
|
||
|
def handle_gesture_discard(self, surface, g, *l):
|
||
|
# Don't bother creating Label if it's not going to be drawn
|
||
|
if surface.draw_timeout == 0:
|
||
|
return
|
||
|
|
||
|
text = '[b]Discarded:[/b] Not enough input'
|
||
|
g._result_label = Label(text=text, markup=True, size_hint=(None, None),
|
||
|
center=(g.bbox['minx'], g.bbox['miny']))
|
||
|
self.surface.add_widget(g._result_label)
|
||
|
|
||
|
def handle_gesture_complete(self, surface, g, *l):
|
||
|
result = self.recognizer.recognize(g.get_vectors())
|
||
|
result._gesture_obj = g
|
||
|
result.bind(on_complete=self.handle_recognize_complete)
|
||
|
|
||
|
def handle_recognize_complete(self, result, *l):
|
||
|
self.history.add_recognizer_result(result)
|
||
|
|
||
|
# Don't bother creating Label if it's not going to be drawn
|
||
|
if self.surface.draw_timeout == 0:
|
||
|
return
|
||
|
|
||
|
best = result.best
|
||
|
if best['name'] is None:
|
||
|
text = '[b]No match[/b]'
|
||
|
else:
|
||
|
text = 'Name: [b]%s[/b]\nScore: [b]%f[/b]\nDistance: [b]%f[/b]' % (
|
||
|
best['name'], best['score'], best['dist'])
|
||
|
|
||
|
g = result._gesture_obj
|
||
|
g._result_label = Label(text=text, markup=True, size_hint=(None, None),
|
||
|
center=(g.bbox['minx'], g.bbox['miny']))
|
||
|
self.surface.add_widget(g._result_label)
|
||
|
|
||
|
def build(self):
|
||
|
# Setting NoTransition breaks the "history" screen! Possibly related
|
||
|
# to some inexplicable rendering bugs on my particular system
|
||
|
self.manager = ScreenManager(transition=SlideTransition(
|
||
|
duration=.15))
|
||
|
self.recognizer = Recognizer()
|
||
|
|
||
|
# Setup the GestureSurface and bindings to our Recognizer
|
||
|
surface = GestureSurface(line_width=2, draw_bbox=True,
|
||
|
use_random_color=True)
|
||
|
surface_screen = Screen(name='surface')
|
||
|
surface_screen.add_widget(surface)
|
||
|
self.manager.add_widget(surface_screen)
|
||
|
|
||
|
surface.bind(on_gesture_discard=self.handle_gesture_discard)
|
||
|
surface.bind(on_gesture_complete=self.handle_gesture_complete)
|
||
|
surface.bind(on_gesture_cleanup=self.handle_gesture_cleanup)
|
||
|
self.surface = surface
|
||
|
|
||
|
# History is the list of gestures drawn on the surface
|
||
|
history = GestureHistoryManager()
|
||
|
history_screen = Screen(name='history')
|
||
|
history_screen.add_widget(history)
|
||
|
self.history = history
|
||
|
self.manager.add_widget(history_screen)
|
||
|
|
||
|
# Database is the list of gesture templates in Recognizer
|
||
|
database = GestureDatabase(recognizer=self.recognizer)
|
||
|
database_screen = Screen(name='database')
|
||
|
database_screen.add_widget(database)
|
||
|
self.database = database
|
||
|
self.manager.add_widget(database_screen)
|
||
|
|
||
|
# Settings screen
|
||
|
app_settings = MultistrokeAppSettings()
|
||
|
ids = app_settings.ids
|
||
|
|
||
|
ids.max_strokes.bind(value=surface.setter('max_strokes'))
|
||
|
ids.temporal_win.bind(value=surface.setter('temporal_window'))
|
||
|
ids.timeout.bind(value=surface.setter('draw_timeout'))
|
||
|
ids.line_width.bind(value=surface.setter('line_width'))
|
||
|
ids.draw_bbox.bind(value=surface.setter('draw_bbox'))
|
||
|
ids.use_random_color.bind(value=surface.setter('use_random_color'))
|
||
|
|
||
|
settings_screen = Screen(name='settings')
|
||
|
settings_screen.add_widget(app_settings)
|
||
|
self.manager.add_widget(settings_screen)
|
||
|
|
||
|
# Wrap in a gridlayout so the main menu is always visible
|
||
|
layout = GridLayout(cols=1)
|
||
|
layout.add_widget(self.manager)
|
||
|
layout.add_widget(MainMenu())
|
||
|
return layout
|
||
|
|
||
|
|
||
|
if __name__ in ('__main__', '__android__'):
|
||
|
MultistrokeApp().run()
|