Skip to content

Commit

Permalink
v2.3 again (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
mark-toma authored Aug 18, 2016
1 parent b6694f6 commit eeb503c
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 105 deletions.
216 changes: 142 additions & 74 deletions MyoMex/MyoData.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@
% m.gyro_fixed % fixed frame gyro - computed from gyro and quat
% m.accel % sensor frame accel
% m.accel_fixed % fixed frame accel - computed from accel and quat
%
% m.timeEMG % corresponds to all other data listed below
% m.emg
% m.pose % enumerated data - logical values below
% m.pose_rest
% m.pose_fist
Expand All @@ -41,6 +38,9 @@
% m.xDir_elbow
% m.xDir_unknown
%
% m.timeEMG % corresponds to all other data listed below
% m.emg
%
% % Inspect the <data>_log properties
% % Just append "_log" to all of the <data> property names above. Take
% % care to use the correct time vectors. Here, I'll generate a plot.
Expand All @@ -54,19 +54,19 @@
% mm.delete();
%
% % Now the MyoData objects aren't receiving new data, but you can
% % still use them to analyze, save, etc. the data you collected.
% % still use them to analyze, save, etc. the data you collected.

properties
% newDataFcn Callback to execute when new data is received
% This is either the empty matrix when not set, or a function handle
% conforming to the signature newDataFcn(source,eventdata,...) when
% set. If newDataFcn is set, it is called when a new frame of data is
% received from MyoMex.
%
% Input parameter source is the handle to this MyoData object, and
% eventdata is currently passed the empty matrix (reserved for future
% use).
newDataFcn
% newDataFcn Callback to execute when new data is received
% This is either the empty matrix when not set, or a function handle
% conforming to the signature newDataFcn(source,eventdata,...) when
% set. If newDataFcn is set, it is called when a new frame of data is
% received from MyoMex.
%
% Input parameter source is the handle to this MyoData object, and
% eventdata is currently passed the empty matrix (reserved for future
% use).
newDataFcn
end

properties (SetAccess = private)
Expand All @@ -75,14 +75,16 @@
% is sampled (at 50Hz). The Myo's IMU data includes quat, gyro, and
% accel. Other data such as rot, gyro_fixed, and accel_fixed are
% computed from the IMU data.
% Other data such as pose, arm, and xDir are also sampled on this
% time base.
%
% See also:
% quat, gyro, accel, rot, gyro_fixed, accel_fixed
% quat, gyro, accel, rot, gyro_fixed, accel_fixed, pose, arm, xDir
timeIMU
% quat Quaternion representing orientation of Myo
% This is a 1x4 array of unit quaternion elements with the scalar
% part listed first. That is, if q = s + vx*i + vy*j + vz*k, then
% quat = [s,vx,vy,vz].
% quat = [s,vx,vy,vz].
%
% Furthermore, the interpretation of quat as a
% rotation or transformation depends on the definition of quaternion
Expand Down Expand Up @@ -124,18 +126,6 @@
% See also:
% quat, rot, accel
accel_fixed
% timeEMG Time of sampling for sEMG data
% This is the time at which the sEMG data is sampled (at 200Hz).
% Other data such as pose, arm, and xDir are also sampled on this
% time base.
%
% See also:
% emg, pose, arm, xDir
timeEMG
% emg Raw data from 8 surface EMG (sEMG) sensors
% This is a 1x8 array of sEMG data in the range [-1,1] sampled at a
% rate of 200Hz.
emg
% pose Indicates the currently detected gesture (enum)
% This is an enumerated value. Access logical indication of a
% particular pose with the pose_suffix properties where suffix is the
Expand Down Expand Up @@ -174,6 +164,16 @@
xDir_wrist % Indicates xDir from enum value in xDir (logical)
xDir_elbow % Indicates xDir from enum value in xDir (logical)
xDir_unknown % Indicates xDir from enum value in xDir (logical)
% timeEMG Time of sampling for sEMG data
% This is the time at which the sEMG data is sampled (at 200Hz).
%
% See also:
% emg
timeEMG
% emg Raw data from 8 surface EMG (sEMG) sensors
% This is a 1x8 array of sEMG data in the range [-1,1] sampled at a
% rate of 200Hz.
emg
% isStreaming Indicates status of data logging (logical)
% This state is toggled by methods startStreaming and
% stopStreaming
Expand All @@ -188,13 +188,13 @@
quat_log
gyro_log
gyro_fixed_log
accel_log
accel_fixed_log
timeEMG_log
emg_log
pose_log
arm_log
xDir_log
accel_log
accel_fixed_log
pose_log
arm_log
timeEMG_log
emg_log
xDir_log
POSE_REST = 0
POSE_FIST = 1
POSE_WAVE_IN = 2
Expand All @@ -213,7 +213,11 @@
%
% See also:
% quat, q2r, gyro, gyro_fixed, accel, accel_fixed
rot;
rot
% rateIMU Approximate data rate for IMU data
rateIMU
% rateEMG Approximate data rate for EMG data
rateEMG
end

properties (Dependent,Hidden=true)
Expand All @@ -234,8 +238,8 @@
end

properties (Access=private,Hidden=true)
prevTimeIMU
prevTimeEMG
prevTimeIMU
prevTimeEMG

% libmyo.h enum order: right, left, unknown
% DeviceListener.hpp enum order: left, right, unknown
Expand All @@ -250,6 +254,13 @@
IMU_SAMPLE_TIME = 0.020 % 50Hz
EMG_SAMPLE_TIME = 0.005 % 200Hz
EMG_SCALE = 128

NUM_INIT_SAMPLES = 4
end

properties (Access={?MyoMex},Hidden=true)
logDataFidIMU
logDataFidEMG
end

methods
Expand All @@ -275,17 +286,33 @@
end

function delete(this)

% close log files
if ~isempty(this.logDataFidIMU)
fclose(this.logDataFidIMU);
end
if ~isempty(this.logDataFidEMG)
fclose(this.logDataFidEMG);
end
end

%% --- Setters
function set.newDataFcn(this,val)
assert(isempty(val)||(isa(val,'function_handle')&&(2==nargin(val))),...
'Property newDataFcn must be the empty matrix when not set, or a function handles conforming to the signature newDataFcn(source,eventdata,...) when set.');
this.newDataFcn = val;
this.newDataFcn = val;
end

%% --- Dependent Getters
function val = get.rateIMU(this)
val = nan;
if isempty(this.timeIMU_log), return; end
val = length(this.timeIMU_log)/range(this.timeIMU_log);
end
function val = get.rateEMG(this)
val = nan;
if isempty(this.timeEMG_log), return; end
val = length(this.timeEMG_log)/range(this.timeEMG_log);
end
function val = get.rot(this)
if isempty(this.quat)
val = [];
Expand Down Expand Up @@ -415,20 +442,23 @@ function addData(this,data,currTime)
for ii=1:length(this)
this(ii).addDataIMU(data(ii),currTime);
this(ii).addDataEMG(data(ii),currTime);
this(ii).onNewData();
end

this.onNewData();

end

end

methods (Access=private)
methods (Access=private)

%% --- Internal Data Management
function addDataIMU(this,data,currTime)
if isempty(data.quat), return; end

N = size(data.quat,1);
if N==0, return; end
P = this.NUM_INIT_SAMPLES;
assert( ~(isempty(this.prevTimeIMU)&&(N<P)),...
'Too few samples received in initialization of log.');

t = (1:1:N)' * this.IMU_SAMPLE_TIME;

q = data.quat;
Expand All @@ -437,17 +467,24 @@ function addDataIMU(this,data,currTime)
q = this.qRenorm(q); %renormalize quaterions
gf = this.qRot(q,g);
af = this.qRot(q,a);
p = data.pose;
m = data.arm;
x = data.xDir;

if ~isempty(this.prevTimeIMU)
t = t + this.prevTimeIMU;
else % init time
t = t - t(end) + currTime;
t = t(2:end,:);
q = q(2:end,:);
g = g(2:end,:);
gf = gf(2:end,:);
a = a(2:end,:);
af = af(2:end,:);
% chop off first P data points
t = t(P+1:end,:);
q = q(P+1:end,:);
g = g(P+1:end,:);
gf = gf(P+1:end,:);
a = a(P+1:end,:);
af = af(P+1:end,:);
p = p(P+1:end,:);
m = m(P+1:end,:);
x = x(P+1:end,:);
end

this.prevTimeIMU = t(end);
Expand All @@ -458,60 +495,91 @@ function addDataIMU(this,data,currTime)
this.gyro_fixed = gf(end,:);
this.accel = a(end,:);
this.accel_fixed = af(end,:);
this.pose = p(end,:);
this.arm = m(end,:);
this.xDir = x(end,:);

if this.isStreaming, this.pushLogsIMU(t,q,g,gf,a,af); end
if this.isStreaming, this.pushLogsIMU(t,q,g,gf,a,af,p,m,x); end

end

function addDataEMG(this,data,currTime)
if isempty(data.emg), return; end

N = size(data.emg,1);
if N==0, return; end
P = this.NUM_INIT_SAMPLES;
assert( ~(isempty(this.prevTimeEMG)&&(N<P)),...
'Too few samples received in initialization of log.');

t = (1:1:N)' * this.EMG_SAMPLE_TIME;

e = data.emg./this.EMG_SCALE;
p = data.pose;
a = data.arm;
x = data.xDir;

if ~isempty(this.prevTimeEMG)
t = t + this.prevTimeIMU;
t = t + this.prevTimeEMG;
else % init time
t = t - t(end) + currTime;
% chop off first data point
t = t(2:end,:);
e = e(2:end,:);
p = p(2:end,:);
a = a(2:end,:);
x = x(2:end,:);
% chop off first P data points
t = t(P+1:end,:);
e = e(P+1:end,:);
end
this.prevTimeEMG = t(end);

this.timeEMG = t(end,:);
this.emg = e(end,:);
this.pose = p(end,:);
this.arm = a(end,:);
this.xDir = x(end,:);

if this.isStreaming, this.pushLogsEMG(t,e,p,a,x); end
if this.isStreaming, this.pushLogsEMG(t,e); end

end

function pushLogsIMU(this,t,q,g,gf,a,af)
function pushLogsIMU(this,t,q,g,gf,a,af,p,m,x)
this.timeIMU_log = [ this.timeIMU_log ; t ];
this.quat_log = [ this.quat_log ; q ];
this.gyro_log = [ this.gyro_log ; g ];
this.gyro_fixed_log = [ this.gyro_fixed_log ; gf ];
this.accel_log = [ this.accel_log ; a ];
this.accel_fixed_log = [ this.accel_fixed_log ; af ];
this.pose_log = [ this.pose_log ; p ];
this.arm_log = [ this.arm_log ; m ];
this.xDir_log = [ this.xDir_log ; x ];

fid = this.logDataFidIMU;
if ~isempty(fid)
if 0==ftell(fid)
% print header
fprintf(fid,...
'%s%s%s%s%s%s%s\n',...
't[s],',...
'q.w[N],q.x[N],q.y[N],q.z[N],',...
'gs.x[deg/s],gs.y[deg/s],gs.z[deg/s],',...
'gf.x[deg/s],gf.y[deg/s],gf.z[deg/s],',...
'as.x[g],as.y[g],as.z[g],',...
'af.x[g],af.y[g],af.z[g],',...
'pose[enum],arm[enum],xDir[enum]');
end
% print data
data = [t,q,g,gf,a,af,p,m,x]';
fprintf(fid,...
[repmat('%f,',[1,17]),'%d,%d,%d\n'],data);
end
end

function pushLogsEMG(this,t,e,p,a,x)
function pushLogsEMG(this,t,e)
this.timeEMG_log = [ this.timeEMG_log ; t ];
this.emg_log = [ this.emg_log ; e ];
this.pose_log = [ this.pose_log ; p ];
this.arm_log = [ this.arm_log ; a ];
this.xDir_log = [ this.xDir_log ; x ];

fid = this.logDataFidEMG;
if ~isempty(fid)
if 0==ftell(fid)
% print header
fprintf(fid,...
't,%se.8[N]\n',...
sprintf('e.%d[N],',1:7));
end
% print data
data = [t,e]';
fprintf(fid,...
[repmat('%f,',[1,8]),'%f\n'],data);
end
end

function onNewData(this)
Expand Down
Loading

0 comments on commit eeb503c

Please sign in to comment.