Skip to content

Commit 8a6c7c4

Browse files
committed
Adding the Kivy package as gui with streaming data, their native LinePlot works much smoother than matplotlib embedding in tkinter
1 parent 22fb235 commit 8a6c7c4

File tree

8 files changed

+253
-0
lines changed

8 files changed

+253
-0
lines changed

symbionic/_model.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import random
2+
import numpy as np
3+
import symbionic
4+
5+
6+
def prepare_data_for_keras(input_data):
7+
# only select last 195 samples
8+
output_data = input_data[-195:, :]
9+
# preparation for 1D Neural Network
10+
# TODO: it's too slow for fast updating at 0.1 second intervals...
11+
output_data = np.apply_along_axis(lambda x: symbionic.calc_envelope(x,smooth=51), 0, output_data)
12+
output_data = output_data.reshape((1,output_data.shape[0]*output_data.shape[1]))
13+
return output_data
14+
15+
16+
def absmax_at_end(signal):
17+
return np.max(abs(signal[-50:]))
18+
19+
20+
def take_max_abs(input_data):
21+
features = np.apply_along_axis(absmax_at_end, 0, input_data)
22+
features = features.reshape(1, -1)
23+
return features
24+
25+
26+
class FakeModel:
27+
def __init__(self, n_gestures=6):
28+
self.number_of_gestures = n_gestures
29+
30+
def predict(self, input_data):
31+
p = self.number_of_gestures*[0]
32+
p[random.randrange(self.number_of_gestures)] = 1
33+
return p
34+
35+
36+
class GestureModel:
37+
def __init__(self,model=None, data_prepare=None):
38+
self.model = model
39+
self.data_prepare = data_prepare
40+
self.collapse = False
41+
42+
def predict(self,input_data):
43+
prediction = 0
44+
if self.data_prepare is not None:
45+
input_data = self.data_prepare(input_data)
46+
if self.model is not None:
47+
prediction = self.model.predict(input_data)
48+
if self.collapse:
49+
prediction = np.argmax(prediction)
50+
return prediction
51+
52+
53+
class PredictionBuffer:
54+
def __init__(self, n_gestures=6):
55+
self.number_of_gestures = n_gestures
56+
self.buffer = [[] for n in range(self.number_of_gestures)]
57+
self.stored_predictions = 1
58+
59+
def append(self,predictions):
60+
for gesture, gesture_probability in zip(range(self.number_of_gestures), predictions):
61+
self.buffer[gesture].append(gesture_probability)
62+
self.stored_predictions += 1
63+
64+
def get_predictions(self,number_of_predictions=30):
65+
predictions = self.buffer
66+
predictions = [p[-number_of_predictions:] for p in predictions]
67+
return np.array(predictions).transpose()

symbionic/gui/__init__.py

Whitespace-only changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

symbionic/gui/kivy_gui.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#https://kivy.org/doc/stable/installation/installation-windows.html
2+
from kivy.lang import Builder
3+
from kivy.app import App
4+
from kivy.uix.boxlayout import BoxLayout
5+
from kivy.clock import Clock
6+
import symbionic
7+
from symbionic.gui.graph import Graph, LinePlot
8+
from symbionic._model import FakeModel, GestureModel, PredictionBuffer, take_max_abs
9+
from kivy.core.window import Window
10+
from sklearn.externals import joblib
11+
12+
13+
class DataIterator:
14+
def __init__(self, data):
15+
self.index = 0
16+
self.step = 1
17+
self.size = 1
18+
self.data = data
19+
20+
def __iter__(self):
21+
return self
22+
23+
def __next__(self):
24+
if self.index >= len(self.data):
25+
#raise StopIteration
26+
self.index = 0 # start over
27+
start_index = self.index
28+
end_index = start_index + self.step + self.size - 1
29+
self.index = start_index + self.step
30+
return self.data[start_index:end_index, :]
31+
32+
def goto(self, index):
33+
self.index = index
34+
35+
36+
class FileDataStub:
37+
def __init__(self):
38+
emg_data = symbionic.EmgData()
39+
emg_data.load(symbionic.example_data_directory() + "raw3.bin")
40+
emg_array = emg_data.data['g1'].drop('time', axis=1).values
41+
emg_iterator = DataIterator(emg_array)
42+
emg_iterator.step = 80
43+
emg_iterator.size = 500
44+
self.emg_iterator = emg_iterator
45+
46+
def __next__(self):
47+
return next(self.emg_iterator)
48+
49+
50+
class RandomForestModel:
51+
def __init__(self):
52+
# using a pre-trained random forest stored via joblib
53+
self.fitted_model = joblib.load('maxabs_forest.joblib')
54+
55+
def predict(self,data):
56+
# need to convert to a 1D array for single sample data
57+
predictions = self.fitted_model.predict(data)
58+
return predictions.ravel()
59+
60+
61+
class Logic(BoxLayout):
62+
def __init__(self):
63+
super().__init__()
64+
self.number_of_channels = 8
65+
self.number_of_packages = 15
66+
self.number_of_gestures = 7
67+
self.update_interval = 0.1
68+
self.graphs = []
69+
self.plots = []
70+
self.receiver = symbionic.GFDataReceiverSocket(stub=True)
71+
self.fake_data = FileDataStub()
72+
self.rawDataBox = []
73+
self.gestureBox = []
74+
self.gestureModel = GestureModel(FakeModel(self.number_of_gestures), data_prepare=take_max_abs)
75+
#self.gestureModel.model = RandomForestModel()
76+
self.predictions = PredictionBuffer(self.number_of_gestures)
77+
78+
def init_graphs(self):
79+
self.rawDataBox = GraphBox(self.ids.raw_data_box,self.number_of_channels)
80+
self.rawDataBox.init_graphs()
81+
self.gestureBox = GraphBox(self.ids.gesture_box, self.number_of_gestures)
82+
self.gestureBox.ylim = (-0.1,1.1)
83+
self.gestureBox.init_graphs()
84+
85+
def start(self):
86+
self.receiver.start()
87+
Clock.schedule_interval(self.update_gui, self.update_interval)
88+
89+
def stop(self):
90+
self.receiver.stop()
91+
Clock.unschedule(self.update_gui)
92+
93+
def update_gui(self, dt):
94+
#data = self.receiver.dataHandler.get_latest_emg_data(self.number_of_packages)
95+
data = next(self.fake_data)
96+
self.rawDataBox.update_graphs(data)
97+
self.update_predictions(data)
98+
self.update_prediction_graphs()
99+
100+
def update_predictions(self,data):
101+
predictions = self.gestureModel.predict(data)
102+
self.predictions.append(predictions)
103+
104+
def update_prediction_graphs(self):
105+
predictions = self.predictions.get_predictions()
106+
self.gestureBox.update_graphs(predictions)
107+
108+
109+
class GraphBox:
110+
def __init__(self, box_id, number_of_graphs):
111+
self.box_id = box_id
112+
self.number_of_graphs = number_of_graphs
113+
self.graphs = []
114+
self.plots = []
115+
self.ylim = (-150,150)
116+
117+
def init_graphs(self):
118+
for g in range(self.number_of_graphs):
119+
self.add_graph()
120+
121+
def add_graph(self):
122+
graph = Graph(xmin=-0, xmax=100, ymin=self.ylim[0], ymax=self.ylim[1])
123+
plot = LinePlot(color=[1, 0, 0, 1], line_width=1.1)
124+
graph.add_plot(plot)
125+
self.plots.append(plot)
126+
self.graphs.append(graph)
127+
self.box_id.add_widget(graph)
128+
129+
def update_graphs(self,data):
130+
buffer_size = data.shape[0] # data size
131+
x_axis = [x for x in range(0, buffer_size)] # x-axis data of graph
132+
for graph in range(self.number_of_graphs):
133+
single_graph = data[:, graph].tolist()
134+
self.plots[graph].points = [(x, y) for (x, y) in zip(x_axis,single_graph)]
135+
self.graphs[graph].xmax = buffer_size
136+
137+
138+
class SymbionicApp(App):
139+
Window.size = (1000,600)
140+
141+
def build(self):
142+
self.root = Builder.load_file("look.kv")
143+
self.root.init_graphs()
144+
return self.root
145+
146+
147+
if __name__ == "__main__":
148+
SymbionicApp().run()

symbionic/gui/look.kv

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#:import MeshLinePlot symbionic.gui.graph.MeshLinePlot
2+
Logic:
3+
BoxLayout:
4+
orientation: "vertical"
5+
padding: 40
6+
BoxLayout:
7+
orientation: "horizontal"
8+
size_hint: [1, .8]
9+
BoxLayout:
10+
orientation: "vertical"
11+
id: raw_data_box
12+
size_hint: [0.4, 1]
13+
BoxLayout:
14+
orientation: "vertical"
15+
size_hint: [0.1, 1]
16+
BoxLayout:
17+
orientation: "vertical"
18+
id: gesture_box
19+
size_hint: [0.4, 1]
20+
BoxLayout:
21+
orientation: "horizontal"
22+
size_hint: [1, .2]
23+
BoxLayout:
24+
orientation: "horizontal"
25+
BoxLayout:
26+
size_hint: None, None
27+
size: 300, 50
28+
orientation: "horizontal"
29+
Button:
30+
id: start_button
31+
text: "START"
32+
bold: True
33+
on_press: root.start()
34+
Button:
35+
id: stop_button
36+
text: "STOP"
37+
bold: True
38+
on_press: root.stop()

0 commit comments

Comments
 (0)