Skip to content

Instantly share code, notes, and snippets.

@swashcap
Created February 5, 2016 22:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save swashcap/b5b9a3cf6b38583c303d to your computer and use it in GitHub Desktop.
Save swashcap/b5b9a3cf6b38583c303d to your computer and use it in GitHub Desktop.
COINSTAC Stuff
'use strict';
const _ = require('lodash');
const coinstacCommon = require('coinstac-common');
const coinstacAlgorithms = require('coinstac-distributed-algorithm-set');
const freesurferParser = require('freesurfer-parser');
const helpers = require('./path/to/helpers/');
const Analysis = coinstacCommon.Analysis;
const ComputationPipeline = coinstacCommon.ComputationPipeline;
const LocalComputation = coinstacCommon.LocalComputation;
const LocalFileComputation = coinstacCommon.LocalFileComputation;
const RemoteComputation = coinstacCommon.RemoteComputation;
/**
* Compute regression on the client.
*
* @param {array} xVals ?
* @param {array} yVals ?
* @param {array} aggregateMVals ?
* @param {string[]} roiKeys Targetted predictors
* @returns {object}
*/
function clientComputeRegression(xVals, yVals, aggregateMVals, roiKeys) {
// @TODO dep vars (control/patient) must have both types
const normalizedYVals = coinstacAlgorithms.utils.normalize(yVals);
const normalizedXVals = coinstacAlgorithms.utils.normalize(xVals);
const objectiveScore = coinstacAlgorithms.ridgeRegression.objective(
aggregateMVals,
normalizedXVals,
normalizedYVals
);
const predictedYVals = coinstacAlgorithms.ridgeRegression.applyModel(
aggregateMVals,
normalizedXVals
);
const gradient = coinstacAlgorithms.ridgeRegression.gradient(
aggregateMVals,
normalizedXVals,
normalizedYVals
);
return {
gradient: _.zipObject(roiKeys, gradient),
objective: coinstacAlgos.ridgeRegression.objective(
aggregateMVals,
normalizedXVals,
normalizedYVals
),
r2: utils.r2(normalizedYVals, predictedYVals),
};
}
module.exports = {
_id: 'freesurfer-multishot',
analysis: new Analysis({
name: 'FreeSurfer Multi-Shot',
definition: {
epsilon: 1,
roiKeys: ['Left-Hippocampus'],
tolerance: 1e-5,
},
description: 'This is a multi-iterative analysis based on…',
}),
consortium: 'left-hippocampus-buffs',
version: '1.0.0',
/**
* The parent `ComputationPipeline` will do a type check on all its
* arguments to determine their params and what to pass their functions.
*/
local: new ComputationPipeline([
/**
* Passes a set of files, set by the user via UI or command line.
*
* @param {object} definition Analysis's definition
* @param {string[]} files Collection of file paths
* @param {function} cb Callback for non-Promise folks
* @returns {Promise}
*/
new LocalFileComputation(function(definition, files, cb) {
return Promise.all(files.map(filePath => {
return new Promise((resolve, reject) => {
fs.readFileAsync(filePath, 'utf8', (err, data) => {
if (err) {
return reject(err);
}
resolve(new FreeSurfer({
string: data.toString(),
}));
}));
})
.then(rois => {
const roiKeys = definition.roiKeys;
return {
file: filePath,
/**
* Freesurfer files have regions of interest (ROIs)
* separated by line. Each line contains a ROI and
* its value, separated by a tab.
*
* @todo Come up with a Freesurfer parser helper
*/
rois: rois
.split('\n')
.reduce((all, line) => {
const pieces = line.split('\t');
if (roiKeys.indexOf(pieces[0]) !== -1) {
all[pieces[0]] = pieces[1];
}
return all;
}, {});
};
})
.then(thing => {
//TODO: ???
return computeRegression(
// xVals
// yVals
// aggregateMVal
definition.roiKeys
);
});
})
.then(results => {
cb(null, results);
return results;
}, error => {
cb(error);
throw error;
});
}),
/**
* Regular local computation. Use after extracting data from files.
*/
new LocalComputation(
function(definition, previousResult, remoteResult, next) {
return computeRegression(
// xVals
// yVals
remoteResult.mVals, // aggregateMVals
definition.roiKeys
);
}
),
]),
remote: new ComputationPipeline([
new RemoteComputation(
/**
* Get an initial, or seed, document for the server's analysis. This
* will get passed as `previousResult` to the `process` iteration
* below.
*
* @todo Consider moving this step to the analysis definition?
*
* @param {object} definition Analysis's definition
* @returns {object} Initial document
*/
getInitialDocument: function(definition) {
const result = {
gradient: {},
learningRate: 0.7,
mVals: {},
objective: Infinity,
r2: 0,
};
definition.roiKeys.forEach(key => {
result.gradient[key] = 0;
result.mVals[key] = Math.random();
});
return result;
},
/**
* Process
*
* @param {object} definition
* @param {object} previousResult Result of the previous server
* computation
* @param {object[]} clientResults Collection of results of clients'
* computations
* @param {function} next
*/
process: function(definition, previousResult, clientResults, next) {
const roiKeys = definition.roiKeys;
const aggregateObjective =
helpers.sum(helpers.getObjectiveValues(clientResults));
const aggregateGradient = coinstacAlgorithms.utils.columnWiseSum(
helpers.getGradientValues(clientResults, roiKeys)
);
const gradient = helpers.zipRoiKeyPairs(
aggregateGradient,
roiKeys
);
const learningRate = previousResult.learningRate;
const previousBestFit = previousResult.previousBestFit;
const history = aggregateDoc.history || [];
if (
numeric.norm2(helpers.unzipRoiKeyPairs(gradient, roiKeys)) <
definition.tolerance
) {
return next();
}
if (aggregateObjective > previousBestFit.objective) {
learningRate /= 2;
}
return {
gradient: gradient,
learningRate: learningRate,
mVals: helpers.zipRoiKeyPairs(
coinstacAlgorithms.ridgeRegression.recalculateMVals(
learningRate,
helpers.unzipRoiKeyPairs(
previousBestFit.mVals,
roiKeys),
helpers.unzipRoiKeyPairs(
previousBestFit.gradient,
roiKeys
)
),
roiKeys
),
objective: aggregateObjective,
r2: helpers.mean(_.map(clientResults, 'r2')),
};
}
),
]),
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment