pangdudu-mamba 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +26 -0
- data/lib/b1trackerdata/b1_data_parser.rb +230 -0
- data/lib/b1trackerdata/openglviewer.rb +278 -0
- data/lib/graph.rb +194 -0
- data/lib/mamba.rb +324 -0
- data/tests/generate_testdata.rb +150 -0
- metadata +128 -0
data/README.rdoc
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
= Mamba
|
2
|
+
|
3
|
+
Hi girls and boys!
|
4
|
+
|
5
|
+
This is a Qt4 ruby tool for analysis of ART tracker data time series generated
|
6
|
+
by the ART motion capturing system.
|
7
|
+
|
8
|
+
== Installation
|
9
|
+
|
10
|
+
sudo gem install pangdudu-mamba --source=http://gems.github.com
|
11
|
+
|
12
|
+
== Usage
|
13
|
+
|
14
|
+
ruby mamba.rb
|
15
|
+
|
16
|
+
== Config
|
17
|
+
|
18
|
+
Is still hardcoded in the source files. :)
|
19
|
+
|
20
|
+
== Rule the universe!
|
21
|
+
|
22
|
+
Oh mighty gods of ruby, please make the GIL go away!
|
23
|
+
|
24
|
+
== License
|
25
|
+
|
26
|
+
GPL -> http://www.gnu.org/licenses/gpl.txt
|
@@ -0,0 +1,230 @@
|
|
1
|
+
=begin
|
2
|
+
This file e.g. the TrackerDataParser fetches the tracker data from the
|
3
|
+
database, and parses them according to the "SFB673TPB1 Annotation Manual".
|
4
|
+
=end
|
5
|
+
|
6
|
+
require "rubygems"
|
7
|
+
require "narray"
|
8
|
+
require "sequel"
|
9
|
+
require "rofl"
|
10
|
+
|
11
|
+
#connect to the database
|
12
|
+
DB = Sequel.connect(:adapter=>'mysql', :host=>'localhost', :database=>'database', :user=>'user', :password=>'password')
|
13
|
+
|
14
|
+
class TrackerDataParser
|
15
|
+
|
16
|
+
attr_accessor :data
|
17
|
+
|
18
|
+
def initialize trial,test=false
|
19
|
+
@trial = trial
|
20
|
+
tablename = "Tracker#{trial}"
|
21
|
+
tablename = "TrackerTest" if test
|
22
|
+
dlog "Trying to connect to table: #{tablename}"
|
23
|
+
#get our rows
|
24
|
+
@rows = DB[tablename.to_sym]
|
25
|
+
@highpass = 0.25 #for later filtering
|
26
|
+
dlog "Table has #{@rows.count} rows."
|
27
|
+
#processed data will end up here
|
28
|
+
@data = {:left => {}, :right => {}}
|
29
|
+
#do we know that one marker has been worn wrong?
|
30
|
+
@data[:left_twist] = false
|
31
|
+
@data[:right_twist] = false
|
32
|
+
#get and parse the tracker data
|
33
|
+
operate
|
34
|
+
end
|
35
|
+
|
36
|
+
#main controller function
|
37
|
+
def operate
|
38
|
+
@data[:reference] = (parsetrackerdata @rows.filter(:identifier => "Neck")).sort
|
39
|
+
dlog "Parsed neckdata count is #{@data[:reference].length}"
|
40
|
+
left_raw = (parsetrackerdata @rows.filter(:identifier => "Left_Hand")).sort
|
41
|
+
dlog "Left hand quality is #{(Float(left_raw.length)/Float(@data[:reference].length))*100}%"
|
42
|
+
right_raw = (parsetrackerdata @rows.filter(:identifier => "Right_Hand")).sort
|
43
|
+
dlog "Right hand quality is #{(Float(right_raw.length)/Float(@data[:reference].length))*100}%"
|
44
|
+
|
45
|
+
#sometimes people are wearing the tracker wrong, need to fix this
|
46
|
+
fix_marker_twist
|
47
|
+
|
48
|
+
#now parse the tracker data for both hands
|
49
|
+
left_raw.each do |timestamp,matrix|
|
50
|
+
d = getannotationdata timestamp,matrix,@data[:left_polarity]
|
51
|
+
@data[:left][timestamp] = d unless d.nil?
|
52
|
+
end
|
53
|
+
right_raw.each do |timestamp,matrix|
|
54
|
+
d = getannotationdata timestamp,matrix,@data[:right_polarity]
|
55
|
+
@data[:right][timestamp] = d unless d.nil?
|
56
|
+
end
|
57
|
+
#Debug and info
|
58
|
+
ilog "Statistics for trial #{@trial}:"
|
59
|
+
ilog " Left hand - parsed: #{@data[:left].length}"
|
60
|
+
ilog " Right hand - parsed: #{@data[:right].length}"
|
61
|
+
end
|
62
|
+
|
63
|
+
#method parsing tracking data from the db and loading it into an hash(organized by timestamp)
|
64
|
+
def parsetrackerdata trackerdata
|
65
|
+
parseddata = {}
|
66
|
+
trackerdata.each do |d|
|
67
|
+
m = NMatrix.float(4,4)
|
68
|
+
#that's how the coordinates would look if the matrix was transposed (now the bottom is 0,0,0,1)
|
69
|
+
m[0,0],m[0,1],m[0,2],m[0,3] = d[:m00], d[:m01], d[:m02], d[:m03]
|
70
|
+
m[1,0],m[1,1],m[1,2],m[1,3] = d[:m10], d[:m11], d[:m12], d[:m13]
|
71
|
+
m[2,0],m[2,1],m[2,2],m[2,3] = d[:m20], d[:m21], d[:m22], d[:m23]
|
72
|
+
m[3,0],m[3,1],m[3,2],m[3,3] = d[:m30], d[:m31], d[:m32], d[:m33]
|
73
|
+
m = m.transpose #matrix data in the db are transposed
|
74
|
+
parseddata[Float(d[:timestamp])] = m
|
75
|
+
end
|
76
|
+
return parseddata
|
77
|
+
end
|
78
|
+
|
79
|
+
def getannotationdata timestamp,matrix,polarity
|
80
|
+
annotationdata = {}
|
81
|
+
reference = @data[:reference][timestamp][1]
|
82
|
+
relative = reference.inverse * matrix * polarity
|
83
|
+
annotationdata[:timestamp] = timestamp
|
84
|
+
annotationdata[:reference] = reference
|
85
|
+
annotationdata[:data] = matrix
|
86
|
+
annotationdata[:relative] = relative
|
87
|
+
annotationdata[:boh] = getbackofhanddirection relative
|
88
|
+
annotationdata[:pd] = getpalmdirection relative
|
89
|
+
annotationdata[:wp] = getwristposition relative
|
90
|
+
annotationdata[:we] = getwristextend relative
|
91
|
+
return annotationdata
|
92
|
+
end
|
93
|
+
|
94
|
+
#function computing palm direction
|
95
|
+
def getpalmdirection data
|
96
|
+
#negative z seems to be the palm direction
|
97
|
+
#you should check the data in the visualization to be sure
|
98
|
+
x,y,z = (-1)*data[2,0],(-1)*data[2,1],(-1)*data[2,2]
|
99
|
+
result = []
|
100
|
+
result << "PAB" if z < -@highpass
|
101
|
+
result << "PDN" if y < -@highpass
|
102
|
+
result << "PTB" if z > @highpass
|
103
|
+
result << "PTL" if x < -@highpass
|
104
|
+
result << "PTR" if x > @highpass
|
105
|
+
result << "PUP" if y > @highpass
|
106
|
+
return result.join("/")
|
107
|
+
end
|
108
|
+
|
109
|
+
#function computing back of hand direction
|
110
|
+
def getbackofhanddirection data
|
111
|
+
#ok, it looks like the markers y axis is the boh direction
|
112
|
+
#you should check the data in the visualization to be sure
|
113
|
+
x,y,z = data[1,0],data[1,1],data[1,2]
|
114
|
+
result = []
|
115
|
+
result << "BAB" if z < -@highpass
|
116
|
+
result << "BDN" if y < -@highpass
|
117
|
+
result << "BTB" if z > @highpass
|
118
|
+
result << "BTL" if x < -@highpass
|
119
|
+
result << "BTR" if x > @highpass
|
120
|
+
result << "BUP" if y > @highpass
|
121
|
+
return result.join("/")
|
122
|
+
end
|
123
|
+
|
124
|
+
#function computing wrist extend
|
125
|
+
def getwristextend data
|
126
|
+
result = "aight!"
|
127
|
+
x,y,z = data[3,0],data[3,1],data[3,2]
|
128
|
+
#puts "wp: x '#{x}', y '#{y}', z '#{z}'"
|
129
|
+
zf = 100 #normalizing factor (100 = SH standard human)
|
130
|
+
xf = 100 #normalizing factor (100 = SH standard human)
|
131
|
+
x = x*xf
|
132
|
+
z = z*zf
|
133
|
+
r = Math.sqrt(x*x + z*z)
|
134
|
+
#ok, need to put real values in here (look at the annotationmanual)
|
135
|
+
result = "D-GTO" if r > 80 #greater than length of outstretched arm in front away
|
136
|
+
result = "D-KO" if r <= 80 #between knee and length of outstretched arm in front away
|
137
|
+
result = "D-EK" if r < 65 #between elbow and knee
|
138
|
+
result = "D-CE" if r < 45 #between body and elbows length away
|
139
|
+
result = "D-C" if r < 20 #in contact with body
|
140
|
+
#puts "extend is '#{result}'"
|
141
|
+
return result
|
142
|
+
end
|
143
|
+
|
144
|
+
#function computing wrist position
|
145
|
+
def getwristposition data
|
146
|
+
result = "aight!"
|
147
|
+
#negative z seems to be palm direction
|
148
|
+
#you should check the data in the visualization to be sure
|
149
|
+
x,y,z = data[3,0],data[3,1],data[3,2]
|
150
|
+
yf = 100
|
151
|
+
xf = 100
|
152
|
+
x = x*xf
|
153
|
+
y = y*yf
|
154
|
+
#puts "wp: x '#{x}', y '#{y}', z '#{z}'"
|
155
|
+
#will not be rock solid, so we go from outwards->inwards
|
156
|
+
#extreme periphery
|
157
|
+
eperiphery = "EP-"
|
158
|
+
eperiphery += "UP" if(y > 30 && x.abs <= 19)
|
159
|
+
eperiphery += "UL" if(y > 17 && x < -19)
|
160
|
+
eperiphery += "UR" if(y > 17 && x > 19)
|
161
|
+
eperiphery += "LT" if(y <= 17 && y > -17 && x < -31.5)
|
162
|
+
eperiphery += "RT" if(y <= 17 && y > -17 && x > 31.5)
|
163
|
+
eperiphery += "LW" if(y < -30 && x.abs <= 19)
|
164
|
+
eperiphery += "LL" if(y < -17 && x < -19)
|
165
|
+
eperiphery += "LR" if(y < -17 && x > 19)
|
166
|
+
result = eperiphery unless eperiphery.eql?("EP-")
|
167
|
+
#periphery
|
168
|
+
if (x.abs <= 31.5 && y.abs <= 30)
|
169
|
+
periphery = "P-"
|
170
|
+
periphery += "UP" if(y > 17 && x.abs <= 13)
|
171
|
+
periphery += "UL" if(y > 9 && x < -13)
|
172
|
+
periphery += "UR" if(y > 9 && x > 13)
|
173
|
+
periphery += "LT" if(y <= 9 && y > -10 && x < -21)
|
174
|
+
periphery += "RT" if(y <= 9 && y > -10 && x > 21)
|
175
|
+
periphery += "LW" if(y <= -10 && y > -30 && x.abs <= 13)
|
176
|
+
periphery += "LL" if(y <= -10 && y > -30 && x < -13)
|
177
|
+
periphery += "LR" if(y <= -10 && y > -30 && x > 13)
|
178
|
+
result = periphery unless periphery.eql?("P-")
|
179
|
+
end
|
180
|
+
#center
|
181
|
+
if (x.abs <= 21 && y.abs <= 17)
|
182
|
+
center = "C-"
|
183
|
+
center += "UP" if(y > 9 && x.abs <= 10)
|
184
|
+
center += "UL" if(y > 4.5 && x < -10)
|
185
|
+
center += "UR" if(y > 4.5 && x > 10)
|
186
|
+
center += "LT" if(y <= 4.5 && y > -7 && x < -13)
|
187
|
+
center += "RT" if(y <= 4.5 && y > -7 && x > 13)
|
188
|
+
center += "LW" if(y <= -7 && y > -17 && x.abs <= 10)
|
189
|
+
center += "LL" if(y <= -7 && y > -17 && x < -10)
|
190
|
+
center += "LR" if(y <= -7 && y > -17 && x > 10)
|
191
|
+
result = center unless center.eql?("C-")
|
192
|
+
end
|
193
|
+
#center-center
|
194
|
+
if (x.abs <= 13 && y.abs <= 9)
|
195
|
+
result = "CC"
|
196
|
+
end
|
197
|
+
#puts "result #{result}"
|
198
|
+
return result
|
199
|
+
end
|
200
|
+
|
201
|
+
#method to fix the direction of the marker, if it has been worn wrong
|
202
|
+
def fix_marker_twist
|
203
|
+
#left hand
|
204
|
+
@data[:left_polarity] = NMatrix.float(4,4).unit
|
205
|
+
if @data[:left_twist]
|
206
|
+
p = NMatrix.float(4,4).unit
|
207
|
+
#if y direction of tracker is WRONG we need to rotate around z axis by 180 deg
|
208
|
+
p[0,0], p[0,1], p[1,0], p[1,1] = Math.cos(Math::PI),-Math.sin(Math::PI),Math.sin(Math::PI),Math.cos(Math::PI)
|
209
|
+
ilog "Will multiply left hand with this polarity matrix: #{p.inspect}"
|
210
|
+
@data[:left_polarity] = p
|
211
|
+
end
|
212
|
+
#and the right hand
|
213
|
+
@data[:right_polarity] = NMatrix.float(4,4).unit
|
214
|
+
if @data[:right_twist]
|
215
|
+
p = NMatrix.float(4,4).unit
|
216
|
+
#if y direction of tracker is WRONG we need to rotate around z axis by 180 deg
|
217
|
+
p[0,0], p[0,1], p[1,0], p[1,1] = Math.cos(Math::PI),-Math.sin(Math::PI),Math.sin(Math::PI),Math.cos(Math::PI)
|
218
|
+
ilog "Will multiply right hand with this polarity matrix: #{p.inspect}"
|
219
|
+
@data[:right_polarity] = p
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
#for testing
|
225
|
+
if false
|
226
|
+
tdp = TrackerDataParser.new "V06",true #will select table TrackerTest
|
227
|
+
require "openglviewer"
|
228
|
+
ogv = OpenGlViewer.new tdp.data
|
229
|
+
ogv.run
|
230
|
+
end
|
@@ -0,0 +1,278 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'opengl'
|
3
|
+
require 'rational'
|
4
|
+
require 'narray'
|
5
|
+
require 'rofl'
|
6
|
+
#include Gl,Glu,Glut
|
7
|
+
|
8
|
+
class OpenGlViewer
|
9
|
+
|
10
|
+
attr_accessor :currenttimestamp,:timestamps,:pause
|
11
|
+
|
12
|
+
STDOUT.sync = TRUE
|
13
|
+
POS = [0, 0, 0, 0.0]
|
14
|
+
|
15
|
+
def initialize data
|
16
|
+
@data = data
|
17
|
+
@base = NMatrix.float(4,4).unit
|
18
|
+
@matrices = {}
|
19
|
+
@matrices[:base] = @base
|
20
|
+
@currenttimestamp = 0.0
|
21
|
+
@current = 0
|
22
|
+
@timestamps = {}
|
23
|
+
@pause = true
|
24
|
+
@forward = true
|
25
|
+
@leftboh = "none"
|
26
|
+
@rightboh = "none"
|
27
|
+
filltimestamps
|
28
|
+
glShadeModel(GL::SMOOTH)
|
29
|
+
glNormal(0.0, 0.0, 1.0)
|
30
|
+
glLightfv(GL::LIGHT0, GL::POSITION, POS)
|
31
|
+
glEnable(GL::CULL_FACE)
|
32
|
+
glEnable(GL::LIGHTING)
|
33
|
+
glEnable(GL::LIGHT0)
|
34
|
+
glEnable(GL::DEPTH_TEST)
|
35
|
+
glEnable(GL::LINE_SMOOTH)
|
36
|
+
glEnable(GL::POINT_SMOOTH)
|
37
|
+
glClearColor(0.0, 0.0, 0.0, 0.0)
|
38
|
+
end
|
39
|
+
|
40
|
+
def filltimestamps
|
41
|
+
i = 0
|
42
|
+
@data[:reference].each do |timestamp,value|
|
43
|
+
@timestamps[i] = timestamp
|
44
|
+
i += 1
|
45
|
+
end
|
46
|
+
ilog "Found '#{@timestamps.count}'"
|
47
|
+
ilog "Will now try to display '#{@data[:left].count}' left and '#{@data[:right].count}' right hand values"
|
48
|
+
end
|
49
|
+
|
50
|
+
def fillmatrices
|
51
|
+
unless @data[:left][@currenttimestamp].nil?
|
52
|
+
#@matrices[:reference] = @data[:left][@currenttimestamp][:reference]
|
53
|
+
#@matrices[:left_hand_data] = @data[:left][@currenttimestamp][:data]
|
54
|
+
@matrices[:left_hand_relative] = @data[:left][@currenttimestamp][:relative]
|
55
|
+
@leftboh = @data[:left][@currenttimestamp][:boh]
|
56
|
+
end
|
57
|
+
unless @data[:right][@currenttimestamp].nil?
|
58
|
+
#@matrices[:reference] = @data[:right][@currenttimestamp][:reference]
|
59
|
+
#@matrices[:right_hand_data] = @data[:right][@currenttimestamp][:data]
|
60
|
+
@matrices[:right_hand_relative] = @data[:right][@currenttimestamp][:relative]
|
61
|
+
@rightboh = @data[:right][@currenttimestamp][:boh]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def setcurrenttimestamp
|
66
|
+
unless @pause
|
67
|
+
if @forward
|
68
|
+
@current += 1
|
69
|
+
@currenttimestamp = @timestamps[@current]
|
70
|
+
end
|
71
|
+
if !@forward
|
72
|
+
@current -= 1 unless (@current.eql? 0)
|
73
|
+
@currenttimestamp = @timestamps[@current]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# gl display method
|
79
|
+
def display
|
80
|
+
fillmatrices
|
81
|
+
setcurrenttimestamp
|
82
|
+
fps_control(60)
|
83
|
+
glClear(GL::COLOR_BUFFER_BIT | GL::DEPTH_BUFFER_BIT)
|
84
|
+
glColor(0.0, 0.0, 0.0)
|
85
|
+
glPushMatrix()
|
86
|
+
glRotate(@view_rotx, 1.0, 0.0, 0.0)
|
87
|
+
glRotate(@view_roty, 0.0, 1.0, 0.0)
|
88
|
+
glRotate(@view_rotz, 0.0, 0.0, 1.0)
|
89
|
+
@matrices.each { |k,v| draw_object k,v }
|
90
|
+
displayinfo
|
91
|
+
glPopMatrix()
|
92
|
+
glutSwapBuffers()
|
93
|
+
@frames = 0 if not defined? @frames
|
94
|
+
@t0 = 0 if not defined? @t0
|
95
|
+
@frames += 1
|
96
|
+
t = GLUT.Get(GLUT::ELAPSED_TIME)
|
97
|
+
if t - @t0 >= 5000
|
98
|
+
seconds = (t - @t0) / 1000.0
|
99
|
+
fps = @frames / seconds
|
100
|
+
@t0, @frames = t, 0
|
101
|
+
exit if defined? @autoexit and t >= 999.0 * @autoexit
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def drawOneLine x1,y1,z1,x2,y2,z2
|
106
|
+
glBegin(GL_LINES)
|
107
|
+
glVertex(x1,y1,z1)
|
108
|
+
glVertex(x2,y2,z2)
|
109
|
+
glEnd()
|
110
|
+
end
|
111
|
+
|
112
|
+
def drawLineWithText text,scale,x,y,z
|
113
|
+
scale2 = scale*scale
|
114
|
+
glPushMatrix()
|
115
|
+
drawOneLine(0,0,0,scale*x,scale*y,scale*z)
|
116
|
+
glTranslate((scale*x),(scale*y),(scale*z))
|
117
|
+
glScale(1.0/(scale2),1.0/(scale2),1.0/(scale2))
|
118
|
+
text.each_byte { |x| glutStrokeCharacter(GLUT_STROKE_ROMAN, x) }
|
119
|
+
glScale(scale2,scale2,scale2)
|
120
|
+
glTranslate(-(scale*x),-(scale*y),-(scale*z))
|
121
|
+
glPopMatrix()
|
122
|
+
end
|
123
|
+
|
124
|
+
# draw an imd and its childs
|
125
|
+
def draw_object key,matrix
|
126
|
+
glPushMatrix()
|
127
|
+
scale = 10
|
128
|
+
translation_scale = 2
|
129
|
+
glTranslate(matrix[3,0]*scale*translation_scale,matrix[3,1]*scale*translation_scale,matrix[3,2]*scale*translation_scale)
|
130
|
+
glColor(1,0,0)
|
131
|
+
drawLineWithText "#{key} x",scale,matrix[0,0],matrix[0,1],matrix[0,2]
|
132
|
+
glColor(0,1,0)
|
133
|
+
drawLineWithText "#{key} y",scale,matrix[1,0],matrix[1,1],matrix[1,2]
|
134
|
+
glColor(0,0,1)
|
135
|
+
drawLineWithText "#{key} z",scale,matrix[2,0],matrix[2,1],matrix[2,2]
|
136
|
+
glTranslate(-matrix[3,0]*scale*translation_scale,-matrix[3,1]*scale*translation_scale,-matrix[3,2]*scale*translation_scale)
|
137
|
+
glPopMatrix()
|
138
|
+
end
|
139
|
+
|
140
|
+
def displayinfo
|
141
|
+
info = "Information:"
|
142
|
+
info += " timestamp: #{(@timestamps[@current]).to_s}"
|
143
|
+
info += " PAUSED" if @pause
|
144
|
+
info += " FORWARD" if (!@pause && @forward)
|
145
|
+
info += " REVERSE" if (!@pause && !@forward)
|
146
|
+
info += " BOH left:#{@leftboh} right:#{@rightboh}"
|
147
|
+
scale = 10
|
148
|
+
scale2 = scale*scale
|
149
|
+
glPushMatrix()
|
150
|
+
glTranslate((scale),(scale),(scale))
|
151
|
+
glScale(1.0/(scale2),1.0/(scale2),1.0/(scale2))
|
152
|
+
info.each_byte { |x| glutStrokeCharacter(GLUT_STROKE_ROMAN, x) }
|
153
|
+
glScale(scale2,scale2,scale2)
|
154
|
+
glTranslate(-(scale),-(scale),-(scale))
|
155
|
+
glPopMatrix()
|
156
|
+
end
|
157
|
+
|
158
|
+
def fps_control(fps)
|
159
|
+
t = GLUT.Get(GLUT::ELAPSED_TIME)
|
160
|
+
@t0_idle = t if !defined? @t0_idle
|
161
|
+
time_to_sleep = (1000./fps) - (t - @t0_idle)
|
162
|
+
sleep(time_to_sleep.to_f/1000) if time_to_sleep > 0
|
163
|
+
@t0_idle = t
|
164
|
+
end
|
165
|
+
|
166
|
+
# gets called when window is resized etc.
|
167
|
+
def reshape width, height
|
168
|
+
h = height.to_f / width.to_f
|
169
|
+
glViewport(0, 0, width, height)
|
170
|
+
glMatrixMode(GL::PROJECTION)
|
171
|
+
glLoadIdentity()
|
172
|
+
glFrustum(-1.0, 1.0, -h, h, 1.0, 1000.0)
|
173
|
+
glMatrixMode(GL::MODELVIEW)
|
174
|
+
setViewpoint
|
175
|
+
end
|
176
|
+
|
177
|
+
def idle
|
178
|
+
GLUT.PostRedisplay()
|
179
|
+
end
|
180
|
+
|
181
|
+
def setViewpoint
|
182
|
+
glMatrixMode(GL_MODELVIEW)
|
183
|
+
glLoadIdentity()
|
184
|
+
gluLookAt(@eyex, 0, @znear, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
|
185
|
+
glutPostRedisplay()
|
186
|
+
end
|
187
|
+
|
188
|
+
# Change view angle
|
189
|
+
def special (k, x, y)
|
190
|
+
case k
|
191
|
+
when GLUT::KEY_UP
|
192
|
+
@view_rotx += 5.0
|
193
|
+
when GLUT::KEY_DOWN
|
194
|
+
@view_rotx -= 5.0
|
195
|
+
when GLUT::KEY_LEFT
|
196
|
+
@view_roty += 5.0
|
197
|
+
when GLUT::KEY_RIGHT
|
198
|
+
@view_roty -= 5.0
|
199
|
+
end
|
200
|
+
GLUT.PostRedisplay()
|
201
|
+
end
|
202
|
+
|
203
|
+
# mouse ;)
|
204
|
+
def mouse button, state, x, y
|
205
|
+
changed = false
|
206
|
+
case button
|
207
|
+
when 3 then @eyex -= 5; changed = true;
|
208
|
+
when 4 then @eyex += 5; changed = true
|
209
|
+
end
|
210
|
+
setViewpoint if changed
|
211
|
+
@mouse = state
|
212
|
+
@x0, @y0 = x, y
|
213
|
+
end
|
214
|
+
|
215
|
+
# motion control
|
216
|
+
def motion x, y
|
217
|
+
if @mouse == GLUT::DOWN then
|
218
|
+
@view_rotz += @x0 - x
|
219
|
+
@view_roty += @y0 - y
|
220
|
+
setViewpoint
|
221
|
+
end
|
222
|
+
@x0, @y0 = x, y
|
223
|
+
end
|
224
|
+
|
225
|
+
#keyboard control handling
|
226
|
+
def keyboard(key, x, y)
|
227
|
+
case (key)
|
228
|
+
when ?s
|
229
|
+
@znear = @znear - 1
|
230
|
+
when ?w
|
231
|
+
@znear = @znear + 1
|
232
|
+
when ?a
|
233
|
+
@eyex = @eyex - 1
|
234
|
+
when ?d
|
235
|
+
@eyex = @eyex + 1
|
236
|
+
when ?f
|
237
|
+
@forward = true
|
238
|
+
when ?r
|
239
|
+
@forward = false
|
240
|
+
when ?p
|
241
|
+
togglepause
|
242
|
+
when 27 # Escape
|
243
|
+
exit
|
244
|
+
end
|
245
|
+
setViewpoint
|
246
|
+
end
|
247
|
+
|
248
|
+
def togglepause
|
249
|
+
@pause = !@pause
|
250
|
+
puts "Toggle pause now '#{@pause}'"
|
251
|
+
end
|
252
|
+
|
253
|
+
def visible(vis)
|
254
|
+
GLUT.IdleFunc((vis == GLUT::VISIBLE ? method(:idle).to_proc : nil))
|
255
|
+
end
|
256
|
+
#the loop
|
257
|
+
def run
|
258
|
+
@znear = 30
|
259
|
+
@eyex = 0
|
260
|
+
@view_rotx, @view_roty, @view_rotz = 0, 0, 0
|
261
|
+
glutInit
|
262
|
+
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB)
|
263
|
+
glutInitWindowSize(640, 480)
|
264
|
+
glutInitWindowPosition(0, 0)
|
265
|
+
glutCreateWindow($0)
|
266
|
+
glutDisplayFunc(method(:display).to_proc)
|
267
|
+
glutReshapeFunc(method(:reshape).to_proc)
|
268
|
+
glutSpecialFunc(method(:special).to_proc)
|
269
|
+
glutKeyboardFunc(method(:keyboard).to_proc)
|
270
|
+
glutMouseFunc(method(:mouse).to_proc)
|
271
|
+
glutMotionFunc(method(:motion).to_proc)
|
272
|
+
glutVisibilityFunc(method(:visible).to_proc)
|
273
|
+
glShadeModel(GL::SMOOTH)
|
274
|
+
glNormal(0.0, 0.0, 1.0)
|
275
|
+
glClearColor(0.0, 0.0, 0.0, 0.0)
|
276
|
+
glutMainLoop()
|
277
|
+
end
|
278
|
+
end
|
data/lib/graph.rb
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
=begin
|
2
|
+
This class is a little B1 specific. It implements the graph we'll later
|
3
|
+
visualize.
|
4
|
+
=end
|
5
|
+
|
6
|
+
require "rubygems"
|
7
|
+
require "narray"
|
8
|
+
require "Qt4"
|
9
|
+
require "rofl"
|
10
|
+
require "b1trackerdata/b1_data_parser"
|
11
|
+
|
12
|
+
class Graph
|
13
|
+
|
14
|
+
attr_accessor :points,:deltas
|
15
|
+
|
16
|
+
def initialize trial,deltas,hfrac,test=false
|
17
|
+
#deltas we will compute
|
18
|
+
@deltas = deltas
|
19
|
+
ilog "Will compute deltas for #{@deltas.inspect}."
|
20
|
+
#in case the deltas are to small/big change this factor
|
21
|
+
@delta_factor = 256
|
22
|
+
#in case the angles are to small/big change this factor
|
23
|
+
@euler_factor = hfrac
|
24
|
+
#this factor governs how many pixels the points are away from another
|
25
|
+
@x_factor = 4
|
26
|
+
#get the data from the database
|
27
|
+
parser = TrackerDataParser.new trial,test
|
28
|
+
@data = parser.data
|
29
|
+
#generate unique array with all occuring timestamps and prepare @values
|
30
|
+
@timestamps = build_timestamps
|
31
|
+
#fill values (deltas,etc.)
|
32
|
+
@previous = {:left => NMatrix.float(4,4), :right => NMatrix.float(4,4)}
|
33
|
+
fill_values
|
34
|
+
#build the points array (this will be used later on to draw the graph)
|
35
|
+
@points = build_points
|
36
|
+
end
|
37
|
+
|
38
|
+
#build an array with all occuring timestamps
|
39
|
+
def build_timestamps
|
40
|
+
@values = {}
|
41
|
+
tss = []
|
42
|
+
@data[:reference].each { |t,v| tss << t }
|
43
|
+
@data[:left].each { |t,v| tss << t }
|
44
|
+
@data[:right].each { |t,v| tss << t }
|
45
|
+
dlog "tss count: #{tss.length}"
|
46
|
+
timestamps = (tss.uniq).sort
|
47
|
+
dlog "Unique timestamps: #{timestamps.length}"
|
48
|
+
dlog "Unique timestamps first: #{timestamps.first}"
|
49
|
+
#now preparing @values
|
50
|
+
timestamps.each do |t|
|
51
|
+
@values[t] = {:left => {}, :right => {}}
|
52
|
+
end
|
53
|
+
return timestamps
|
54
|
+
end
|
55
|
+
|
56
|
+
#fill values with parsed data
|
57
|
+
def fill_values
|
58
|
+
[:left,:right].each do |hand|
|
59
|
+
dlog "Data for #{hand.inspect} has #{@data[hand].length} entries."
|
60
|
+
#it's crucial to do sort here, otherwise the timestamps are mixed up
|
61
|
+
@data[hand].sort.each do |t,v|
|
62
|
+
wlog "Empty or nil value hash!" if v.nil? or v.empty?
|
63
|
+
#we have tracker data
|
64
|
+
v[:blackout] = false
|
65
|
+
#will only happen the first time
|
66
|
+
@previous[hand] = v[:relative] if @previous[hand].abs.sum == 0.0
|
67
|
+
#compute euler angles
|
68
|
+
v[:euler] = compute_euler_angles(v)
|
69
|
+
#compute deltas for plotting
|
70
|
+
v[:deltas] = compute_deltas(hand,v)
|
71
|
+
#for next delta computation
|
72
|
+
@previous[hand] = v[:relative]
|
73
|
+
#put values into @values
|
74
|
+
@values[t][hand] = v
|
75
|
+
end
|
76
|
+
end
|
77
|
+
dlog "Processed #{@values.length} values."
|
78
|
+
end
|
79
|
+
|
80
|
+
#compute euler angles for plotting
|
81
|
+
#http://en.wikipedia.org/wiki/Euler_angles
|
82
|
+
def compute_euler_angles values
|
83
|
+
#we'll put the angles in here
|
84
|
+
e = {}
|
85
|
+
#get the hand matrix
|
86
|
+
m = values[:relative]
|
87
|
+
#alpha = atan2(-Z2, Z1)
|
88
|
+
e[:alpha] = { :value => Math.atan2(-1.0*m[2,1],m[2,0]) }
|
89
|
+
#beta = atan2(Z3, sqrt(Z1^2+Z2^2))
|
90
|
+
e[:beta] = { :value => Math.atan2(m[2,2],Math.sqrt(m[2,0]**2+m[2,1]**2)) }
|
91
|
+
#gamma = -atan2(Y3,-X3) if Z3 < 0
|
92
|
+
e[:gamma] = { :value => -1.0*Math.atan2(m[1,2],-1.0*m[0,2]) } if m[2,2] <= 0.0
|
93
|
+
#gamma = atan2(Y3,X3) if Z3 > 0
|
94
|
+
e[:gamma] = { :value => Math.atan2(m[1,2],m[0,2]) } if m[2,2] > 0.0
|
95
|
+
return e
|
96
|
+
end
|
97
|
+
|
98
|
+
#compute deltas for plotting
|
99
|
+
def compute_deltas hand,values,t=0
|
100
|
+
#we'll put the deltas in here
|
101
|
+
ds = {}
|
102
|
+
#get the matrix
|
103
|
+
m = values[:relative]
|
104
|
+
#to reduce computation time we only compute what's needed
|
105
|
+
if @deltas.include? :simple
|
106
|
+
#compute simple delta over the complete 4x4 matrix
|
107
|
+
value = (m-@previous[hand]).abs.sum
|
108
|
+
ds[:simple] = { :value => value }
|
109
|
+
end
|
110
|
+
if @deltas.include? :pos
|
111
|
+
#compute simple delta over the position vector
|
112
|
+
value = (m[3,0..2]-@previous[hand][3,0..2]).abs.sum
|
113
|
+
ds[:pos] = { :value => value }
|
114
|
+
end
|
115
|
+
if @deltas.include? :rot
|
116
|
+
#compute simple delta over the 3x3 rotation matrix
|
117
|
+
value = (m[0..2,0..2]-@previous[hand][0..2,0..2]).abs.sum
|
118
|
+
ds[:rot] = { :value => value }
|
119
|
+
end
|
120
|
+
if @deltas.include? :xrot
|
121
|
+
#compute simple delta over the x-rotation axis vector
|
122
|
+
value = (m[0,0..2]-@previous[hand][0,0..2]).abs.sum
|
123
|
+
ds[:xrot] = { :value => value }
|
124
|
+
end
|
125
|
+
if @deltas.include? :yrot
|
126
|
+
#compute simple delta over the y-rotation axis vector
|
127
|
+
value = (m[1,0..2]-@previous[hand][1,0..2]).abs.sum
|
128
|
+
ds[:yrot] = { :value => value }
|
129
|
+
end
|
130
|
+
if @deltas.include? :zrot
|
131
|
+
#compute simple delta over the z-rotation axis vector
|
132
|
+
value = (m[2,0..2]-@previous[hand][2,0..2]).abs.sum
|
133
|
+
ds[:zrot] = { :value => value }
|
134
|
+
end
|
135
|
+
#and return the computed deltas
|
136
|
+
return ds
|
137
|
+
end
|
138
|
+
|
139
|
+
#build the points array we'll use later on to draw the graph
|
140
|
+
def build_points
|
141
|
+
@timestamps.each_index do |i|
|
142
|
+
ts = @timestamps[i]
|
143
|
+
#do the following for both hands
|
144
|
+
[:left,:right].each do |h|
|
145
|
+
#if tracker data are missing we substitute
|
146
|
+
if @values.has_key? ts
|
147
|
+
@values[ts][h] = get_missing_values if @values[ts][h].empty?
|
148
|
+
end
|
149
|
+
#set index and x position
|
150
|
+
@values[ts][h][:i] = i
|
151
|
+
x = i*@x_factor
|
152
|
+
@values[ts][h][:x] = x
|
153
|
+
#now we create a Qt::PointF.new(x,y) for each euler angle
|
154
|
+
@values[ts][h][:euler].each do |name,a|
|
155
|
+
#create Point, mirror hands on x axis and apply y-scaling
|
156
|
+
y = 0
|
157
|
+
unless @values[ts][h][:blackout]
|
158
|
+
y += @euler_factor-a[:value]*@euler_factor/Math::PI
|
159
|
+
y *= -1.0 if h.eql? :left
|
160
|
+
end
|
161
|
+
#and add the point to the euler values
|
162
|
+
@values[ts][h][:euler][name][:point] = Qt::PointF.new(x,y)
|
163
|
+
end
|
164
|
+
#now we create a Qt::PointF.new(x,y) for every delta type
|
165
|
+
@values[ts][h][:deltas].each do |type,delta|
|
166
|
+
#create Point, mirror hands on x axis and apply y-scaling
|
167
|
+
y = delta[:value]*@delta_factor
|
168
|
+
y *= -1.0 if h.eql? :left
|
169
|
+
#and add the point to the delta values
|
170
|
+
@values[ts][h][:deltas][type][:point] = Qt::PointF.new(x,y)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
dlog "Processed #{@values.length} timestamps."
|
175
|
+
points = @values.sort
|
176
|
+
dlog "points array has #{points.length} entries."
|
177
|
+
return points
|
178
|
+
end
|
179
|
+
|
180
|
+
#in case values are missing for timestamps (no tracker data), we'll supply values
|
181
|
+
def get_missing_values
|
182
|
+
#we'll put the deltas in here
|
183
|
+
missing_deltas = {}
|
184
|
+
#put 0.0 to deltas
|
185
|
+
@deltas.each { |d| missing_deltas[d] = { :value => 0.0 } }
|
186
|
+
#we'll put the euler angles in here
|
187
|
+
missing_euler = {}
|
188
|
+
#put 0.0 to euler angles
|
189
|
+
[:alpha,:beta,:gamma].each { |a| missing_euler[a] = { :value => 0.0 } }
|
190
|
+
#and build the values array
|
191
|
+
v = { :deltas => missing_deltas, :euler => missing_euler, :blackout => true }
|
192
|
+
return v
|
193
|
+
end
|
194
|
+
end
|
data/lib/mamba.rb
ADDED
@@ -0,0 +1,324 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "rofl"
|
3
|
+
require "Qt4"
|
4
|
+
require "graph"
|
5
|
+
#require "unprof"
|
6
|
+
|
7
|
+
#oky, this will become a visualizer for large data sequences
|
8
|
+
|
9
|
+
class Gui < Qt::Widget
|
10
|
+
|
11
|
+
attr_accessor :width,:height
|
12
|
+
attr_accessor :x_delta,:mousex,:mousey
|
13
|
+
|
14
|
+
def initialize(width,height,parent = nil)
|
15
|
+
super()
|
16
|
+
@width,@height = width,height
|
17
|
+
resize(@width,@height)
|
18
|
+
@lfw = 16 # legend font width
|
19
|
+
@tfw = 15 # timestamp font width
|
20
|
+
@pfw = 11 # predicate font width
|
21
|
+
#text will not be filled
|
22
|
+
#@brush = Qt::NoBrush
|
23
|
+
#text will be filled
|
24
|
+
@brush = Qt::Brush.new Qt::Color.new 0, 0, 0
|
25
|
+
@current_color = 0
|
26
|
+
#generate these deltas
|
27
|
+
deltas = []
|
28
|
+
#display these predicates
|
29
|
+
@predicates = [:boh, :pd, :wp, :we]
|
30
|
+
#graph with the data we want to visualize
|
31
|
+
#@graph = Graph.new "V06",deltas,@height/12
|
32
|
+
@graph = Graph.new "",deltas,@height/12,true #will build graph from table 'TrackerTest'
|
33
|
+
#max movement speed
|
34
|
+
@speedlimit = 20
|
35
|
+
#setup the rest
|
36
|
+
setup
|
37
|
+
#method doing the x scrolling for us
|
38
|
+
dlog "Initialized, starting active scroll loop."
|
39
|
+
reactive_scroll
|
40
|
+
end
|
41
|
+
|
42
|
+
#setup all the stuff we need to display
|
43
|
+
def setup
|
44
|
+
#legend painter path
|
45
|
+
@legend = build_legend_path
|
46
|
+
#text painter path
|
47
|
+
@text = build_text_path
|
48
|
+
#graph painter paths
|
49
|
+
@graphs = setup_graph_paths
|
50
|
+
#now some gui specific stuff
|
51
|
+
setPalette(Qt::Palette.new(Qt::Color.new(250, 250, 250)))
|
52
|
+
setAutoFillBackground(true)
|
53
|
+
#cursor settings
|
54
|
+
@cur1 = self.cursor
|
55
|
+
@cur2 = Qt::Cursor.new(Qt::PointingHandCursor)
|
56
|
+
self.mouseTracking = true
|
57
|
+
#used for scrolling objects to the left and right
|
58
|
+
@xdelta,@xgoal,@xspeed = 0.0,0.0,0.0
|
59
|
+
end
|
60
|
+
|
61
|
+
#build painter path for the legend
|
62
|
+
def build_legend_path
|
63
|
+
p = Qt::PainterPath.new
|
64
|
+
p.moveTo 0,0
|
65
|
+
font = Qt::Font.new "Arial", @lfw
|
66
|
+
p.addText @lfw,1.5*@lfw, font, "LEFT: euler angles"
|
67
|
+
p.addText @lfw,2*@height/12, font, "RIGHT: euler angles"
|
68
|
+
p.addText @lfw,5*@height/12, font, "LEFT: data"
|
69
|
+
p.addText @lfw,7*@height/12, font, "RIGHT: data"
|
70
|
+
p.addText @lfw,10*@height/12-@lfw, font, "LEFT: blackout"
|
71
|
+
p.addText @lfw,10*@height/12+@lfw, font, "RIGHT: blackout"
|
72
|
+
p.addText @lfw,11*@height/12-@lfw/2, font, "timestamps"
|
73
|
+
dlog "Finished legend path setup."
|
74
|
+
return p
|
75
|
+
end
|
76
|
+
|
77
|
+
#build painter path for the text data
|
78
|
+
def build_text_path
|
79
|
+
#y to timestamps
|
80
|
+
ty = 11*@height/12
|
81
|
+
#back to left hand data
|
82
|
+
ypl = -5*@height/12-2*@lfw
|
83
|
+
#and back to right hand data
|
84
|
+
ypr = -3*@height/12-2*@lfw
|
85
|
+
tfont = Qt::Font.new "Arial", @tfw
|
86
|
+
pfont = Qt::Font.new "Arial", @pfw
|
87
|
+
every = 25 #display text every ... frames
|
88
|
+
current = 0
|
89
|
+
p = Qt::PainterPath.new
|
90
|
+
p.moveTo 0,0
|
91
|
+
@graph.points.each do |ts,hands|
|
92
|
+
if current >= every
|
93
|
+
p.addText hands[:left][:x],@lfw, tfont, "#{ts}"
|
94
|
+
#display the predicates
|
95
|
+
@predicates.each_index do |i|
|
96
|
+
p.addText hands[:left][:x], ypl+i*@lfw, pfont, "#{hands[:left][@predicates[i]]}"
|
97
|
+
p.addText hands[:right][:x], ypr+i*@lfw, pfont, "#{hands[:right][@predicates[i]]}"
|
98
|
+
end
|
99
|
+
current = 0
|
100
|
+
else
|
101
|
+
current += 1
|
102
|
+
end
|
103
|
+
end
|
104
|
+
dlog "Finished subtitles path setup."
|
105
|
+
return { :y => ty, :path => p }
|
106
|
+
end
|
107
|
+
|
108
|
+
#setup the graph paths we want to paint
|
109
|
+
def setup_graph_paths
|
110
|
+
dlog "Starting graph path setup."
|
111
|
+
graph_paths = {}
|
112
|
+
#build following graphs for both hands
|
113
|
+
[:left,:right].each do |hand|
|
114
|
+
#build painter paths for blackout graph
|
115
|
+
blackout = build_blackout_path(hand)
|
116
|
+
graph_paths[blackout[:name]] = blackout
|
117
|
+
dlog "Finished blackout path setup. Y: #{blackout[:y]}"
|
118
|
+
#build painter paths for euler angles
|
119
|
+
[:alpha,:beta,:gamma].each do |angle|
|
120
|
+
euler = build_euler_path(hand,angle)
|
121
|
+
graph_paths[euler[:name]] = euler
|
122
|
+
dlog "Finished #{euler[:name]} path setup. Y: #{euler[:y]}"
|
123
|
+
end
|
124
|
+
#build painter paths for deltas
|
125
|
+
@graph.deltas.each do |d|
|
126
|
+
delta = build_delta_path(hand,d)
|
127
|
+
graph_paths[delta[:name]] = delta
|
128
|
+
dlog "Finished #{delta[:name]} path setup. Y: #{delta[:y]}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
dlog "Finished graph path setup."
|
132
|
+
return graph_paths
|
133
|
+
end
|
134
|
+
|
135
|
+
#build painter path for blackout graph
|
136
|
+
def build_blackout_path hand
|
137
|
+
name = "blackout_#{hand}".to_sym
|
138
|
+
color = Qt::Color.new(0, 0, 64) if hand.eql? :left
|
139
|
+
color = Qt::Color.new(64, 0, 0) if hand.eql? :right
|
140
|
+
#y-translation of the graph, we only need y
|
141
|
+
ty = 10*@height/12+@lfw/2
|
142
|
+
ty -= @lfw if hand.eql? :left
|
143
|
+
ty += @lfw if hand.eql? :right
|
144
|
+
path = Qt::PainterPath.new
|
145
|
+
path.moveTo 0,0
|
146
|
+
#now we construct the path
|
147
|
+
@graph.points.each do |ts,v|
|
148
|
+
unless v[hand][:blackout]
|
149
|
+
path.moveTo v[hand][:x],0
|
150
|
+
else
|
151
|
+
path.lineTo v[hand][:x],0
|
152
|
+
end
|
153
|
+
end
|
154
|
+
#and return the values
|
155
|
+
return { :y => ty, :name => name, :path => path, :color => color, :width => 2.5 }
|
156
|
+
end
|
157
|
+
|
158
|
+
#build painter path for euler graph
|
159
|
+
def build_euler_path hand,angle
|
160
|
+
name = "#{angle}_#{hand}".to_sym
|
161
|
+
color = Qt::Color.new(255, 0, 0) if angle.eql? :alpha
|
162
|
+
color = Qt::Color.new(0, 255, 0) if angle.eql? :gamma
|
163
|
+
color = Qt::Color.new(0, 0, 255) if angle.eql? :beta
|
164
|
+
#y-translation of the graph, we only need y
|
165
|
+
ty = @height/6
|
166
|
+
path = Qt::PainterPath.new
|
167
|
+
path.moveTo 0,0
|
168
|
+
#now we construct the path
|
169
|
+
@graph.points.each do |ts,v|
|
170
|
+
path.lineTo v[hand][:euler][angle][:point]
|
171
|
+
end
|
172
|
+
#and return the values
|
173
|
+
return { :y => ty, :name => name, :path => path, :color => color }
|
174
|
+
end
|
175
|
+
|
176
|
+
#build painter path for delta graph
|
177
|
+
def build_delta_path hand,delta
|
178
|
+
name = "#{delta}_#{hand}".to_sym
|
179
|
+
color = Qt::Color.new(64, 0, @current_color%255) if hand.eql? :left
|
180
|
+
color = Qt::Color.new(@current_color%255, 0, 64) if hand.eql? :right
|
181
|
+
#change color for next delta
|
182
|
+
@current_color += 16
|
183
|
+
ty = 7*@height/12
|
184
|
+
path = Qt::PainterPath.new
|
185
|
+
path.moveTo 0,0
|
186
|
+
#now we construct the path
|
187
|
+
@graph.points.each do |ts,v|
|
188
|
+
path.lineTo v[hand][:deltas][delta][:point]
|
189
|
+
end
|
190
|
+
#and return the values
|
191
|
+
return { :y => ty, :name => name, :path => path, :color => color }
|
192
|
+
end
|
193
|
+
|
194
|
+
#gets called when a repaint is necessary
|
195
|
+
def paintEvent(event)
|
196
|
+
paint_graphs
|
197
|
+
paint_text
|
198
|
+
paint_legend
|
199
|
+
end
|
200
|
+
|
201
|
+
#outsourced graph painting
|
202
|
+
def paint_graphs
|
203
|
+
p = Qt::Painter.new(self)
|
204
|
+
p.setRenderHint(Qt::Painter::HighQualityAntialiasing)
|
205
|
+
p.setBrush Qt::NoBrush
|
206
|
+
@graphs.each do |name,path|
|
207
|
+
p.resetMatrix
|
208
|
+
p.translate((@width/2)-@xdelta,path[:y])
|
209
|
+
pen = Qt::Pen.new(Qt::SolidLine)
|
210
|
+
pen.setColor path[:color]
|
211
|
+
if path.has_key? :width
|
212
|
+
pen.setWidth path[:width]
|
213
|
+
else
|
214
|
+
pen.setWidth 1.5
|
215
|
+
end
|
216
|
+
p.setPen(pen)
|
217
|
+
p.drawPath path[:path]
|
218
|
+
end
|
219
|
+
p.end()
|
220
|
+
end
|
221
|
+
|
222
|
+
#outsourced text painting
|
223
|
+
def paint_text
|
224
|
+
p = Qt::Painter.new(self)
|
225
|
+
p.setRenderHint(Qt::Painter::HighQualityAntialiasing)
|
226
|
+
p.setBrush Qt::NoBrush
|
227
|
+
p.resetMatrix
|
228
|
+
p.translate((@width/2)-@xdelta,@text[:y])
|
229
|
+
pen = Qt::Pen.new(Qt::SolidLine)
|
230
|
+
pen.setWidth 2
|
231
|
+
p.setPen(pen)
|
232
|
+
p.drawPath @text[:path]
|
233
|
+
p.end()
|
234
|
+
end
|
235
|
+
|
236
|
+
#outsourced legend painting
|
237
|
+
def paint_legend
|
238
|
+
p = Qt::Painter.new(self)
|
239
|
+
p.setRenderHint(Qt::Painter::HighQualityAntialiasing)
|
240
|
+
p.resetMatrix
|
241
|
+
p.translate(0,0)
|
242
|
+
p.setBrush @brush
|
243
|
+
pen = Qt::Pen.new(Qt::SolidLine)
|
244
|
+
pen.setWidth 1
|
245
|
+
p.setPen(pen)
|
246
|
+
p.drawPath @legend
|
247
|
+
p.end()
|
248
|
+
end
|
249
|
+
|
250
|
+
#mouse press event
|
251
|
+
def mousePressEvent event
|
252
|
+
if event.buttons == Qt::RightButton
|
253
|
+
elsif event.buttons == Qt::LeftButton
|
254
|
+
#update
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
#a mouse move event
|
259
|
+
def mouseMoveEvent event
|
260
|
+
@mousex,@mousey = event.x,event.y
|
261
|
+
end
|
262
|
+
|
263
|
+
#ruby thread test
|
264
|
+
def reactive_scroll
|
265
|
+
fps = 60.0 #frames per second we attempt to compute
|
266
|
+
tts = 0.0 #time to sleep
|
267
|
+
last_time = Time.now
|
268
|
+
Thread.new do
|
269
|
+
loop do
|
270
|
+
tts = 1.0/fps - (Time.now-last_time)
|
271
|
+
sleep tts if tts > 0
|
272
|
+
unless @mousex.nil?
|
273
|
+
scroll_x
|
274
|
+
smooth_delta
|
275
|
+
if @xspeed.abs > 1
|
276
|
+
#are we speeding?
|
277
|
+
if @xspeed < -1.0*@speedlimit
|
278
|
+
@xspeed = -1.0*@speedlimit
|
279
|
+
@xgoal = @xdelta
|
280
|
+
end
|
281
|
+
if @xspeed > @speedlimit
|
282
|
+
@xspeed = @speedlimit
|
283
|
+
@xgoal = @xdelta
|
284
|
+
end
|
285
|
+
#new delta
|
286
|
+
@xdelta = (@xdelta + @xspeed).to_i
|
287
|
+
update
|
288
|
+
end
|
289
|
+
end
|
290
|
+
last_time = Time.now
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
#scroll on the x_axis if necessary
|
296
|
+
def scroll_x
|
297
|
+
damper = 10
|
298
|
+
#need some kind of zone that triggers scrolling: 1/4 width left and right
|
299
|
+
if @mousex < @width/4
|
300
|
+
@xgoal += (@mousex - @width/4)/damper
|
301
|
+
end
|
302
|
+
if @mousex > 3*@width/4
|
303
|
+
@xgoal += (@mousex - 3*@width/4)/damper
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
#method that smoothes the scroll deltas over time
|
308
|
+
def smooth_delta
|
309
|
+
@xspeed = (@xgoal-@xdelta)*0.05
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
#start the qt application
|
314
|
+
app = Qt::Application.new(ARGV)
|
315
|
+
gui = Gui.new(1200,760)
|
316
|
+
#dirty qt timer magic to make ruby threads work
|
317
|
+
block=Proc.new{ Thread.pass }
|
318
|
+
timer=Qt::Timer.new(gui)
|
319
|
+
invoke=Qt::BlockInvocation.new(timer, block, "invoke()")
|
320
|
+
Qt::Object.connect(timer, SIGNAL("timeout()"), invoke, SLOT("invoke()"))
|
321
|
+
timer.start(12.5) #in millis
|
322
|
+
#end of dirty timer hack
|
323
|
+
gui.show()
|
324
|
+
app.exec()
|
@@ -0,0 +1,150 @@
|
|
1
|
+
=begin
|
2
|
+
This class is used to generate test Tracker-Data to validate and debug
|
3
|
+
the other software.
|
4
|
+
=end
|
5
|
+
|
6
|
+
require "rubygems"
|
7
|
+
require "narray"
|
8
|
+
require "sequel"
|
9
|
+
require "rofl"
|
10
|
+
|
11
|
+
#connect to the database
|
12
|
+
DB = Sequel.connect(:adapter=>'mysql', :host=>'localhost', :database=>'database', :user=>'user', :password=>'password')
|
13
|
+
|
14
|
+
class TrackerTestDataGenerator
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
tablename = "TrackerTest"
|
18
|
+
#drop old test tables
|
19
|
+
DB.drop_table tablename.to_sym
|
20
|
+
#create new table for test data
|
21
|
+
create_test_table tablename
|
22
|
+
#our access to the db is here
|
23
|
+
@rows = DB[tablename.to_sym]
|
24
|
+
@freq = 50.0 #data frequency in Hz
|
25
|
+
length = 60.0 #in seconds
|
26
|
+
generate_test_data length
|
27
|
+
ilog "Generated #{@rows.count} test frames."
|
28
|
+
end
|
29
|
+
|
30
|
+
#this method will generate some easy to debug test tracker data for Neck,Left Hand,Right Hand
|
31
|
+
def generate_test_data length
|
32
|
+
chunk = 1.0 / @freq #frame distance in seconds
|
33
|
+
frames = length * @freq #number of frames per identifier
|
34
|
+
#now we generate the test frames
|
35
|
+
(0..frames).each do |i|
|
36
|
+
timestamp = i*chunk
|
37
|
+
m = NMatrix.float(4,4).unit
|
38
|
+
#identifier specific changes
|
39
|
+
write_matrix_to_db timestamp,"Neck",m
|
40
|
+
write_matrix_to_db timestamp,"Left_Hand",get_left_hand(i)
|
41
|
+
write_matrix_to_db timestamp,"Right_Hand",get_right_hand(i)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
#generate test data for left hand
|
46
|
+
def get_left_hand index
|
47
|
+
m = NMatrix.float(4,4).unit
|
48
|
+
#Left hand changes to position vector
|
49
|
+
m[3,0] = -1 #x
|
50
|
+
m[3,1] = 0.2 #y
|
51
|
+
m[3,2] = -0.5 #z
|
52
|
+
#make potential BOH point to BAB
|
53
|
+
rot_x = x_rotation_matrix -0.5 #rotate 90 degrees around x axis
|
54
|
+
m *= rot_x
|
55
|
+
#and now rotate around BOH axis at 1Hz
|
56
|
+
rot_y = y_rotation_matrix(-index*(2.0/@freq))
|
57
|
+
m *= rot_y
|
58
|
+
return m
|
59
|
+
end
|
60
|
+
|
61
|
+
#generate test data for right hand
|
62
|
+
def get_right_hand index
|
63
|
+
m = NMatrix.float(4,4).unit
|
64
|
+
#Right hand changes to position vector
|
65
|
+
m[3,0] = 1 #x
|
66
|
+
m[3,1] = 0.2 #y
|
67
|
+
m[3,2] = -0.5 #z
|
68
|
+
#make potential BOH point to BAB
|
69
|
+
rot_x = x_rotation_matrix -0.5 #rotate 90 degrees around x axis
|
70
|
+
m *= rot_x
|
71
|
+
#and now rotate around BOH axis at 1Hz
|
72
|
+
rot_y = y_rotation_matrix(index*(2.0/@freq))
|
73
|
+
m *= rot_y
|
74
|
+
return m
|
75
|
+
end
|
76
|
+
|
77
|
+
#get a rotation matrix for a rotation around the x axis
|
78
|
+
def x_rotation_matrix radian
|
79
|
+
m = NMatrix.float(4,4).unit
|
80
|
+
m[1,1] = Math.cos(radian*Math::PI)
|
81
|
+
m[1,2] = Math.sin(radian*Math::PI)
|
82
|
+
m[2,1] = Math.sin(radian*Math::PI)*(-1.0)
|
83
|
+
m[2,2] = Math.cos(radian*Math::PI)
|
84
|
+
return m
|
85
|
+
end
|
86
|
+
|
87
|
+
#get a rotation matrix for a rotation around the y axis
|
88
|
+
def y_rotation_matrix radian
|
89
|
+
m = NMatrix.float(4,4).unit
|
90
|
+
m[0,0] = Math.cos(radian*Math::PI)
|
91
|
+
m[0,2] = Math.sin(radian*Math::PI)*(-1.0)
|
92
|
+
m[2,0] = Math.sin(radian*Math::PI)
|
93
|
+
m[2,2] = Math.cos(radian*Math::PI)
|
94
|
+
return m
|
95
|
+
end
|
96
|
+
|
97
|
+
#get a rotation matrix for a rotation around the z axis
|
98
|
+
def z_rotation_matrix radian
|
99
|
+
m = NMatrix.float(4,4).unit
|
100
|
+
m[0,0] = Math.cos(radian*Math::PI)
|
101
|
+
m[0,1] = Math.sin(radian*Math::PI)
|
102
|
+
m[1,0] = Math.sin(radian*Math::PI)*(-1.0)
|
103
|
+
m[1,1] = Math.cos(radian*Math::PI)
|
104
|
+
return m
|
105
|
+
end
|
106
|
+
|
107
|
+
#write matrix to db
|
108
|
+
def write_matrix_to_db timestamp,identifier,matrix
|
109
|
+
m = matrix.transpose #matrix data in the db are transposed
|
110
|
+
#insert the matrix into the db
|
111
|
+
@rows.insert(:timestamp => timestamp, :identifier => identifier,
|
112
|
+
:xpos => m[0,3]*1000,:ypos => m[1,3]*1000,:zpos => m[2,3]*1000,
|
113
|
+
:m00 => m[0,0],:m10 => m[1,0],:m20 => m[2,0],:m30 => m[3,0],
|
114
|
+
:m01 => m[0,1],:m11 => m[1,1],:m21 => m[2,1],:m31 => m[3,1],
|
115
|
+
:m02 => m[0,2],:m12 => m[1,2],:m22 => m[2,2],:m32 => m[3,2],
|
116
|
+
:m03 => m[0,3],:m13 => m[1,3],:m23 => m[2,3],:m33 => m[3,3])
|
117
|
+
end
|
118
|
+
|
119
|
+
#create table to store test data into
|
120
|
+
def create_test_table tablename
|
121
|
+
DB.create_table tablename.to_sym do
|
122
|
+
primary_key :index
|
123
|
+
String :identifier
|
124
|
+
Double :timestamp
|
125
|
+
Float :xpos
|
126
|
+
Float :ypos
|
127
|
+
Float :zpos
|
128
|
+
#unity matrix is default
|
129
|
+
Float :m00, :default => 1.0
|
130
|
+
Float :m01, :default => 0.0
|
131
|
+
Float :m02, :default => 0.0
|
132
|
+
Float :m03, :default => 0.0
|
133
|
+
Float :m10, :default => 0.0
|
134
|
+
Float :m11, :default => 1.0
|
135
|
+
Float :m12, :default => 0.0
|
136
|
+
Float :m13, :default => 0.0
|
137
|
+
Float :m20, :default => 0.0
|
138
|
+
Float :m21, :default => 0.0
|
139
|
+
Float :m22, :default => 1.0
|
140
|
+
Float :m23, :default => 0.0
|
141
|
+
Float :m30, :default => 0.0
|
142
|
+
Float :m31, :default => 0.0
|
143
|
+
Float :m32, :default => 0.0
|
144
|
+
Float :m33, :default => 1.0
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
#generate some test data
|
150
|
+
test = TrackerTestDataGenerator.new
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pangdudu-mamba
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- pangdudu
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-08-27 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: pangdudu-rofl
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: sequel
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: sequel_core
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: sequel_model
|
47
|
+
type: :runtime
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: mysql
|
57
|
+
type: :runtime
|
58
|
+
version_requirement:
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: narray
|
67
|
+
type: :runtime
|
68
|
+
version_requirement:
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0"
|
74
|
+
version:
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: ruby-opengl
|
77
|
+
type: :runtime
|
78
|
+
version_requirement:
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: "0"
|
84
|
+
version:
|
85
|
+
description: qt-ruby tool for ART tracker data time series analysis.
|
86
|
+
email: pangdudu@github
|
87
|
+
executables: []
|
88
|
+
|
89
|
+
extensions: []
|
90
|
+
|
91
|
+
extra_rdoc_files:
|
92
|
+
- README.rdoc
|
93
|
+
files:
|
94
|
+
- README.rdoc
|
95
|
+
- lib/mamba.rb
|
96
|
+
- lib/graph.rb
|
97
|
+
- lib/b1trackerdata/b1_data_parser.rb
|
98
|
+
- lib/b1trackerdata/openglviewer.rb
|
99
|
+
- tests/generate_testdata.rb
|
100
|
+
has_rdoc: true
|
101
|
+
homepage: http://github.com/pangdudu/mamba
|
102
|
+
licenses:
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
|
106
|
+
require_paths:
|
107
|
+
- lib
|
108
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: "0"
|
113
|
+
version:
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: "0"
|
119
|
+
version:
|
120
|
+
requirements: []
|
121
|
+
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 1.3.5
|
124
|
+
signing_key:
|
125
|
+
specification_version: 2
|
126
|
+
summary: a ruby-qt ART tracker data analysis tool
|
127
|
+
test_files: []
|
128
|
+
|