Appendix A Javascript and HTML
A.1 Flex App
A.1.1 index.html
Muscle Rehabilitation
Bluetooth Connection
Refresh
Devices
Select a paired device:
{{ device.name}}
Disconnect
1
Bend Threshold Calibration:
{{ (100.0 * (
bendThreshold / 1023.0)).toFixed () }}%
Target number of repetitions:
{{ repsTarget }}
Count repetitions:
{{
countBtnString }}
A.1.2 app.js
var app = angular.module('flexApp ', ['ionic ', 'ngCordova ', 'chart.js '])
app.run(function($ionicPlatform) {
$ionicPlatform.ready(function () {
// Hide the accessory bar by default (remove this to show the accessory bar above the
keyboard
// for form inputs)
if(window.cordova && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
if(window.StatusBar) {
StatusBar.styleDefault ();
}
});
2
});
app.controller (" flexController", function($scope , $cordovaBluetoothSerial ,
$ionicModal , $ionicPlatform , $timeout) {
$ionicModal.fromTemplateUrl ("bluetooth -modal.html",
{ scope: $scope , animation: "slide -in-up" })
.then(function(modal) {
$scope.modal = modal;
});
$scope.$on('$destroy ', function () {
$scope.modal.remove ();
});
$ionicPlatform.ready(function () {
$scope.modal.show();
$scope.refreshDevices ();
});
$scope.devicesEmpty = true;
$scope.noDevices = false;
$scope.selectedDevice = 0;
$scope.connectedDevice = 0;
$scope.refreshDevices = function () {
var listPromise = $cordovaBluetoothSerial.list();
listPromise.then(function(devicesData) {
$scope.devices = devicesData; // Get rid of placeholder
$scope.devicesEmpty = false;
}, function(error) {
alert(error);
});
};
$scope.selectDevice = function(device , index) {
$scope.selectedDevice = device;
$scope.deviceError = 0;
$scope.connectedDevice = 0;
var connectionPromise = $cordovaBluetoothSerial.connect(device.address);
connectionPromise.then(function () {
$scope.deviceError = 0;
$scope.connectedDevice = device;
$timeout($scope.modal.hide(), 1000);
}, function(error) {
$scope.deviceError = device;
$scope.selectedDevice = 0;
alert(error);
})
}
$scope.disconnect = function () {
var disconnectPromise = $cordovaBluetoothSerial.disconnect ();
disconnectPromise.then(function () {
$scope.connectedDevice = 0;
$scope.selectedDevice = 0;
$scope.deviceError = 0;
}, function(error) {
alert(error);
});
}
$scope.sendToDevice = function(msg) {
var writePromise = $cordovaBluetoothSerial.write(msg);
writePromise.then(null , function(error) { alert(error); });
}
$scope.chartOpts = { animation: false ,
showTooltips : false ,
scaleShowLabels: false ,
scaleShowGridLines: false ,
scaleOverride: true ,
scaleSteps: 1,
scaleStepWidth: 1024,
scaleStartValue: 0,
3
pointDot: false ,
bezierCurve: false };
$scope.chartLabels = Array (50).join (".").split (".");
var zeros = Array.apply(null , new Array (50)).map(Number.prototype.valueOf , 0)
var thresholdLine = Array.apply(null , new Array (50)).map(Number.prototype.valueOf , 0);
$scope.chartData = [zeros , thresholdLine ];
$scope.calibrationLocked = true;
$scope.bendThreshold = 512;
$scope.repsTarget = 25;
$scope.nReps = 0;
$scope.countBtnString = "Start Counting !";
$scope.manageCalibration = function () {
$scope.calibrationLocked = !$scope.calibrationLocked;
if (! $scope.calibrationLocked) {
$scope.sendToDevice ("C"); // Start calibration
$cordovaBluetoothSerial.subscribe ("\r").then(null ,
function(error) {
alert(error);
},
function(data) {
$scope.bendThreshold = parseInt(data.substring(0, data.length - 1));
});
} else {
$scope.sendToDevice ("F"); // Finish calibration
$scope.chartData [1] = thresholdLine.map(function () { return $scope.bendThreshold; });
$cordovaBluetoothSerial.unsubscribe ();
}
}
var countOn = false;
$scope.startCounting = function () {
if (countOn) {
$scope.countBtnString = "Start Counting !";
$scope.sendToDevice ("T");
countOn = false;
return;
}
countOn = true;
$scope.nReps = 0;
$scope.countBtnString = $scope.nReps;
$scope.sendToDevice ("S"); // Start
$cordovaBluetoothSerial.subscribe ("\r").then(null ,
function(error) {
alert(error);
},
function(data) {
data = String(data.substring(0, data.length - 1));
if (!isNaN(data)) {
console.log(data);
$scope.chartData [0]. shift();
$scope.chartData [0]. push(data);
} else {
console.log(" Repetition ");
$scope.nReps ++;
$scope.countBtnString ++;
}
if ($scope.nReps == $scope.repsTarget) {
$scope.countBtnString = "Succeeded !";
countOn = false;
$scope.sendToDevice ("T"); // Terminate
$cordovaBluetoothSerial.unsubscribe ();
$scope.chartData [0] = zeros;
}
});
}
});
4
A.1.3 style.css
.grey {
color: gray;
}
.black {
color: black;
}
.selected {
color: #387 ef5;
}
.red {
color: red;
}
.progress -bar {
width: 100%;
height: 30px;
background: #f5f5f5;
border -radius: 2px;
}
.increment {
height: 100%;
background: #444;
transition: all .75s;
border -radius: 2px;
}
.increment -calm {
height: 100%;
background: #33 cd5f;
transition: all .75s;
border -radius: 2px;
}
A.2 EMG App
A.2.1 index.html
5
EMG Fatigue Analysis
Bluetooth Connection
Refresh
Devices
Select a paired device:
{{ device.name}}
Disconnect
Please connect the electrode to the target muscle and set the parameters below.
When you 're ready , press the button to start analysis.
Fatigue threshold:
(% of initial value)
{{ fatigueThreshold
}}%
Update frequency:
(ms)
{{ psdFreq }}
Start
6
Fatigued
Fresh
Initial Level
A.2.2 app.js
var app = angular.module('emgApp ', ['ionic ', 'ngCordova ']);
app.run(function($ionicPlatform) {
$ionicPlatform.ready(function () {
// Hide the accessory bar by default (remove this to show the accessory bar above the
keyboard
// for form inputs)
if(window.cordova && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
if(window.StatusBar) {
StatusBar.styleDefault ();
}
});
});
function arburg(input , m) {
// Initialise vars
var N = input.length - 1;
var Ak = new Float32Array(m+1); // Default values are 0.0
Ak[0] = 1.0;
var f = new Float32Array(input);
var b = new Float32Array(input);
var t1 = 0.0;
var t2 = 0.0;
var mu = 0.0;
var Dk = 0.0;
for (i = 0; i
return Ak;
}
function burg2psd(Ak, NFFT) {
// Zero -padded array for FFT
var fourierPadded = new complex_array.ComplexArray(NFFT);
fourierPadded.map(function(x,i) {
x.real = (i < Ak.length) ? Ak[i] : 0.0;
});
var psd = fourierPadded.FFT();
var mags = psd.magnitude ()
for (i = 0; i < mags.length; i++) {
mags[i] = 1.0/( mags[i]*mags[i]);
}
return mags.subarray(0, 0.5*( mags.length));
}
function getFreqFromData(data) {
var BURG_ORDER = 20;
var burgParameters = arburg(data , BURG_ORDER);
var psd = burg2psd(burgParameters , data.length);
var mnf = 0.0;
var sumPwr = 0.0;
var nFs = psd.length;
for (i = 0; i < nFs; i++) {
mnf += i*psd[i];
sumPwr += psd[i];
}
mnf /= sumPwr;
return mnf;
}
app.controller (" emgController", function($scope , $cordovaBluetoothSerial ,
$ionicPlatform , $ionicModal , $timeout) {
$ionicModal.fromTemplateUrl ("bluetooth -modal.html",
{ scope: $scope , animation: "slide -in-up" })
.then(function(modal) {
$scope.modal = modal;
});
$scope.$on('$destroy ', function () {
$scope.modal.remove ();
});
$ionicPlatform.ready(function () {
$scope.modal.show();
$scope.refreshDevices ();
});
$scope.devicesEmpty = true;
$scope.noDevices = false;
$scope.selectedDevice = 0;
$scope.connectedDevice = 0;
$scope.refreshDevices = function () {
var listPromise = $cordovaBluetoothSerial.list();
listPromise.then(function(devicesData) {
$scope.devices = devicesData; // Get rid of placeholder
$scope.devicesEmpty = false;
}, function(error) {
alert(error);
});
};
$scope.selectDevice = function(device , index) {
$scope.selectedDevice = device;
$scope.deviceError = 0;
$scope.connectedDevice = 0;
8
var connectionPromise = $cordovaBluetoothSerial.connect(device.address);
connectionPromise.then(function () {
$scope.deviceError = 0;
$scope.connectedDevice = device;
$timeout($scope.modal.hide(), 1000);
}, function(error) {
$scope.deviceError = device;
$scope.selectedDevice = 0;
alert(error);
})
}
$scope.disconnect = function () {
var disconnectPromise = $cordovaBluetoothSerial.disconnect ();
disconnectPromise.then(function () {
$scope.connectedDevice = 0;
$scope.selectedDevice = 0;
$scope.deviceError = 0;
}, function(error) {
alert(error);
});
}
$scope.sendToDevice = function(msg) {
var writePromise = $cordovaBluetoothSerial.write(msg);
writePromise.then(null , function(error) { alert(error); });
}
$scope.emgOn = false;
$scope.fatigueThreshold = 75;
$scope.psdFreq = 500;
var startFreq = null;
$scope.startEMG = function () {
$scope.sendToDevice ("S");
var dataWindow = new Float32Array($scope.psdFreq);
var nInWindow = 0;
$cordovaBluetoothSerial.subscribe('\r', null ,
function(error) {
alert(error);
}, function(received) {
received = received.substring(0, received.length - 1);
console.log(" Received: " + parseFloat(received))
dataWindow[nInWindow] = parseFloat(received);
nInWindow ++;
if (nInWindow == $scope.psdFreq) {
nInWindow = 0;
var mnf = getFreqFromData(dataWindow);
$scope.meanFreq = mnf.toFixed (2);
}
});
var meanFreq = getFreqFromData(dataWindow);
if (! startFreq) startFreq = meanFreq;
// $scope.currentFreqPercentage = (80* meanFreq/startFreq).toFixed ();
$timeout(function () { $scope.emgOn = true; }, 800);
}
});
A.2.3 style.css
.freq -indicator {
width: 100%;
height: 50px;
background: #3A1414;
border -radius: 2px;
}
.freq -indicator -handle {
float:left;
9
background: #e42012;
height: 100%;
width: 15px;
opacity: 0.8;
border -radius: 2px;
margin -left: 50%;
transition: all 0.5s;
}
.freq -start -marker {
background: #fff;
height: 100%;
width: 6px;
margin -left: 80%;
z-index: 10;
opacity: 0.2;
}
.initial -level -text {
text -align: center;
margin -left :60%;
margin -top: 5px;
}
A.3 Ergonomics App
A.3.1 index.html
10
Please fill in the forms below. All measurements are in centimetres (cm).
If you wish to use the app s accelerometer measurement function , then hold
down the crosshair icon next to the field while you move the phone through
the desired distance , then release it.
{{param.label}}
Male
Female
{{ param.label }}:
Save
Target health improvements:
{{ target.name}}
Save
See your
recommendations
11
Your height is approximately in the:
{{ heightPercentileString }}
Percentile for your gender
Based on this , your approximate measurements are below:
{{ measurement.val.toFixed ()}}cm
Here is an analysis of your results. Refer to the diagram below for an explanation
of measurements.
Light Level:
{{ lightLevel }} lx
Study/Sleep:
12
Too Dark
OK...
Good!
Too Bright
OK...
Good!
A.3.2 app.js
angular.module('ergoApp.controllers ', []);
var app = angular.module('ergoApp ', ['ionic ', 'ngCordova ', 'ergoApp.controllers ']);
app.run(function($ionicPlatform) {
$ionicPlatform.ready(function () {
// Hide the accessory bar by default (remove this to show the accessory bar above the
keyboard
// for form inputs)
if(window.cordova && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
if(window.StatusBar) {
StatusBar.styleDefault ();
}
});
});
app.service('personalParameters ', function($filter) {
var paramsList = [];
var defaultParams = [ { label : "Gender", val : "Male", type : 'sel ', id : 'gender ' },
{ label : "Height", val : null , type : 'num ', id : 'height ' },
{ label : "Shoe Height", val : null , type: 'num ', id : 'shoeHeight ' },
{ label : "Desk Height", val : null , type: 'num ', id : 'deskHeight ' }
];
this.resetParams = function () {
paramsList = defaultParams;
}
this.getParams = function () {
return paramsList;
}
this.getParam = function(name) {
return $filter('filter ')(paramsList , { id : name }, true)[0];
}
this.paramsFromString = function(paramsString) {
if (paramsString) {
var tempParamsObj = angular.fromJson(paramsString);
if (tempParamsObj.length != defaultParams.length) {
this.resetParams ();
13
} else {
paramsList = tempParamsObj;
}
} else {
this.resetParams ();
}
}
this.loadParams = function () {
this.paramsFromString(window.localStorage['parameters ']);
}
this.saveParams = function () {
window.localStorage['parameters '] = angular.toJson(paramsList);
}
});
app.service('healthTargets ', function () {
var targets = [ { name : "Upper leg circulation", selected : false },
{ name : "Lower back pain", selected : false },
{ name : "Shoulders slouching", selected : false },
{ name : "Neck strain", selected : false },
{ name : "Leaning shoulders forwards", selected : false },
{ name : "Head -forward posture", selected : false },
{ name : "Slouching (body)", selected : false } ];
this.getTargets = function () {
return targets;
}
this.targetsFromString = function(targetsString) {
if (targetsString) {
var tempTargetsObj = angular.fromJson(targetsString);
if (tempTargetsObj.length != targets.length) return;
targets = tempTargetsObj;
}
}
this.loadTargets = function () {
this.targetsFromString(window.localStorage['healthTargets ']);
}
this.saveTargets = function () {
window.localStorage['healthTargets '] = angular.toJson(targets);
}
});
app.service('measurements ', function($filter) {
var measurements = [];
var percentile = null;
var percentileInterps_ = {
popliteal : { M : [37.9103 , 0.27397 , -0.00474992 , 0.0000329045] ,
F : [33.6337 , 0.262061 , -0.00406481 , 0.0000264853]
},
buttockPopliteal : { M : [41.9744 , 0.357434 , -0.00627922 ,
0.0000422334] ,
F : [41.1117 , 0.314483 , -0.00544547 ,
0.0000374628] },
seatedLegHeight : { M : [46.9841 , 0.344695 , -0.00583202 ,
0.0000380687] ,
F : [43.5857 , 0.289506 , -0.00513983 ,
0.0000354398] },
upperArmLength : { M : [34.4534 , 0.18154 , -0.00265015 ,
0.0000174923] ,
F : [31.7875 , 0.14473 , -0.00204166 ,
0.000014238] },
seatedShoulderHeight : { M : [48.7464 , 0.139865 , -0.00232796 ,
0.0000153862] ,
F : [47.9073 , 0.115281 , -0.0019322 ,
0.0000128412] }
14
};
this.calc = function(height , gender) {
percentile = height2percentile(height);
measurements = [ { name : "Popliteal Height", id : "popliteal",
val : polynomial(percentileInterps_['popliteal '][ gender], percentile)
},
{ name : "Buttock -popliteal Length", id : "buttockPopliteal",
val : polynomial(percentileInterps_['buttockPopliteal '][ gender],
percentile) },
{ name : "Seated Leg Height", id : "seatedLegHeight",
val : polynomial(percentileInterps_['seatedLegHeight '][ gender],
percentile) },
{ name : "Upper Arm Length" , id : "upperArmLength",
val : polynomial(percentileInterps_['upperArmLength '][ gender],
percentile) },
{ name : "Seated Shoulder Height (excl. seat height)", id : '
seatedShoulderHeight ',
val : polynomial(percentileInterps_['upperArmLength '][ gender],
percentile) } ];
}
this.getPercentile = function () {
return percentile;
}
this.getMeasurements = function () {
return measurements;
}
this.getVal = function(idIn) {
var obj = $filter('filter ')(measurements , { id : idIn }, true);
return obj.val;
}
// "Private" helper functions
var height2percentile = function(h) {
var d = Math.pow ( -1.68468 e68 + 9.7134 e65*h + 1.18584 e30*Math.sqrt (2.01849 e76 - 2.32737 e74*
h + 6.70949 e71*h*h), 1/3.);
return 50.434 - (2.62631 e23/d) + (1.85244e-21*d);
}
var polynomial = function(coeffs , x) {
return coeffs [0] + coeffs [1]*x + coeffs [2]*x*x + coeffs [3]*x*x*x;
}
this.seatHeightEqn = function(shoeHeight) {
var s = getVal('popliteal ') + shoeHeight;
return { low : s*Math.cos(Math.PI/6.),
high : s*Math.cos(Math.PI/36.) };
}
this.elbowDeskEqn = function () {
var SEH = getVal('seatedShoulderHeight ') - getVal('upperArmLength ');
return { low : SEH ,
high : SEH + 5 };
}
this.backrestEqn = function () {
var SSH = getVal('seatedShoulderHeight ');
return { low : 0.6*SSH ,
high : 0.8* SSH };
}
this.seatDepthEqn = function () {
var BPL = getVal('buttockPopliteal ');
return { low : 0.8*BPL ,
high : 0.95* BPL };
}
this.leaningElbowEqn = function(theta , beta) {
var SSH = getVal('seatedShoulderHeight ');
var U = getVal('upperArmLength ');
var SEH = SSH - U;
return SEH + U*((1 - Math.cos(theta)) + Math.cos(theta)*(1 - Math.cos(beta)));
15
}this.shoeHeightEqn = function(shoeHeight) {
var SLH = getVal('seatedLegHeight ');
return SLH + shoeHeight + 2.;
}
});
app.config(function($stateProvider , $urlRouterProvider) {
$urlRouterProvider.otherwise('/personal ');
$stateProvider.state('personal ', {
url: '/personal ',
views: {
personal: {
templateUrl: 'personal.html ',
controller: 'personalController '
}
}
});
$stateProvider.state('results ', {
url: '/results ',
views: {
results: {
templateUrl: 'results.html ',
controller: 'resultsController '
}
}
});
$stateProvider.state('light ', {
url: '/light ',
views: {
light: {
templateUrl: 'light.html ',
controller: 'lightController '
}
}
});
});
A.3.3 lightController.js
angular.module('ergoApp.controllers ')
.controller (" lightController", function($scope) {
$scope.measureLight = function () {
AmbientLightPlugin.start();
AmbientLightPlugin.getValue( function(success) {
$scope.lightLevel = success.substring (0, success.length - 2);
$scope.$apply ();
}, function(error) {
alert(error);
});
AmbientLightPlugin.stop();
};
$scope.measureLight = function () { return 600; };
$scope.lightLevel = $scope.measureLight ();
$scope.dayLevels = true;
$scope.nightThreshold = { 'low ' : 80, 'high ' : 140 };
$scope.dayThreshold = { 'low ' : 750, 'high ' : 1000 };
$scope.threshold = $scope.dayThreshold;
});
A.3.4 personalController.js
// Helper functions
function parseAccel(accel) {
return { x : parseFloat(accel.x), y : parseFloat(accel.y), z : parseFloat(accel.z) };
}
function accelComponentsLessThan(accel , threshold) {
if (Math.abs(accel.x)
}initAccel.x /= 10;
initAccel.y /= 10;
initAccel.z /= 10;
accels.push(parseAccel(accels[accels.length - 1]));
accels.unshift(parseAccel(accels [0]));
for (i = 0; i < accels.length; i++) {
accels[i] = parseAccel(accels[i]);
accels[i].x -= initAccel.x;
accels[i].y -= initAccel.y;
accels[i].z -= initAccel.z;
accels[i] = accelComponentsLessThan(accels[i], 0.1);
}
for (i = 1; i < accels.length - 1; i++) {
var thisAccel = accels[i];
if (i > 10 && i < accels.length - 2) {
thisAccel.x = gauss [0]* accels[i-2].x + gauss [1]* accels[i-1].x + gauss [2]* accels[i].x
+ gauss [3]* accels[i+1].x + gauss [4]* accels[i+2].x;
thisAccel.y = gauss [0]* accels[i-2].y + gauss [1]* accels[i-1].y + gauss [2]* accels[i].y
+ gauss [3]* accels[i+1].y + gauss [4]* accels[i+2].y;
thisAccel.z = gauss [0]* accels[i-2].z + gauss [1]* accels[i-1].z + gauss [2]* accels[i].z
+ gauss [3]* accels[i+1].z + gauss [4]* accels[i+2].z;
}
vs.push( { x : vs[i-1].x + thisAccel.x * timestep ,
y : vs[i-1].y + thisAccel.y * timestep ,
z : vs[i-1].z + thisAccel.z * timestep } );
rs.push( { x : rs[i-1].x + vs[i].x * timestep + 0.5 * thisAccel.x * timestep * timestep ,
y : rs[i-1].y + vs[i].y * timestep + 0.5 * thisAccel.y * timestep * timestep ,
z : rs[i-1].z + vs[i].z * timestep + 0.5 * thisAccel.z * timestep * timestep }
);
}
var e = rs.length - 1;
return (115* Math.max(Math.abs(rs[e].x), Math.abs(rs[e].y), Math.abs(rs[e].z))).toFixed (2);
};
});
A.3.5 resultsController.js
function getOrdinal(n) {
var s=["th","st","nd","rd"],
v=n%100;
return n+(s[(v-20) %10]||s[v]||s[0]);
}
angular.module('ergoApp.controllers ')
.controller (" resultsController", function($scope , personalParameters ,
healthTargets
,
measurements
)
{
personalParameters.loadParams ();
var height = personalParameters.getParam('height ');
var gender = personalParameters.getParam('gender ');
measurements.calc(height.val , gender.val.charAt (0));
$scope.measurements = measurements.getMeasurements ();
var percentile = measurements.getPercentile ();
$scope.heightPercentileString = getOrdinal(percentile.toFixed ());
});
18
A.3.6 style.css
.traffic {
opacity: 1.0 !important;
cursor: default !important;
pointer -events: none !important;
}
.traffic.traffic -off {
background: #f6f6f6;
color: #cecece;
}
.traffic.traffic -red {
background: #D63030;
color: #400 E0E;
}
.traffic.traffic -amber {
background: #FFBF00;
color: #473600;
}
.traffic.traffic -green {
background: #33 AD5C;
color: #0 F341C;
}
Appendix B Mathematica
B.1 percentileInterpolations.nb
[next page]
B.2 burg.nb
[2nd page from here]
19
percentiles = {1, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99};heightsM ={percentiles, {156.718, 161.544, 163.83, 167.64, 169.672, 171.704, 173.482,
174.752, 177.038, 179.324, 182.372, 184.912, 189.484}};heightsF = {percentiles, {145.034, 149.86, 151.892, 155.194, 156.972, 158.496,
159.766, 161.798, 163.576, 165.354, 168.656, 170.434, 174.752}};poplitealsM = {percentiles, {37.846, 39.37, 40.64, 41.656, 42.418,
43.18, 43.942, 44.704, 45.212, 46.228, 47.752, 49.022, 50.8}};poplitealsF = {percentiles, {33.274, 35.56, 36.068, 37.338, 38.354,
39.116, 39.878, 40.64, 41.402, 42.164, 43.18, 44.45, 45.72}};buttockPopM = {percentiles, {41.91, 43.942, 45.466, 46.736, 47.752,
48.768, 49.53, 50.292, 51.054, 52.07, 53.34, 54.864, 57.658}};buttockPopF = {percentiles, {40.894, 43.18, 43.942, 45.466, 46.228,
47.244, 48.006, 48.768, 49.53, 50.546, 52.324, 53.34, 55.88}};slhM = {percentiles, {46.482, 49.022, 50.8, 51.816, 52.578, 53.594,
54.356, 55.118, 55.88, 56.896, 58.166, 59.436, 61.214}};slhF = {percentiles, {43.434, 45.466, 46.228, 47.244, 48.514, 49.022,
49.784, 50.292, 51.054, 52.07, 53.34, 54.61, 56.896}};uahM = {{5, 10, 15, 25, 50, 75, 85, 90, 95},{13.9, 14.2, 14.4, 14.8, 15.4, 16.0, 16.3, 16.5, 16.9} * 2.54};uahF = {{5, 10, 15, 25, 50, 75, 85, 90, 95},{12.8, 13.0, 13.2, 13.5, 14.1, 14.6, 15.0, 15.2, 15.5} * 2.54};allMetrics = {heightsM, heightsF, poplitealsM, poplitealsF,
buttockPopM, buttockPopF, slhM, slhF, uahM, uahF};fits = Fit#, 1, x, x2, x3, x & /@ allMetrics;CoefficientList[fits, x]Show[Plot[#1, {x, 0, 100}], ListPlot[#2, PlotStyle PointSize[Medium]],
PlotRange All] & /@ ({fits, allMetrics})sshM = {{5, 50, 95}, {19.5, 20.3, 21.4} * 2.54};sshF = {{5, 50, 90}, {19.1, 19.8, 20.5} * 2.54};mapSparseData[sparse_, mapFit_] :=
Max@sparseAll, 2 - Min@sparseAll, 2(mapFit /. x sparse-1, 1) - (mapFit /. x sparse1, 1) mapFit + a /.a ArgMin[#, a] &@
Total Max@sparseAll, 2 - Min@sparseAll, 2(mapFit /. x sparse-1, 1) - (mapFit /. x sparse1, 1) mapFit +a - y 2 /. {x sparseAll, 1, y sparseAll, 2} // Expand
mappedSshs = {mapSparseData[sshM, fits[[1]]], mapSparseData[sshF, fits[[2]]]};Show[Plot[#1, {x, 0, 100}, ImageSize Medium],
ListPlot[#2, PlotStyle PointSize[Large]],PlotRange All] & /@ ({mappedSshs, {sshM, sshF}})
CoefficientList[mappedSshs, x]mh = fits1Plot[mh, {x, 0, 100}]Discriminant[mh, x]Solve[mh y, x]3
zi[arr_List, idx_Integer] := arridx + 1burgMethod[input_List, m_Integer] :=Module{n = Length@input - 1, coeffs = Prepend[ConstantArray[0., m], 1.],
f = input, b = input, d, , t1, t2}, d = i=0n 2 zi[f, i]2 - zi[f, 0]2 - zi[b, n]2;
Fork = 0, k < m, k++, = -2
d
i=0n-k-1 zi[f, i + k + 1] zi[b, i];Fori = 0, i 1
2(k + 1), i++,
t1 = zi[coeffs, i] + zi[coeffs, k + 1 - i];t2 = zi[coeffs, k + 1 - i] + zi[coeffs, i];coeffs[[i + 1]] = t1;coeffs[[k + 2 - i]] = t2;;
For[i = 0, i n - k - 1, i++,t1 = zi[f, i + k + 1] + zi[b, i];t2 = zi[b, i] + zi[f, i + k + 1];f[[i + k + 2]] = t1;b[[i + 1]] = t2;];
d = 1 - 2 d - zi[f, k + 1]2 - zi[b, n - k - 1]2;;Return[coeffs]
data = Table[2 Sin[0.2 n] + Sin[0.5 n] + RandomReal[{-1, 1}], {n, 0, 499}]bd = Drop[burgMethod[data, 20], 1]armaExpansion[coeffs_List, f_, t_Real: 1.] :=
1 +k=1Length@coeffs (coeffsk Exp[-2 f k t]);
burgPsd[coeffs_, f_, t_Real: 1.] := tAbs[armaExpansion[coeffs, f, t]]2
discreteArma[coeffs_List, nfft_Integer] :=Fourier[Evaluate@PadRight[coeffs, nfft, 0.], FourierParameters {1, -1}];
discretePsd[coeffs_List] := 1Abs[discreteArma[coeffs, 500]]2
myburg = burgPsd[bd, f / 2] // N;LogPlot[myburg, {f, 0, 1}, PlotRange All]dburg = discretePsd[burgMethod[data, 20]];ListLinePlot[dburg]
Appendix C Arduino
C.1 Flex App
C.1.1 BendSensor.ino
#include
BendSensor* sensor;
boolean calibrationOn = false;
boolean countingOn = false;
void setup () {
Serial.begin (9600);
sensor = new BendSensor(A0 , 10);
}
void loop() {
// Read current value
int bendVal = sensor ->readValue ();
// If we're counting reps
if (countingOn) {
if (sensor ->movedPastThreshold ()) {
Serial.println ("REP");
sensor ->ledUp();
}
if (sensor ->movedBelowThreshold ()) {
sensor ->ledDown ();
}
}
// Return the current bend level if in an active state
if (calibrationOn || countingOn) Serial.println(bendVal);
}
void serialEvent () {
while (Serial.available ()) {
char serialChar = (char) Serial.read();
calibrationOn = serialChar == 'C';
countingOn = serialChar == 'S';
if (serialChar == 'F') sensor ->lockBendThresholdAtValue ();
}
}
C.1.2 BendSensor.cpp
#include "BendSensor.h"
BendSensor :: BendSensor(int pin , int ledPin) {
pin_ = pin;
ledPin_ = ledPin;
bendThreshold_ = 1023;
count_ = 0;
value_ = 0;
pinMode(ledPin_ , OUTPUT);
digitalWrite(ledPin_ , LOW);
}
void BendSensor :: incrementCount () {
count_ ++;
}
int BendSensor :: readValue () {
previousValue_ = value_;
value_ = analogRead(pin_);
delay (50);
return value_;
}
bool BendSensor :: movedPastThreshold () {
22
// the loop routine runs over and over again forever:
void loop() {
// read the input on analog pin 0:
int sensorValue = analogRead(A0);
// Convert the analog reading (which goes from 0 - 1023)
// to a voltage (0 - 5V):
float voltage = sensorValue * (5.0 / 1023.0);
// print out the final amplified EMG output signal:
Serial.println(voltage);
}
Appendix D Git Logs
D.1 Apps Repository
D.1.1 Log
* 679 acc5 : Added frequency slider and badges for sliders
* 184 a496 : Moved all measurements stuff to a service
* af1a7bc : Moved local storage save to services
* b7a159a : Updated app names and descriptions in config.xml files
* a6ac9a6 : Lightened Flex logo
* 68 ed767 : Added icons
* 34 ee270 : Added measurement equations service
* 214789d : Implemented percentile lookup in interface , and added diagram
* db35d22 : Further improvements to service model. Started results page
* 6039490 : Refactored healthTargets and parameters to be Angular services - can now share
between scopes
* 551739a : Merge branch 'percentile -interp '
|\
| * e6b7743 : Completed percentile interpolation and lookup code
| * 5412199 : Corrections in maths and added height inverse function
| * f329b0d : Hard -coded interpolation coeffs into JS
| * cce23b6 : Worked out interps in mathematica
|/
* 00 c4f18 : Almost completed discrete percentile data
* 779 bca0 : Moved ergonomics controllers into separate files
* 223 f78b : Changed 'desk ' to 'results '
* a607639 : Changed bend value to percentage
* 75 b9054 : Changed received BT string handling to work with Arduino
* 2ad44a8 : Added health targets form
* 035278d : Changed night/day to sleep/study and updated functionality to reflect this
* 1dc75f6 : Added night/day differentiation
* 0094 a16 : Added traffic lights scheme
* 580 d25f : EMG to BT mode
* 380035d : Completed PSD estimation capability
* fbabdd5 : Burg method proof of concept in Mathematica notebook
* 61 b6b43 : Bluetooth and interface basics for EMG app
* 456 a675 : Changed serial send strings , polished modal code , renamed controller
* ffda026 : Merge branch 'flex -app -full '
|\
| * 8a8d95f : Merge branch 'flex -app -graph ' into flex -app -full
| |\
| | * cfc7346 : Working graph
| | * b056a9d : Got basic angular -chart example working
| |/
| * 2880415 : Mostly completed flex app with reps progress bar
| * 5c0b41e : Added bend -threshold and target -reps sliders
| * 45099fb : Switched bluetooth interface modal relationship (more logical)
| * f10e907 : Actually fixed light sensor error
| * 416297c : Fixed error with light sensor (JS) code
| * 21c1ecb : Added light measuring tab
| * e6a6c5e : Mostly completed the personal details section. Includes save feature
| * 13146d0 : Created basic tabbed interface (no logic atm)
|/
* eb8990e : Fixed bluetooth comms
* 6c1749e : Updated ionicons css
* a31a061 : Mostly working bluetooth interface
* cdad5f5 : Removed echoTest
* 39 d8a99 : Created 2 new blank apps
* 65 d001e : Changed title in ergonomics app
* 444 a4a0 : Renamed from testProject
* 561 b207 : Wrote light sensor plugin , added to app
* d1e8e47 : Accelerometer measurement first commit
* 0edf519 : Added basic accelerometer demo project
24
* 0ac3b47 : Initial commit
D.1.2 Additions to Master
D.2 Hardware Repository
D.2.1 Log
* 24 f6d00 : Added EMG sketch
* 0a2fa63 : Fixed some errors and improved code
* 805 a460 : Updated sketch to (hopefully) work with the app
* eda30f4 : Added 50ms delay when reading values to eliminate voltage bounce
* 63 c4f37 : Merge branch 'bend -oop -refactor '
|\
| * 9087963 : Added include
* | 26f7673 : Merge pull request #2 from Team -Rocket -UCL/bend -oop -refactor
|\ \
| |/
| * 14861f5 : Fully separated implementation and definition in library
| * 4993 bfb : Wrote and added BendSensor library; the sketch should run as before , but the
code is more generalised
|/
* f439b5a : Committed the deletion
* 2e12d59 : Converted GitHub text files to Arduino IDE sketch folders
* 126 f3a9 : Create Bend sensor code
* 215 d6ab : Merge pull request #1 from Team -Rocket -UCL/Bluetooth -thing
|\
| * 248 dee4 : Create Bluetooth Calibration
|/
* 771844e : Initial commit
D.2.2 Additions to Master
25