Skip to content

Commit

Permalink
Minor revision update 2.1
Browse files Browse the repository at this point in the history
* Added help for data properties

* Implemented newDataFcn callback property

After new data is received and handled in addData, onNewData() is
called. This new method calls newDataFcn if it is set.

* Removed test... identified the invisible bug.

The "bug" in Myo Connect doesn't exist. I conclude that it was caused by
an issue in old myo_mex.cpp code related to DataCollector in global
scope and "clear myo_mex" not being called in MATLAB. This results in
DataCollector remaining in memory after "myo_mex delete" and stale Myo*
hanging on DataCollector.

* Added descriptive comments to class members

* Added test for bug in Myo Connect

Sometimes myo::Hub passes two unique myo::Myo* pointers into
myo::DeviceListener callbacks IFF there is currently one Myo connected
in Myo Connect, but there were previously two Myos connected.

* Bugfixes, features, change behavior of EMG streaming when countMyos>1

* Formatting

* Updates for release 2.1

* Final update for release 2.1
  • Loading branch information
mark-toma committed Apr 4, 2016
1 parent 66cc691 commit 3f6de58
Show file tree
Hide file tree
Showing 8 changed files with 597 additions and 262 deletions.
331 changes: 225 additions & 106 deletions MyoMex/MyoData.m

Large diffs are not rendered by default.

49 changes: 30 additions & 19 deletions MyoMex/MyoMex.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,30 @@
% data out of the FIFO. The timer callback then calls back into the
% MyoMex property myoData (a MyoData object) to provide it with the
% latest batch of samples.


properties
newDataFcn
end
properties (SetAccess = private)
% myo_data Data objects for physical Myo devices
myoData;
myoData
end
properties (Dependent,Hidden=true)
currTime;
currTime
end
properties (Access=private,Hidden=true)
timerStreamingData
nowInit
DEFAULT_STREAMING_FRAME_TIME = 0.040
NUM_INIT_SAMPLES = 4
end

methods

%% --- Object Management
function this = MyoMex(countMyos)
% MyoMex Construct a MyoMex object
%
%
% Inputs:
% countMyos - Number of Myos
% Numerical scalar specifying the number of physical Myo devices
Expand All @@ -66,7 +70,7 @@
% variable throughout the lifecycle of MyoMex and then explicitly
% call the delete() method on the object when finished.
assert(nargout==1,...
'MyoMex must be assigned to an output variable.');
'MyoMex must be assigned to an output variable.');

if nargin<1, countMyos = 1; end

Expand All @@ -87,22 +91,15 @@
end

% call into myo_mex init
[fail,emsg,countMyosInit] = this.myo_mex_init();
[fail,emsg] = this.myo_mex_init(countMyos);
if fail
if strcmp(emsg,'Myo failed to init!') % extra hint
warning('Myo will fail to init if it is not connected to your system via Myo Connect.');
end
this.myo_mex_clear();
error('MEX-file ''myo_mex'' failed to initialize with error:\n\t''%s''',emsg);
end

% error out if myo_mex failed to initialize with desired countMyos
if countMyosInit ~= countMyos
this.myo_mex_delete(); % clean up myo_mex internal state
this.myo_mex_clear(); % clean up mex file myo_mex
error('MyoMex failed to initialize %d Myos. myo_mex initialized to %d Myos instead.',...
countMyos,countMyosInit);
end

this.myoData = MyoData(countMyos);

% at this point, myo_mex should be alive!
Expand All @@ -121,6 +118,14 @@ function delete(this)
MyoMex.myo_mex_clear();
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;
end

%% --- Dependent Getters
function val = get.currTime(this)
val = (now - this.nowInit)*24*60*60;
end
Expand All @@ -139,7 +144,7 @@ function startStreaming(this)
'executionmode','fixedrate',...
'name','MyoMex-timerStreamingData',...
'period',this.DEFAULT_STREAMING_FRAME_TIME,...
'startdelay',this.DEFAULT_STREAMING_FRAME_TIME,...
'startdelay',this.DEFAULT_STREAMING_FRAME_TIME*this.NUM_INIT_SAMPLES,...
'timerfcn',@(src,evt)this.timerStreamingDataCallback(src,evt));
[fail,emsg] = this.myo_mex_start_streaming();
if fail
Expand Down Expand Up @@ -182,8 +187,13 @@ function timerStreamingDataCallback(this,~,~)
'myo_mex get_streaming_data failed with message\n\t''%s''\n%s',emsg,...
sprintf('MyoMex has been cleaned up and destroyed.'));
this.myoData.addData(data,this.currTime);
this.onNewData();
end
function onNewData(this)
if ~isempty(this.newDataFcn)
this.newDataFcn(this,[]);
end
end

end

%% --- Wrappers for myo_mex Interface
Expand All @@ -192,11 +202,12 @@ function timerStreamingDataCallback(this,~,~)
% the single quotes
methods (Static=true,Access=private,Hidden=true)

function [fail,emsg,data] = myo_mex_init()
function [fail,emsg] = myo_mex_init(countMyos)
assert( (nargin==1) && isnumeric(countMyos) && isscalar(countMyos) && any(countMyos==[1,2]),...
'Input countMyos must be a numeric scalar in [1,2].');
fail = false; emsg = [];
data = [];
try
data = myo_mex('init');
myo_mex('init',countMyos);
catch err
fail = true; emsg = err.message;
end
Expand Down
118 changes: 118 additions & 0 deletions MyoMex/example/MyoMexExample_DataContinuity.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
%% MyoMexExample_DataContinuity
% In this script, we log samples from countMyos devices and then inspect
% the data to detect cases in which data may have been lost. Since missing
% data is interpolated by zero order hold, we can only say for sure if no
% samples have been missed.


%% Log Data
% Configure |TIME_DURATION| (10 seconds is a good choice) and |countMyos|
% before running this script.

TIME_DURATION = 20; % seconds
countMyos = 2;

fprintf('Collecting data from %d Myos for roughly %5.2f[s] ... ',...
countMyos,TIME_DURATION);
mm = MyoMex(countMyos);
tic;
m = mm.myoData;
pause(TIME_DURATION-toc);
mm.delete();
fprintf('Done!\n\n');

%% Test Data for Missed Samples
% Anonymous function returns logical scalar representing the validity of
% samples. It returns true if no two consecutive rows have the same values
% in all columns. Otherwise it returns false.

hasDataContinuity = @(x)~any(all((~diff(x))'));

flags = [];
for ii = 1:countMyos
flags = [flags,hasDataContinuity(m(ii).quat_log)];
flags = [flags,hasDataContinuity(m(ii).gyro_log)];
flags = [flags,hasDataContinuity(m(ii).accel_log)];
if countMyos == 1
flags = [flags,hasDataContinuity(m(ii).emg_log)];
end
fprintf('Estimated sample rate (Myo %d):\n\tIMU: %5.2f[Hz]\tEMG: %5.2f[Hz]\n',...
ii,length(m(ii).timeIMU_log)/TIME_DURATION,length(m(ii).timeEMG_log)/TIME_DURATION);
end
fprintf('\n');

fprintf('Data continuity test result:\n\t');
if all(flags)
fprintf('PASS: No samples were missed\n\n');
else
fprintf('FAIL: At least one sample was missed\n\n');
end


%% Plot Duplicate Samples
% Usually we find that the duplicates are in up to about 15% of the
% quaternion samples. It may be that the quaternion estimate is acually
% updated less frquently than it is sampled. Over many 60 second trials, I
% seldom witness lost data in gyro, accel, or emg. This leads me to believe
% that we're not actually missing quaternion data.

idDuplicates = @(x)all((~diff(x))');

tIMU1 = m(1).timeIMU_log(2:end);
qd1 = idDuplicates(m(1).quat_log);
gd1 = idDuplicates(m(1).gyro_log);
ad1 = idDuplicates(m(1).accel_log);

if countMyos == 2
tIMU2 = m(2).timeIMU_log(2:end);
qd2 = idDuplicates(m(2).quat_log);
gd2 = idDuplicates(m(2).gyro_log);
ad2 = idDuplicates(m(2).accel_log);
elseif countMyos==1
tEMG1 = m(1).timeEMG_log(2:end);
ed1 = idDuplicates(m(1).emg_log);
end

figure;
if countMyos == 2, subplot(2,1,1); end
plot(tIMU1,1*qd1,'r',tIMU1,2*gd1,'g',tIMU1,3*ad1,'b'); legCell = {'quat','gyro','accel'};
if countMyos == 1, hold on; plot(tEMG1,4*ed1,'k'); legCell{4} = 'emg'; end
legend(legCell);
if countMyos == 2
subplot(2,1,2);
plot(tIMU2,1*qd2,'r',tIMU2,2*gd2,'g',tIMU2,3*ad2,'b');
legend('quat','gyro','accel');
end

%% Plot Acceleration Magnitude
% If logging from two Myos, plot the 2 norm of kinematic acceleration to
% inspect the time-synchronization of signals. You may choose to perform
% similar motions on the two Myos to visualize the results.

if countMyos == 2
figure;
an1 = sqrt(sum((m(1).accel_log.^2)'))-1;
an2 = sqrt(sum((m(2).accel_log.^2)'))-1;
plot(...
m(1).timeIMU_log,...
an1-mean(an1),'r',...
m(2).timeIMU_log,...
an2-mean(an2),'b');
legend('|a|_1','|a|_2');
end
















Binary file added MyoMex/example/MyoMexExample_DataContinuity.pdf
Binary file not shown.
Loading

0 comments on commit 3f6de58

Please sign in to comment.