diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 9186d475..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,27 +0,0 @@ -machine: - node: - version: 6.3.1 - hosts: - localhost: 127.0.0.1 -jobs: - build: - docker: - - image: circleci/node:6.10.3 - - image: circleci/mongo:3.4.4 - steps: - - checkout - - run: npm install - - run: - name: Instantiate github-keys.json - command: 'echo "{ \"clientID\": \"$GITHUB_CLIENT_ID\", \"clientSecret\": \"$GITHUB_CLIENT_SECRET\", \"callbackURL\": \"$GITHUB_CALLBACK_URL\"}">github-keys.json' - - run: - name: Instantiate blacklist - command: 'mv blacklist.json.example blacklist.json' - - run: - name: Instantiate whitelist - command: 'echo "{}" > whitelist.json' - - run: - name: Run server in background - command: 'npm start' - background: true - - run: sleep 5 diff --git a/.gitignore b/.gitignore index 396c689d..599af78b 100644 --- a/.gitignore +++ b/.gitignore @@ -19,9 +19,6 @@ js/sample-001.nii.gz js/sample-002.mgz js/sample-002.nii.gz public/.DS_Store -public/doc2/* -public/nihpd-plane/* .DS_Store -package-lock.json js/.DS_Store diff --git a/ANNOTATIONS.md b/ANNOTATIONS.md deleted file mode 100644 index 878c0082..00000000 --- a/ANNOTATIONS.md +++ /dev/null @@ -1,20 +0,0 @@ -Come to our [**dolphin brain**](http://brainbox.pasteur.fr/mri?url=http://braincatalogue.org/data/Bottlenose_dolphin/MRI-n4.nii.gz) , (log in with your GitHub account) and select the Cerebrum from the Braincatalogue project and **join our segmentation sprint!** - -screen shot 2017-06-01 at 11 08 34 - -When you have done 1 slice, -* add your name or github handle to our BRAINMAPPERS.md file -* add the slice number where you have been working (currently, 111 & 112 are done, you could continue with 113++) -* add "length:..." and -* "volume:..." from the upper left corner of the BrainBox viewer into the file (just for the fun!! :D) -* and make a pull request! - -The best devices for drawing over the brain are tablets with pens. With a computer mouse or trackpad it is a bit more tedious but works very fine as well 😃 Looking forward to seeing you! - -Just say hi in the BrainBox chat and we will guide you to the view where we are currently working. - -We will work in an axial slice (113++) and erase all the parts of the mask that cover the sulci of the brain. You see the impressively folded structure of the dolphin brain. The sulci, which are the valleys of the folds, appear darker. We erase them from the mask to being able to reconstruct the 3D surface preserving the folding. -**It's fun! Join us! :)** Careful! It's addictive! <3 😄 - -![gyrus_sulcus](https://cloud.githubusercontent.com/assets/6297454/26672808/de7c347c-46ba-11e7-9f19-01fef1da9295.png) - diff --git a/BRAINMAPPERS.md b/BRAINMAPPERS.md deleted file mode 100644 index 36f21c1d..00000000 --- a/BRAINMAPPERS.md +++ /dev/null @@ -1,23 +0,0 @@ -We are the brain mappers! \ö/ - -Here, we are segmenting an amazingly folded [**dolphin brain**](http://brainbox.pasteur.fr/mri?url=http://braincatalogue.org/data/Bottlenose_dolphin/MRI-n4.nii.gz). Once you clicked the link, you will find our work in 'Cerebrum' annotation layer from the Braincatalogue project. You can **join our segmentation sprint any time!** You can also choose a different species to work on, if you prefer! Any contributions are featured below. - -screen shot 2017-06-01 at 11 08 34 - -**Our BrainMapper team** -current Volume segmented: **509 491 mm3** (please update! :D) - -Name: ** ** -GitHub: -Species: -Slice numbers: -Length: mm -Volume: mm3 - -Name: **katja** -GitHub: katjaq -Species: Bottlenose dolphin -Slice numbers: axial 112 -Length: mm -Volume: 509491 mm3 - diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index 749155c4..00000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,27 +0,0 @@ -## Hi! Thank you for getting back to us! <3 - -### Is this a feature request or a bug report? -* Please choose one of the labels on the right side and tag your issue :) - -### What is the current behaviour? - -* If this is a bug, please explain how to reproduce the problem - * Which tools did you use in which sequence? - * Is the error concerning a text or a volume annotation? - * Which label set has been used for the volume annotation (if applicable)? - * What is the type and resolution of your data? (Can you provide a link to your data?) - * Do not include sensitive information. - * Upload the browser error message if you are familiar with web developer tools. - - -### What is the expected or desired behaviour? - -### Version information (for bug reports) - -* **Browser + version**: -* **Your OS and version**: -* Paste the output of these commands: -``` -node --version && npm --version -``` - diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 194c37d7..00000000 --- a/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,79 +0,0 @@ - - - - - ---- - -- [ ] These changes fix #__ (github issue number if applicable). -- [ ] All BrainBox tools behave as expected: - * **KEYS** - * right and left arrow keys - - [ ] jump to next or previous slice within one brain, respectively - - [ ] update the slider accordingly - - [ ] update the slice number accordingly (upper left corner of the viewer window) - * down and up arrow keys - - [ ] jump to the next or previous brain within one project, respectively - - [ ] update the selected subject in the annotation table - * **TOOL BUTTONS** - * minus - - [ ] jumps to the previous slice within one brain - - [ ] updates the slider accordingly - - [ ] updates the slice number accordingly (upper left corner of the viewer window) - * plus - - [ ] jumps to the next slice within one brain - - [ ] updates the slider accordingly - - [ ] updates the slice number accordingly (upper left corner of the viewer window) - * slider - - [ ] updates slice view and slice number on the fly - * sag / cor / axi buttons - - [ ] switch view between the three orthogonal planes - * show tool - - [ ] when you click and drag in your browser window, this tool displays a cirlce at the position of your mouse click & drag as well as the user name in all browser windows connected to the same brain - * the numbers at the bottom of the tool panel - - [ ] change pencil size and eraser size accordingly - * pencil tool - - [ ] draws a line in the colour displayed in the color field - - [ ] in combination with bucket tool filles a complete area with the chosen colour (be sure to have closed the contour line ;) Otherwise, the undo button will be your friend ;) - - [ ] updates length and volume information (of what has been segmented) in the upper left corner of the viewer - * erase tool - - [ ] erases upon click drag from the annotation - - [ ] in combination with the bucket tool erases the complete area of the color where you click - * fill bucket tool - * in combination with pencil tool - - [ ] fills a complete area with the colour displayed in the colour field - * in combination with erase tool - - [ ] erases the complete area that is filled by the colour of where you click - * colour field - - [ ] displays the currently chosen colour to draw and fill - - [ ] on click opens the set of colours available within the chosen label set where a new colour can be selected upon click - * ruler tool - - [ ] measures the distance between start and end of your defined path - - [ ] points of mouse click appear and stay visible until you hit return key (this functionality is currently broken!) (you can click as many points as you wish to define the path you are interested in) - - [ ] on return key, BrainBox will print the distance into the chat field (the ruler tool seems to be currently broken!!!) - * adjust tool - - [ ] slide opacity of overlaid annotation from 0 to 100% - - [ ] increase or decrease brightness of the underlying MRI data - - [ ] increase or decrease the contrast of the underlying MRI data - * eyedropper tool - - [ ] updates the colour field in the tool panel - - [ ] displays/updates the region name in the upper left corner of the viewer - * undo tool - - [ ] undoes the user's actions in reverse chronological order and currently has the bug that it even undoes actions in slices you are currently not seeing! and there is currently no redo... - * save button - - [ ] saves the annotation of the data set into the data base - - [ ] displays a message that user needs to login in case they are not (CHECK!) - - [ ] display a message `Atlas saved Wed Oct 18 2017 12:49:12 GMT+0200 (CEST)` - - - - -- [ ] I implemented tests for these changes OR -- [ ] These changes do not require tests because _____ - - - - - - - diff --git a/README.md b/README.md index 084898ff..741de512 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,18 @@ -[![CircleCI](https://circleci.com/gh/OpenNeuroLab/BrainBox/tree/master.svg?style=shield)](https://circleci.com/gh/OpenNeuroLab/BrainBox/tree/master) [![Join the chat at https://gitter.im/OpenNeuroLab/BrainBox](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/OpenNeuroLab/BrainBox?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - # BrainBox - An application from the [Open Neuroimaging Laboratory](http://openneu.ro/) -BrainBox is a web application that lets you annotate and segment 3D brain -imaging data in real time, collaboratively. You can try it +BrainBox is a web application that lets you annotate and segment 3D brain +imaging data in real time, collaboratively. You can try it at http://brainbox.pasteur.fr. BrainBox is a web application to share, visualise and annotate MRI brain - data collaboratively. BrainBox will provide the means to create a - layer of collaborative annotation over all the available MRI data without - having to rely on a centralised data repository or the necessity of + data collaboratively. BrainBox will provide the means to create a + layer of collaborative annotation over all the available MRI data without + having to rely on a centralised data repository or the necessity of having to install software. -You can try it at http://brainbox.pasteur.fr. Log in with your github -account and enter the URL to an MRI file you want to visualise, annotate +You can try it at http://brainbox.pasteur.fr. Log in with your github +account and enter the URL to an MRI file you want to visualise, annotate and edit. You can also click one of the examples. @@ -25,9 +23,10 @@ If you want to work on BrainBox's code, you'll need a local installation: ## Using Docker to install and run BrainBox 1. `git clone` this repository or download it -2. `mv BrainBox brainbox` to rename the directory +2. `mv BrainBox brainbox` to rename the directory 3. `cd` to brainbox -4. [create a new OAuth application](https://github.com/settings/applications/new) for your local brainbox url (http://localhost:3000 by default) +4. get Github developer keys for your local brainbox url +(http://localhost:3000 by default) 5. paste the keys into the github-keys.json.example file, change the `callbackURL` to `"http://localhost:3000/auth/github/callback"` and drop the .example 6. drop the `.example` from `blacklist.json.example` 7. drop the `.example` from `whitelist.json.example` @@ -40,11 +39,10 @@ If you want to work on BrainBox's code, you'll need a local installation: 1. install and start `mongo` database 2. clone the repo and `cd` to the brainbox directory 3. `mkdir public/data` -4. [create a new OAuth application](https://github.com/settings/applications/new) for your local brainbox url (http://localhost:3000 by default) +4. get Github developer keys for your local brainbox url +(http://localhost:3000 by default) 5. paste the keys into the github-keys.json.example file, and drop the .example -6. drop the `.example` from `blacklist.json.example` -7. drop the `.example` from `whitelist.json.example` -8. `npm install` -9. `npm start` -10. To lint your files use `npm test`, you can use `xo --fix` to fix common mistakes, before committing, -to do that install `xo` globally using `npm i -g xo` +6. `npm install` +7. `npm start` + + diff --git a/app.js b/app.js index 4ec879e2..91b8dd69 100644 --- a/app.js +++ b/app.js @@ -1,120 +1,113 @@ -'use strict'; +"use strict"; /* Atlas Maker Server Roberto Toro, 25 July 2014 - + Launch using > node atlasMakerServer.js */ -const debug = 1; - -const fs = require('fs'); -const express = require('express'); -const path = require('path'); -const favicon = require('serve-favicon'); -const logger = require('morgan'); -const cookieParser = require('cookie-parser'); -const bodyParser = require('body-parser'); -const mustacheExpress = require('mustache-express'); -const crypto = require('crypto'); -const request = require('request'); -const url = require('url'); -const async = require('async'); -const mongo = require('mongodb'); -const monk = require('monk'); - -let MONGO_DB; -const DOCKER_DB = process.env.DB_PORT; -const DOCKER_DEVELOP = process.env.DEVELOP; - -if (DOCKER_DB) { - MONGO_DB = DOCKER_DB.replace('tcp', 'mongodb') + '/brainbox'; +var debug = 1; + +var express = require('express'); +var path = require('path'); +var favicon = require('serve-favicon'); +var logger = require('morgan'); +var cookieParser = require('cookie-parser'); +var bodyParser = require('body-parser'); +var mustacheExpress = require('mustache-express'); +var crypto = require('crypto'); +var request = require("request"); +var url = require("url"); +var async = require("async"); +var mongo = require('mongodb'); +var monk = require('monk'); + +var MONGO_DB; +var DOCKER_DB = process.env.DB_PORT; +var DOCKER_DEVELOP = process.env.DEVELOP; + +if ( DOCKER_DB ) { + MONGO_DB = DOCKER_DB.replace( 'tcp', 'mongodb' ) + '/brainbox'; } else { - MONGO_DB = 'localhost:27017/brainbox'; // Process.env.MONGODB; + MONGO_DB = 'localhost:27017/brainbox'; //process.env.MONGODB; } -const db = monk(MONGO_DB); -const expressValidator = require('express-validator'); +var db = monk(MONGO_DB); +var fs = require('fs'); +var expressValidator = require('express-validator'); -const atlasMakerServer = require('./js/atlasMakerServer.js'); +var atlasMakerServer = require('./js/atlasMakerServer.js'); -// Init web server -// var routes = require('./routes/index'); +// init web server +//var routes = require('./routes/index'); // var users = require('./routes/users'); -/* jslint nomen: true */ -const dirname = __dirname; // Local directory -/* jslint nomen: false */ +/*jslint nomen: true*/ +var dirname = __dirname; // local directory +/*jslint nomen: false*/ -if (DOCKER_DEVELOP == '1') { - const livereload = require('livereload'); +if ( DOCKER_DEVELOP == '1' ) { + var livereload = require('livereload'); // Create a livereload server const hotServer = livereload.createServer({ // Reload on changes to these file extensions. - exts: ['json', 'mustache'], + exts: [ 'json', 'mustache' ], // Print debug info - debug: true + debug: true }); // Specify the folder to watch for file-changes. hotServer.watch(__dirname); - console.log('Watching: ' + __dirname); + console.log('Watching: ' + __dirname) } -const app = express(); +var app = express(); app.engine('mustache', mustacheExpress()); app.set('views', path.join(dirname, 'views')); app.set('view engine', 'mustache'); app.use(favicon(dirname + '/public/favicon.png')); app.set('trust proxy', 'loopback'); -app.use(logger(':remote-addr :method :url :status :response-time ms - :res[content-length]'));// App.use(logger('dev')); +app.use(logger(':remote-addr :method :url :status :response-time ms - :res[content-length]'));//app.use(logger('dev')); app.use(bodyParser.json({limit: '50mb'})); app.use(bodyParser.urlencoded({limit: '50mb', extended: true})); app.use(expressValidator()); app.use(cookieParser()); app.use(express.static(path.join(dirname, 'public'))); -if (DOCKER_DEVELOP == '1') { +if ( DOCKER_DEVELOP == '1') { app.use(require('connect-livereload')()); } -app.use((req, res, next) => { +app.use(function (req, res, next) { req.dirname = dirname; req.db = db; - req.tokenDuration = 24 * (1000 * 3600); // Token duration in milliseconds + req.tokenDuration = 24 * (1000 * 3600); // token duration in milliseconds next(); }); -// App.use('/', routes); +//app.use('/', routes); // app.use('/users', users); -// {-----passport -const session = require('express-session'); -const passport = require('passport'); -const GithubStrategy = require('passport-github').Strategy; - +//{-----passport +var session = require('express-session'); +var passport = require('passport'); +var GithubStrategy = require('passport-github').Strategy; passport.use(new GithubStrategy( - JSON.parse(fs.readFileSync(dirname + '/github-keys.json')), - (accessToken, refreshToken, profile, done) => { - return done(null, profile); - } + JSON.parse(fs.readFileSync(dirname + "/github-keys.json")), + function (accessToken, refreshToken, profile, done) {return done(null, profile); } )); app.use(session({ - secret: 'a mi no me gusta la sémola', + secret: "a mi no me gusta la sémola", resave: false, saveUninitialized: false })); app.use(passport.initialize()); app.use(passport.session()); -// Add custom serialization/deserialization here (get user from mongo?) null is for errors -passport.serializeUser((user, done) => { - done(null, user); -}); -passport.deserializeUser((user, done) => { - done(null, user); -}); +// add custom serialization/deserialization here (get user from mongo?) null is for errors +passport.serializeUser(function (user, done) {done(null, user); }); +passport.deserializeUser(function (user, done) {done(null, user); }); // Simple authentication middleware. Add to routes that need to be protected. function ensureAuthenticated(req, res, next) { if (req.isAuthenticated()) { @@ -122,115 +115,111 @@ function ensureAuthenticated(req, res, next) { } res.redirect('/'); } -app.get('/secure-route-example', ensureAuthenticated, (req, res) => { - res.send('access granted'); -}); -app.get('/logout', (req, res) => { +app.get('/secure-route-example', ensureAuthenticated, function (req, res) {res.send("access granted"); }); +app.get('/logout', function (req, res) { req.logout(); res.redirect(req.session.returnTo || '/'); delete req.session.returnTo; }); -app.get('/loggedIn', (req, res) => { +app.get('/loggedIn', function (req, res) { if (req.isAuthenticated()) { res.send({loggedIn: true, username: req.user.username}); } else { res.send({loggedIn: false}); } }); -// Start the GitHub Login process -app.get('/auth/github', passport.authenticate('github')); +// start the GitHub Login process +app.get('/auth/github',passport.authenticate('github')); app.get('/auth/github/callback', passport.authenticate('github', {failureRedirect: '/'}), - (req, res) => { - // Successfully loged in. Check if user is new - db.get('user').findOne({nickname: req.user.username}, '-_id') - .then(json => { + function (req, res) { + // successfully loged in. Check if user is new + db.get('user').findOne({nickname: req.user.username}, "-_id") + .then(function (json) { if (!json) { - // Insert new user + // insert new user json = { name: req.user.displayName, nickname: req.user.username, url: req.user._json.blog, - brainboxURL: '/user/' + req.user.username, + brainboxURL: "/user/" + req.user.username, avatarURL: req.user._json.avatar_url, joined: (new Date()).toJSON() }; db.get('user').insert(json); } else { - console.log('Update user data from GitHub'); - db.get('user').update({nickname: req.user.username}, {$set: { + console.log("Update user data from GitHub"); + db.get('user').update({nickname: req.user.username},{$set:{ name: req.user.displayName, url: req.user._json.blog, avatarURL: req.user._json.avatar_url }}); } }); - res.redirect(req.session.returnTo || '/'); - delete req.session.returnTo; + res.redirect(req.session.returnTo || '/'); + delete req.session.returnTo; }); -// -----} +//-----} global.tokenAuthentication = function (req, res, next) { - console.log('>> Check token'); - let token; - if (req.params.token) { + console.log(">> Check token"); + var token; + if(req.params.token) token = req.params.token; - } - if (req.query.token) { + if(req.query.token) token = req.query.token; - } - if (!token) { - console.log('>> No token'); + if(!token) { + console.log(">> No token"); next(); return; } - req.db.get('log').findOne({token}) - .then(obj => { - if (obj) { + req.db.get("log").findOne({"token":token}) + .then(function (obj) { + if(obj) { // Check token expiry date - const now = new Date(); - if (obj.expiryDate.getTime() - now.getTime() < req.tokenDuration) { - console.log('>> Authenticated by token'); + var now = new Date(); + if(obj.expiryDate.getTime()-now.getTime() < req.tokenDuration) { + console.log(">> Authenticated by token"); req.isTokenAuthenticated = true; req.tokenUsername = obj.username; } else { - console.log('>> Token expired'); + console.log(">> Token expired"); } } next(); }) - .catch(err => { - console.log('ERROR:', err); + .catch(function(err) { + console.log("ERROR:",err); next(); }); -}; +} + // GUI routes -app.get('/', (req, res) => { // /auth/github - const login = (req.isAuthenticated()) ? - ('' + req.user.username + ' (Log Out)') : - ('Log in with GitHub'); +app.get('/', function (req, res) { // /auth/github + var login = (req.isAuthenticated()) ? + ("" + req.user.username + " (Log Out)") + : ("Log in with GitHub"); - // Store return path in case of login + // store return path in case of login req.session.returnTo = req.originalUrl; res.render('index', { title: 'BrainBox', - login + login: login }); }); + app.use('/mri', require('./controller/mri/')); app.use('/project', require('./controller/project/')); app.use('/user', require('./controller/user/')); -app.get('/api/getLabelsets', (req, res) => { - let i, - arr = fs.readdirSync(dirname + '/public/labels/'), - info = []; +app.get('/api/getLabelsets', function (req, res) { + var i, arr = fs.readdirSync(dirname + "/public/labels/"), info = []; for (i in arr) { - const json = JSON.parse(fs.readFileSync(dirname + '/public/labels/' + arr[i])); + var json = JSON.parse(fs.readFileSync(dirname + "/public/labels/" + arr[i])); info.push({ name: json.name, source: arr[i] @@ -238,35 +227,35 @@ app.get('/api/getLabelsets', (req, res) => { } res.send(info); }); -app.post('/api/log', (req, res) => { - const loggedUser = req.isAuthenticated() ? req.user.username : 'anonymous'; - const json = req.body; - let obj; - - switch (json.key) { - case 'annotationLength': +app.post('/api/log', function (req, res) { + var loggedUser = req.isAuthenticated()?req.user.username:"anonymous"; + var json = req.body; + var obj; + + switch(json.key) { + case "annotationLength": obj = { - key: 'annotationLength', + key: "annotationLength", username: loggedUser, - 'value.source': json.value.source, - 'value.atlas': json.value.atlas + "value.source": json.value.source, + "value.atlas": json.value.atlas }; req.db.get('log').findOne(obj) - .then(result => { - let length = 0; - if (result) { + .then(function(result) { + var length = 0; + if(result) { length = parseFloat(result.value.length); } - const sum = parseFloat(json.value.length) + length; - req.db.get('log').update(obj, {$set: { - 'value.length': sum, + var sum = parseFloat(json.value.length) + length; + req.db.get('log').update(obj,{$set:{ + "value.length":sum, date: (new Date()).toJSON() }}, {upsert: true}); res.send({length: sum}); }) - .catch(err => { - console.log('ERROR', err); - res.send({error: JSON.stringify(err)}); + .catch(function(err) { + console.log("ERROR",err); + res.send({error:JSON.stringify(err)}); }); break; default: @@ -285,48 +274,49 @@ app.post('/api/log', (req, res) => { req.db.get('mri').update({ source: json.value.source, - 'mri.atlas': {$elemMatch: {filename: json.value.atlas}} + "mri.atlas":{$elemMatch:{filename:json.value.atlas}} }, { $set: { - 'mri.atlas.$.modified': (new Date()).toJSON(), - 'mri.atlas.$.modifiedBy': loggedUser + "mri.atlas.$.modified": (new Date()).toJSON(), + "mri.atlas.$.modifiedBy": loggedUser } }); }); -// Init web socket server + +// init web socket server atlasMakerServer.initSocketConnection(); -atlasMakerServer.dataDirectory = dirname + '/public'; - -// Check that the 'anyone' user exists. Insert it otherwise -db.get('user').findOne({nickname: 'anyone'}) - .then(obj => { - if (!obj) { - const anyone = { - name: 'Any BrainBox User', - nickname: 'anyone', - brainboxURL: '/user/anyone', - joined: (new Date()).toJSON() +atlasMakerServer.dataDirectory = dirname + "/public"; + +// check that the 'anyone' user exists. Insert it otherwise +db.get('user').findOne({nickname:'anyone'}) + .then(function(obj) { + if(!obj) { + var anyone = { + name:'Any BrainBox User', + nickname:'anyone', + brainboxURL:'/user/anyone', + joined:(new Date()).toJSON() }; - console.log('WARNING: \'anyone\' user absent: inserting it'); + console.log("WARNING: 'anyone' user absent: inserting it"); db.get('user').insert(anyone); } else { - console.log('\'anyone\' user correctly configured.'); + console.log("'anyone' user correctly configured."); } }); -// Catch 404 and forward to error handler -app.use((req, res, next) => { - const err = new Error('Not Found'); +// catch 404 and forward to error handler +app.use(function (req, res, next) { + var err = new Error('Not Found'); err.status = 404; next(err); }); -// Error handlers +// error handlers // development error handler // will print stacktrace if (app.get('env') === 'development') { - app.use((err, req, res, next) => { + app.use(function (err, req, res, next) { res.status(err.status || 500); res.render('error', { message: err.message, @@ -334,9 +324,9 @@ if (app.get('env') === 'development') { }); }); } -// Production error handler +// production error handler // no stacktraces leaked to user -app.use((err, req, res, next) => { +app.use(function (err, req, res, next) { res.status(err.status || 500); res.render('error', { message: err.message, @@ -344,4 +334,4 @@ app.use((err, req, res, next) => { }); }); -module.exports = app; +module.exports = app; \ No newline at end of file diff --git a/bin/www b/bin/www index e72097d7..0f2543e3 100755 --- a/bin/www +++ b/bin/www @@ -1,9 +1,9 @@ #!/usr/bin/env node -const debug = require('debug')('xt'); -const app = require('../app'); +var debug = require('debug')('xt'); +var app = require('../app'); app.set('port', process.env.PORT || 3000); -const server = app.listen(app.get('port'), () => { - debug('Express server listening on port ' + server.address().port); +var server = app.listen(app.get('port'), function() { + debug('Express server listening on port ' + server.address().port); }); diff --git a/blacklist.json.example b/blacklist.json.example index d1b52314..0838757b 100644 --- a/blacklist.json.example +++ b/blacklist.json.example @@ -1,4 +1,4 @@ { - "1.2.3.4": true, - "2.3.4.5": true + "1.2.3.4": true, + "2.3.4.5": true } diff --git a/controller/mri/index.js b/controller/mri/index.js index fa9c081d..f388e59c 100644 --- a/controller/mri/index.js +++ b/controller/mri/index.js @@ -1,21 +1,22 @@ -const express = require('express'); -const multer = require('multer'); -const router = express.Router(); +var express = require('express'); +var controller = require('./mri.controller'); +var upload_controller = require('./upload.controller'); -const controller = require('./mri.controller'); -const uploadController = require('./upload.controller'); +var multer = require('multer'); +var router = express.Router(); router.get('', controller.validator, controller.mri); router.get('/json', controller.validator, tokenAuthentication, controller.api_mri_get); router.post('/json', controller.validator_post, tokenAuthentication, controller.api_mri_post); -router.get('/upload', uploadController.token); +router.get('/upload', upload_controller.token); + router.post('/upload', - multer({dest: './tmp/'}).array('atlas'), - uploadController.validator, - uploadController.other_validations, - uploadController.upload); + multer({ dest: './tmp/'}).array('atlas'), + upload_controller.validator, + upload_controller.other_validations, + upload_controller.upload); router.get('/reset', controller.reset); -module.exports = router; +module.exports = router; \ No newline at end of file diff --git a/controller/mri/mri.controller.js b/controller/mri/mri.controller.js index 545c1bbe..373f837d 100644 --- a/controller/mri/mri.controller.js +++ b/controller/mri/mri.controller.js @@ -1,50 +1,51 @@ -'use strict'; +"use strict"; -const crypto = require('crypto'); -const url = require('url'); -const fs = require('fs'); -const request = require('request'); -const path = require('path'); -const atlasMakerServer = require('../../js/atlasMakerServer'); -const checkAccess = require('../../js/checkAccess.js'); -const dataSlices = require('../../js/dataSlices.js'); +var crypto = require('crypto'); +var url = require('url'); +var fs = require('fs'); +var request = require('request'); +var path = require('path'); +var atlasMakerServer = require('../../js/atlasMakerServer'); +var checkAccess = require('../../js/checkAccess.js'); +var dataSlices = require("../../js/dataSlices.js"); -const downloadQueue = []; +var downloadQueue = []; -// ExpressValidator = require('express-validator') +//expressValidator = require('express-validator') -const validator = function (req, res, next) { - console.log('Query validator'); - console.log('body:', req.body); - console.log('query:', req.query); +var validator = function (req, res, next) { + console.log("Query validator"); + console.log("body:",req.body); + console.log("query:",req.query); - if (!req.query.url) { + if(!req.query.url) { return next(); - } - req.checkQuery('url', 'please enter a valid URL') + } else { + req.checkQuery('url', 'please enter a valid URL') .isURL(); - // Req.checkQuery('var', 'please enter one of the variables that are indicated') + // req.checkQuery('var', 'please enter one of the variables that are indicated') // .optional() // .matches("localpath|filename|source|url|dim|pixdim"); // todo: decent regexp - const errors = req.validationErrors(); - console.log('errors:', errors); - if (errors) { - res.send(errors).status(403).end(); - } else { - return next(); + var errors = req.validationErrors(); + console.log("errors:",errors); + if (errors) { + res.send(errors).status(403).end(); + } else { + return next(); + } } }; -const validator_post = function (req, res, next) { +var validator_post = function (req, res, next) { req.checkBody('url', 'please enter a valid URL') .isURL(); - // Req.checkQuery('var', 'please enter one of the variables that are indicated') + // req.checkQuery('var', 'please enter one of the variables that are indicated') // .optional() // .matches("localpath|filename|source|url|dim|pixdim"); // todo: decent regexp - const errors = req.validationErrors(); - console.log('errors:', errors); + var errors = req.validationErrors(); + console.log("errors:",errors); if (errors) { res.send(errors).status(403).end(); } else { @@ -53,87 +54,86 @@ const validator_post = function (req, res, next) { }; /* Download MRI file ---------------------- */ +---------------------*/ /** * @todo Change this function callback into a promise */ function downloadMRI(myurl, req, res, callback) { - console.log('downloadMRI'); - const hash = crypto.createHash('md5').update(myurl).digest('hex'); - - req.db.get('mri').findOne({source: myurl, backup: {$exists: 0}}) - .then(mridb => { - console.log('mridb:', mridb); - let filename; - if (!mridb || !mridb.filename) { - filename = url.parse(myurl).pathname.split('/').pop(); + console.log("downloadMRI"); + var hash = crypto.createHash('md5').update(myurl).digest('hex'); + + req.db.get('mri').findOne({source: myurl, backup:{$exists:0}}) + .then(function(mridb) { + console.log("mridb:",mridb); + var filename; + if(!mridb || !mridb.filename) { + filename = url.parse(myurl).pathname.split("/").pop() } else { filename = mridb.filename; } - let dest = req.dirname + '/public/data/' + hash + '/' + filename; - console.log(' source:', myurl); - console.log(' hash:', hash); - console.log(' filename:', filename); - console.log(' dest:', dest); - - if (!fs.existsSync(req.dirname + '/public/data/' + hash)) { - fs.mkdirSync(req.dirname + '/public/data/' + hash, '0777'); + var dest = req.dirname + "/public/data/" + hash + "/" + filename; + console.log(" source:", myurl); + console.log(" hash:", hash); + console.log(" filename:", filename); + console.log(" dest:", dest); + + if (!fs.existsSync(req.dirname + "/public/data/" + hash)) { + fs.mkdirSync(req.dirname + "/public/data/" + hash, '0777'); } - let newFilename, newDest, len, - cur = 0; + var newFilename, newDest, len, cur = 0; request({uri: myurl, followAllRedirects: true}) - .on('error', err => { - console.log('ERROR in downloadMRI', err); - callback({error: err}); + .on('error', function (err) { + console.log("ERROR in downloadMRI", err); + callback({error:err}); }) - .on('response', res => { - const href = res.request.uri.href; - const contentDisp = res.headers['content-disposition']; + .on('response', function(res) { + var href = res.request.uri.href; + var contentDisp = res.headers['content-disposition']; if (contentDisp && /^attachment/.test(contentDisp)) { newFilename = contentDisp.split('filename=')[1].split(';')[0].replace(/"/g, ''); } else { newFilename = path.basename(url.parse(href).path); } - console.log('filename:', newFilename); - const arr = dest.split('/'); + console.log("filename:",newFilename); + var arr = dest.split("/"); arr.pop(); arr.push(newFilename); - newDest = arr.join('/'); - console.log('new dest:', newDest); + newDest = arr.join("/"); + console.log("new dest:",newDest); len = parseInt(res.headers['content-length'], 10); - console.log('file length:', len); + console.log("file length:",len); }) - .on('data', chunk => { - // Body += chunk; + .on('data', function(chunk) { + // body += chunk; cur += chunk.length; - console.log('downloaded:', cur, '/', len, newFilename); + console.log("downloaded:",cur,"/",len,newFilename); downloadQueue[myurl].cur = cur; downloadQueue[myurl].len = len; - // Obj.innerHTML = "Downloading " + (100.0 * cur / len).toFixed(2) + "% " + (cur / 1048576).toFixed(2) + " mb\r" + ".
Total size: " + total.toFixed(2) + " mb"; + // obj.innerHTML = "Downloading " + (100.0 * cur / len).toFixed(2) + "% " + (cur / 1048576).toFixed(2) + " mb\r" + ".
Total size: " + total.toFixed(2) + " mb"; }) .pipe(fs.createWriteStream(dest)) - .on('close', res => { - console.log('new:', newFilename, newDest); - + .on('close', function request_fromDownloadMRI(res) { + console.log("new:",newFilename, newDest); + fs.renameSync(dest, newDest); - filename = newFilename; - dest = newDest; - + filename=newFilename; + dest=newDest; + // NOTE: getBrainAtPath has to be called with a client-side path like "/data/[md5hash]/..." - atlasMakerServer.getBrainAtPath('/data/' + hash + '/' + filename) - .then(mri => { - // Create json file for new dataset - const ip = req.headers['x-forwarded-for'] || + atlasMakerServer.getBrainAtPath("/data/" + hash + "/" + filename) + .then(function getBrainAtPath_fromDownloadMRI(mri) { + // create json file for new dataset + var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.socket.remoteAddress || req.connection.socket.remoteAddress; - const username = (req.isAuthenticated()) ? req.user.username : ip; - const json = { - filename, + var username = (req.isAuthenticated()) ? req.user.username : ip; + var json = { + filename: filename, success: true, source: myurl, - url: '/data/' + hash + '/', + url: "/data/" + hash + "/", included: (new Date()).toJSON(), dim: mri.dim, pixdim: mri.pixdim, @@ -155,30 +155,32 @@ function downloadMRI(myurl, req, res, callback) { }; callback(json); }) - .catch(err => { - console.log('ERROR Cannot get brain at path /data/' + hash + '/' + filename + ': ', err); - callback({error: 'Can\'t get brain'}); + .catch(function(err) { + console.log("ERROR Cannot get brain at path /data/" + hash + "/" + filename + ": ", err); + callback({error:"Can't get brain"}); }); }); + + }); } -const mri = function (req, res) { - const login = (req.isAuthenticated()) ? - ('' + req.user.username + ' (Log Out)') : - ('Log in with GitHub'); - const loggedUser = req.isAuthenticated() ? req.user.username : 'anonymous'; - - // Store return path in case of login +var mri = function (req, res) { + var login = (req.isAuthenticated()) ? + ("" + req.user.username + " (Log Out)") + : ("Log in with GitHub"); + var loggedUser = req.isAuthenticated()?req.user.username:"anonymous"; + + // store return path in case of login req.session.returnTo = req.originalUrl; - - const myurl = req.query.url; - const hash = crypto.createHash('md5').update(myurl).digest('hex'); - console.log('Receive GET, query:', myurl, hash); - - req.db.get('mri').findOne({source: myurl, backup: {$exists: 0}}, {_id: 0}) - .then(json => { - if (!json) { - const obj = { + + var myurl = req.query.url; + var hash = crypto.createHash('md5').update(myurl).digest('hex'); + console.log("Receive GET, query:",myurl,hash); + + req.db.get('mri').findOne({source: myurl, backup:{$exists:0}}, {_id: 0}) + .then(function (json) { + if(!json) { + var obj = { source: myurl }; @@ -186,43 +188,38 @@ const mri = function (req, res) { title: obj.name || 'BrainBox', params: JSON.stringify(req.query), mriInfo: JSON.stringify(obj), - login + login: login }); } else { - // If the json object exists, and has annotations, configure the access to them - if (!json.mri.atlas) { + // if the json object exists, and has annotations, configure the access to them + if(!json.mri.atlas) json.mri.atlas = []; - } - let i, j, k, ii, - prj = new Set(), - arr = []; - // Check access to volume annotations - for (i = 0; i < json.mri.atlas.length; i++) { - if (json.mri.atlas[i].project) { + var i, j, k, ii, prj = new Set(), arr = []; + // check access to volume annotations + for(i=0;i { - return req.db.get('project').findOne({ - shortname: o, - backup: {$exists: 0} - }); - }); - Promise.all([...arr]).then(projects => { - checkAccess.filterAnnotationsByProjects(json.mri, projects, loggedUser); - - // Set access to text annotations - for (i in json.mri.annotations) { - for (j = 0; j < projects.length; j++) { - if (projects[j] && projects[j].shortname == i) { - const access = checkAccess.toAnnotationByProject(projects[j], loggedUser); - const level = checkAccess.accessStringToLevel(access); - if (level > 0) { - for (k in json.mri.annotations[i]) { + arr = [...prj].map(function(o){return req.db.get('project').findOne({ + shortname:o, + backup: {$exists: 0} + })}); + Promise.all([...arr]).then(function(projects) { + checkAccess.filterAnnotationsByProjects(json.mri,projects,loggedUser); + + // set access to text annotations + for(i in json.mri.annotations) { + for(j=0;j 0) { + for(k in json.mri.annotations[i]) { json.mri.annotations[i][k].access = access; } } else { @@ -232,230 +229,225 @@ const mri = function (req, res) { } } - // Send data + // send data res.render('mri', { title: json.name || 'BrainBox', params: JSON.stringify(req.query), mriInfo: JSON.stringify(json), - login + login: login }); - }).catch(err => { - console.log('ERROR Cannot get db information:', err); + }).catch(function(err) { + console.log("ERROR Cannot get db information:",err); }); } - }, err => { - console.log('err 241:', err); + }, function (err) { + console.log("err 241:",err); }); -}; +} -const api_mri_post = function (req, res) { - const myurl = req.body.url; - const hash = crypto.createHash('md5').update(myurl).digest('hex'); - let loggedUser = 'anonymous'; - if (req.isAuthenticated()) { +var api_mri_post = function (req, res) { + var myurl = req.body.url; + var hash = crypto.createHash('md5').update(myurl).digest('hex'); + var loggedUser = "anonymous"; + if(req.isAuthenticated()) { loggedUser = req.user.username; } else - if (req.isTokenAuthenticated) { + if(req.isTokenAuthenticated) { loggedUser = req.tokenUsername; } - req.db.get('mri').findOne({source: myurl, backup: {$exists: 0}}, {_id: 0}) - .then(json => { - // Determine whether we need to download the data from the source - let doDownload = false; - - // If client is not requesting a specific MRI variable + req.db.get('mri').findOne({source:myurl, backup: {$exists: 0}}, {_id: 0}) + .then(function (json) { + // determine whether we need to download the data from the source + var doDownload = false; + + // if client is not requesting a specific MRI variable if (!req.body.var) { - // If the json object is empty, download - if (!json) { - console.log('No DB entry for MRI: download'); + // if the json object is empty, download + if(!json) { + console.log("No DB entry for MRI: download"); doDownload = true; } else { - // If the json object exists, but there's no file, download - const filename = json.filename || url.parse(myurl).pathname.split('/').pop(); - const path = req.dirname + '/public/data/' + hash + '/' + filename; - if (fs.existsSync(path) == false) { - console.log('No MRI file in server: download'); + // if the json object exists, but there's no file, download + var filename = json.filename || url.parse(myurl).pathname.split("/").pop(); + var path = req.dirname + "/public/data/" + hash + "/" + filename; + if(fs.existsSync(path) == false) { + console.log("No MRI file in server: download"); doDownload = true; } else - // If the json object exists, there's a file, but no .dim object, download - if (!json.dim) { - // If(debug>1) console.log("No dim[] field in DB entry: download"); + // if the json object exists, there's a file, but no .dim object, download + if(!json.dim) { + //if(debug>1) console.log("No dim[] field in DB entry: download"); doDownload = true; } } } - if (doDownload === true) { - if (downloadQueue[myurl]) { - if (downloadQueue[myurl].success == true) { - const info = JSON.parse(JSON.stringify(downloadQueue[myurl])); + if(doDownload === true ) { + if(downloadQueue[myurl]) { + if(downloadQueue[myurl].success==true) { + var info = JSON.parse(JSON.stringify(downloadQueue[myurl])); delete downloadQueue[myurl]; res.json(info); } else { - console.log('>>', downloadQueue[myurl], myurl); + console.log(">>",downloadQueue[myurl],myurl); res.json(downloadQueue[myurl]); } + } else { - console.log('Start download:'); - downloadQueue[myurl] = {success: 'downloading', cur: 0, len: 1}; - downloadMRI(myurl, req, res, obj => { - if (obj.error == undefined) { - console.log('Download succeeded'); + console.log("Start download:"); + downloadQueue[myurl] = {success:"downloading",cur:0,len:1}; + downloadMRI(myurl, req, res, function (obj) { + if(obj.error == undefined) { + console.log("Download succeeded"); req.db.get('mri').insert(obj); obj.success = true; - downloadQueue[myurl] = obj; + downloadQueue[myurl]=obj; } else { - console.log('Download failed:', obj); - downloadQueue[myurl] = {success: 'error'}; + console.log("Download failed:", obj); + downloadQueue[myurl]={success:"error"}; } }); - + res.json(downloadQueue[myurl]); } } else { - // Return a specific variable, or the complete json object - console.log('Send the data to the client. End of transaction.'); + // return a specific variable, or the complete json object + console.log("Send the data to the client. End of transaction."); if (req.body.var) { - let i, - arr = req.body.var.split('/'); + var i, arr = req.body.var.split("/"); for (i in arr) { json = json[arr[i]]; } } - res.json(json); + res.json(json); } - }, err => { - console.log('ERROR:', err); - res.json({success: false}); + }, function (err) { + console.log("ERROR:",err); + res.json({success:false}); }); }; /* -Token=054x9gjgfdukozkv25cfgh9f6rzpuc9h1fbwb2o83vondpwrk9 +token=054x9gjgfdukozkv25cfgh9f6rzpuc9h1fbwb2o83vondpwrk9 */ -const api_mri_get = function (req, res) { - const myurl = req.query.url; - let loggedUser = 'anonymous'; - if (req.isAuthenticated()) { +var api_mri_get = function (req, res) { + var myurl = req.query.url; + var loggedUser = "anonymous"; + if(req.isAuthenticated()) { loggedUser = req.user.username; } else - if (req.isTokenAuthenticated) { + if(req.isTokenAuthenticated) { loggedUser = req.tokenUsername; } - // If query does not contain a specific mri, send paginated list of mris - if (!myurl) { - if (req.query.page === undefined) { - res.send({error: 'Specify the \'page\' parameter'}); + // if query does not contain a specific mri, send paginated list of mris + if(!myurl) { + if(req.query.page === undefined) { + res.send({error:"Specify the 'page' parameter"}); return; } - - // Display access-filtered list of mris - const page = Math.max(0, parseInt(req.query.page)); - const nItemsPerPage = 20; - - dataSlices.getFilesSlice(req, page * nItemsPerPage, nItemsPerPage) - .then(values => { + + // display access-filtered list of mris + var page = Math.max(0,parseInt(req.query.page)); + var nItemsPerPage = 20; + + dataSlices.getFilesSlice(req,page*nItemsPerPage,nItemsPerPage) + .then(function (values) { res.json(values); }); - + return; } - req.db.get('mri').findOne({source: myurl, backup: {$exists: 0}}, {_id: 0}) - .then(json => { - if (!json) { + req.db.get('mri').findOne({source: myurl, backup:{$exists:0}}, {_id: 0}) + .then(function (json) { + if(!json) { res.status(404).json({}); } else { - // If the json object exists, and has annotations, configure the access to them - console.log('check access rights'); - if (!json.mri.atlas) { + // if the json object exists, and has annotations, configure the access to them + console.log("check access rights"); + if(!json.mri.atlas) json.mri.atlas = []; - } - let i, j, k, ii, - prj = new Set(), - arr = []; - // Check access to volume annotations - for (i = 0; i < json.mri.atlas.length; i++) { - if (json.mri.atlas[i].project) { - console.log('mri is in project', json.mri.atlas[i].project); + var i, j, k, ii, prj = new Set(), arr = []; + // check access to volume annotations + for(i=0;i { - return req.db.get('project').findOne({ - shortname: o, - backup: {$exists: 0} - }); - }); - - Promise.all([...arr]).then(projects => { - console.log('projects', projects); - // Set access to volume annotations - for (i = json.mri.atlas.length - 1; i >= 0; i--) { - for (j = 0; j < projects.length; j++) { - if (projects[j] && projects[j].shortname == json.mri.atlas[i].project) { - var access = checkAccess.toAnnotationByProject(projects[j], loggedUser); + arr = [...prj].map(function(o){return req.db.get('project').findOne({ + shortname:o, + backup: {$exists: 0} + })}); + + Promise.all([...arr]).then(function(projects) { + console.log("projects",projects); + // set access to volume annotations + for(i=json.mri.atlas.length-1;i>=0;i--) { + for(j=0;j 0) - if (level == 0) { - json.mri.atlas.splice(i, 1); + console.log("loggedUser,access,level:",loggedUser,access,level); + // check for 'view' access (level > 0) + if(level == 0) { + json.mri.atlas.splice(i,1); } break; } } } - // Set access to text annotations - for (i in json.mri.annotations) { - for (j = 0; j < projects.length; j++) { - if (projects[j] && projects[j].shortname == i) { - var access = checkAccess.toAnnotationByProject(projects[j], loggedUser); + // set access to text annotations + for(i in json.mri.annotations) { + for(j=0;j { - console.log('ERROR Cannot get db information:', err); + }).catch(function(err) { + console.log("ERROR Cannot get db information:",err); }); } - }, err => { - console.log('err:', err); + }, function (err) { + console.log("err:",err); }); -}; - -const reset = function reset(req, res) { - const myurl = req.query.url; - const hash = crypto.createHash('md5').update(myurl).digest('hex'); +} - req.db.get('mri').findOne({source: myurl, backup: {$exists: 0}}) - .then(mridb => { - const filename = mridb.filename; - atlasMakerServer.getBrainAtPath('/data/' + hash + '/' + filename) - .then(mri => { - req.db.get('mri').update({source: myurl, backup: {$exists: 0}}, {$set: { +var reset = function reset(req, res) { + var myurl = req.query.url; + var hash = crypto.createHash('md5').update(myurl).digest('hex'); + + req.db.get('mri').findOne({source:myurl,backup:{$exists:0}}) + .then(function(mridb) { + var filename = mridb.filename; + atlasMakerServer.getBrainAtPath("/data/" + hash + "/" + filename) + .then(function getBrainAtPath_fromReset(mri) { + req.db.get('mri').update({source:myurl,backup:{$exists:0}},{$set:{ dim: mri.dim, pixdim: mri.pixdim, voxel2world: mri.v2w, worldOrigin: mri.wori }}) - .then(() => { + .then(function () { res.send({ dim: mri.dim, pixdim: mri.pixdim, @@ -463,23 +455,23 @@ const reset = function reset(req, res) { worldOrigin: mri.wori }); }) - .catch(err => { - console.log('ERROR:', err); + .catch(function(err) { + console.log("ERROR:",err); res.send(err).status(403).end(); }); }) - .catch(err => { - console.log('ERROR:', err); + .catch(function(err) { + console.log("ERROR:",err); res.send(err).status(403).end(); }); }) - .catch(err => { - console.log('ERROR:', err); + .catch(function(err) { + console.log("ERROR:",err); res.send(err).status(403).end(); }); }; -const mriController = function () { +var mriController = function () { this.validator = validator; this.validator_post = validator_post; this.api_mri_get = api_mri_get; diff --git a/controller/mri/upload.controller.js b/controller/mri/upload.controller.js index 44948706..1d0115e8 100644 --- a/controller/mri/upload.controller.js +++ b/controller/mri/upload.controller.js @@ -1,10 +1,10 @@ -'use strict'; +"use strict"; -const fs = require('fs'); -const atlasMakerServer = require('../../js/atlasMakerServer'); -// ExpressValidator = require('express-validator') +var fs = require('fs'); +var atlasMakerServer = require('../../js/atlasMakerServer'); +//expressValidator = require('express-validator') -const validator = function (req, res, next) { +var validator = function (req, res, next) { req.checkBody('url', 'please enter a valid URL') .notEmpty() .isURL(); @@ -15,9 +15,9 @@ const validator = function (req, res, next) { .notEmpty() .isAlphanumeric(); req.checkBody('atlasLabelSet', 'please enter an Atlas Project') - .notEmpty(); + .notEmpty() req.checkBody('token', 'please enter an upload token') - .notEmpty(); + .notEmpty() /* Check for all these required fields: url: url @@ -26,119 +26,118 @@ const validator = function (req, res, next) { atlasProject: Alphanumeric string atlasLabelSet: One of the labels available inside the /public/labels/ directory */ - - const errors = req.validationErrors(); - console.log('errors 33:', errors); + + var errors = req.validationErrors(); + console.log("errors 33:",errors); if (errors) { return res.send(errors).status(403).end(); + } else { + return next(); } - return next(); }; -const other_validations = function (req, res, next) { - const token = req.body.token; - req.db.get('log').findOne({token}) - .then(obj => { - if (obj) { +var other_validations = function(req, res, next) { + + var token = req.body.token; + req.db.get("log").findOne({"token":token}) + .then(function (obj) { + if(obj) { // Check token expiry date - const now = new Date(); - if (obj.expiryDate.getTime() - now.getTime() < req.tokenDuration) { - req.db.get('mri').findOne({source: req.body.url, backup: {$exists: false}}) - .then(json => { + var now = new Date(); + if(obj.expiryDate.getTime()-now.getTime() < req.tokenDuration) { + req.db.get('mri').findOne({source:req.body.url, backup: {$exists: false}}) + .then(function (json) { if (json && req.files.length > 0) { req.atlasUpload = { mri: json, username: obj.username }; next(); - } else { - const err = new Array(); - if (req.files.length == 0 || !req.files) { - err.push({error: 'there is no File'}); - } - if (!json) { - err.push({error: 'Unkown URL'}); - } - console.log('err 63:', err); + } + else { + var err = new Array(); + if (req.files.length == 0 || !req.files) {err.push({error:"there is no File"});} + if (!json) {err.push({error:"Unkown URL"});} + console.log("err 63:",err); return res.json(err).status(403).end(); } - }); + }) } else { - return res.send('ERROR: Token expired').status(403).end(); + return res.send("ERROR: Token expired").status(403).end(); } } else { - return res.send('ERROR: Cannot find token').status(403).end(); - } + return res.send("ERROR: Cannot find token").status(403).end(); + } }) - .catch(err => { - console.log('ERROR:', err); + .catch(function (err) { + console.log("ERROR:",err); res.send().status(403).end(); }); -}; +} -const upload = function (req, res) { - const username = req.atlasUpload.username; - const url = req.body.url; - const mri = req.atlasUpload.mri; - const atlasName = req.body.atlasName; - const atlasProject = req.body.atlasProject; - const atlasLabelSet = req.body.atlasLabelSet; - const files = req.files; +var upload = function(req, res) { + var username = req.atlasUpload.username; + var url = req.body.url; + var mri = req.atlasUpload.mri; + var atlasName = req.body.atlasName; + var atlasProject = req.body.atlasProject; + var atlasLabelSet = req.body.atlasLabelSet; + var files = req.files; delete mri._id; - - console.log('Everything is in order'); - console.log('username:', username); - console.log('url:', url); - console.log('mri:', mri); - console.log('atlasName:', atlasName); - console.log('atlasProject:', atlasProject); - console.log('atlasLabelSet:', atlasLabelSet); - console.log('files:', files); - - // Create final filename - let ext; - let filename; - let dir, path; - if (/.nii.gz$/.test(files[0].originalname)) { - ext = '.nii.gz'; - } else - if (/.mgz$/.test(files[0].originalname)) { - ext = '.mgz'; - } else { - return res.json({error: 'Atlas encoding neither .nii.gz nor .mgz'}).status(400).end(); + + console.log("Everything is in order"); + console.log("username:",username); + console.log("url:", url); + console.log("mri:", mri); + console.log("atlasName:", atlasName); + console.log("atlasProject:", atlasProject); + console.log("atlasLabelSet:", atlasLabelSet); + console.log("files:", files); + + // create final filename + var ext; + var filename; + var dir, path; + if(/.nii.gz$/.test(files[0].originalname)) + ext=".nii.gz"; + else + if(/.mgz$/.test(files[0].originalname)) + ext=".mgz"; + else { + return res.json({error:"Atlas encoding neither .nii.gz nor .mgz"}).status(400).end(); } - filename = Math.random().toString(36).slice(2) + ext; - - // Check if directory exists (it may not exist if a volume annotation is being uploaded - // for an mri that has only a db entry but has not yet been accessed) - dir = req.dirname + '/public' + mri.url; - if (!fs.existsSync(dir)) { - // Directory does not exist, create it - console.log('> mri directory did not exist, create it'); + filename=Math.random().toString(36).slice(2)+ext; + + // check if directory exists (it may not exist if a volume annotation is being uploaded + // for an mri that has only a db entry but has not yet been accessed) + dir = req.dirname + "/public" + mri.url; + if(!fs.existsSync(dir)) { + // directory does not exist, create it + console.log("> mri directory did not exist, create it"); fs.mkdirSync(dir, '0777'); } - // Move tmp atlas file to final location + // move tmp atlas file to final location path = dir + filename; try { - fs.renameSync(req.dirname + '/' + files[0].path, path); - } catch (err) { - console.log('ERROR rename failed:', err); - return res.json({error: 'cannot upload volume annotation'}).status(400).end(); - } + fs.renameSync(req.dirname + "/" + files[0].path, path); + } catch(err) { + console.log("ERROR rename failed:",err); + return res.json({error:"cannot upload volume annotation"}).status(400).end(); + }; // Check that the dimensions of the atlas are the same as its parent mri - console.log('> load parent mri'); + console.log("> load parent mri"); atlasMakerServer.loadMRI(path) - .then(atlas => { - console.log('atlas.dim: ', atlas.dim); - console.log('mri.dim: ', mri.dim); + .then(function(atlas){ + console.log("atlas.dim: ",atlas.dim); + console.log("mri.dim: ",mri.dim); /** * @todo How do we check for volume dimensions now? */ - + /* // check volume dimensions if (atlas.dim[0] != mri.dim[0] || @@ -149,80 +148,80 @@ const upload = function (req, res) { */ // create the atlas object - const date = new Date(); - const atlasMetadata = { + var date = new Date(); + var atlasMetadata = { name: atlasName, project: atlasProject, - access: 'edit', - created: date.toJSON(), - modified: date.toJSON(), - filename, // Automatically generated filename + access: "edit", + created: date.toJSON(), + modified: date.toJSON(), + filename: filename, // automatically generated filename originalname: files[0].originalname, labels: atlasLabelSet, owner: username, - type: 'volume' + type: "volume" }; - - console.log('final volume annotation entry:'); - console.log('atlasMetadata:', atlasMetadata); - - // Remove previous atlases with the same atlasName and atlasProject - let i; - for (i = mri.mri.atlas.length - 1; i >= 0; i--) { - if (mri.mri.atlas[i].name == atlasName && mri.mri.atlas[i].project == atlasProject) { - mri.mri.atlas.splice(i, 1); + + console.log("final volume annotation entry:"); + console.log("atlasMetadata:",atlasMetadata); + + // remove previous atlases with the same atlasName and atlasProject + var i; + for(i=mri.mri.atlas.length-1;i>=0;i--) { + if(mri.mri.atlas[i].name == atlasName && mri.mri.atlas[i].project == atlasProject) { + mri.mri.atlas.splice(i,1); } } - // Update the database + // update the database mri.mri.atlas.push(atlasMetadata); - // Mark previous version as backup - req.db.get('mri').update({source: req.body.url, backup: {$exists: false}}, {$set: {backup: true}}, {multi: true}) - .then(() => { - // Insert new version + // mark previous version as backup + req.db.get('mri').update({source:req.body.url,backup:{$exists:false}},{$set:{backup:true}},{multi:true}) + .then(function() { + // insert new version req.db.get('mri').insert(mri); }); - // Return the full mri object ??? + // return the full mri object ??? return res.json(mri).status(200).end(); }) - .catch(err => { - console.log('ERROR: mri file is not valid: ', err); - return res.json({error: 'mri file is not valid: ' + err}).status(400).end(); + .catch(function (err) { + console.log("ERROR: mri file is not valid: ",err); + return res.json({error:"mri file is not valid: "+err}).status(400).end(); }); -}; +} -const token = function token(req, res) { +var token = function token(req, res) { if (req.isAuthenticated()) { - let obj = {}, + var obj = {}, a = Math.random().toString(36).slice(2), b = Math.random().toString(36).slice(2), token, now, expiryDate; - // Token duration is set to 1 h in milliseconds + // token duration is set to 1 h in milliseconds // generate a random token obj.token = a + b; - // Expiration date: now plus tokenDuration milliseconds + // expiration date: now plus tokenDuration milliseconds now = new Date(); obj.expiryDate = new Date(now.getTime() + req.tokenDuration); - // Record the username + // record the username obj.username = req.user.username; - // Store it in the database for the user - req.db.get('log').insert(obj); - + // store it in the database for the user + req.db.get("log").insert(obj); + /* // schedule its removal or log them forever? setTimer(function () { req.db.get("log").remove(obj); }, req.tokenDuration); */ - + res.json(obj); } else { res.redirect('/'); } -}; +} -const uploadController = function () { +var uploadController = function () { this.validator = validator; this.other_validations = other_validations; this.upload = upload; diff --git a/controller/project/index.js b/controller/project/index.js index 047f1aae..82491eb7 100644 --- a/controller/project/index.js +++ b/controller/project/index.js @@ -1,18 +1,18 @@ -const express = require('express'); -const controller = require('./project.controller'); +var express = require('express'); +var controller = require('./project.controller'); -const router = express.Router(); +var router = express.Router(); // router.get('/new', controller.newProject); router.get('/json', tokenAuthentication, controller.api_projectAll); -router.get('/json/:projectName', controller.validator, tokenAuthentication, controller.api_project); -router.get('/json/:projectName/files', controller.validator, tokenAuthentication, controller.api_projectFiles); +router.get('/json/:projectName', controller.validator , tokenAuthentication, controller.api_project); +router.get('/json/:projectName/files', controller.validator , tokenAuthentication, controller.api_projectFiles); router.post('/json/:projectName', controller.validator, tokenAuthentication, controller.post_project); router.delete('/json/:projectName', controller.validator, tokenAuthentication, controller.delete_project); router.get('/:projectName', controller.validator, controller.project); router.get('/:projectName/settings', controller.validator, controller.settings); -module.exports = router; +module.exports = router; \ No newline at end of file diff --git a/controller/project/project.controller.js b/controller/project/project.controller.js index 61afbf08..80368899 100644 --- a/controller/project/project.controller.js +++ b/controller/project/project.controller.js @@ -1,38 +1,38 @@ +var async = require("async"); +var url = require('url'); +var crypto = require('crypto'); +var dateFormat = require('dateformat'); +var validatorNPM = require('validator'); var async = require('async'); -const url = require('url'); -const crypto = require('crypto'); -const dateFormat = require('dateformat'); -const validatorNPM = require('validator'); -var async = require('async'); -const checkAccess = require('../../js/checkAccess.js'); -const dataSlices = require('../../js/dataSlices.js'); +var checkAccess = require("../../js/checkAccess.js"); +var dataSlices = require("../../js/dataSlices.js"); const createDOMPurify = require('dompurify'); const jsdom = require('jsdom'); - const window = jsdom.jsdom('', { - features: { - FetchExternalResources: false, // Disables resource loading over HTTP / filesystem - ProcessExternalResources: false // Do not execute JS within script blocks - } + features: { + FetchExternalResources: false, // disables resource loading over HTTP / filesystem + ProcessExternalResources: false // do not execute JS within script blocks + } }).defaultView; const DOMPurify = createDOMPurify(window); -const validator = function (req, res, next) { - req.checkParams('projectName', 'incorrect project name').isAlphanumeric(); - // Req.checkQuery('url', 'please enter a valid URL') - // .isURL(); - - // req.checkQuery('var', 'please enter one of the variables that are indicated') - // .optional() - // .matches("localpath|filename|source|url|dim|pixdim"); //todo: decent regexp - const errors = req.validationErrors(); - if (errors) { - res.send(errors).status(403).end(); - } else { - return next(); - } -}; +var validator = function(req, res, next) { + + req.checkParams('projectName', 'incorrect project name').isAlphanumeric(); + // req.checkQuery('url', 'please enter a valid URL') + // .isURL(); + + // req.checkQuery('var', 'please enter one of the variables that are indicated') + // .optional() + // .matches("localpath|filename|source|url|dim|pixdim"); //todo: decent regexp + var errors = req.validationErrors(); + if (errors) { + res.send(errors).status(403).end(); + } else { + return next(); + } +} /** * @func isProjectObject @@ -41,142 +41,142 @@ const validator = function (req, res, next) { * @param {Object} object Project definition object * @todo object.annotations?? */ -const isProjectObject = function (req, res, object) { - const goodOwner = false; - const goodCollaborators = false; - - const pr = new Promise((resolve, reject) => { - let i, k, flag, arr; - const allowed = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.,_- \'–:;'.split(''); - +var isProjectObject = function(req,res,object) { + var goodOwner = false; + var goodCollaborators = false; + + var pr = new Promise(function(resolve, reject) { + var i, k, flag, arr; + var allowed="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.,_- '–:;".split(""); + // 1. Synchronous checks //---------------------- - + // files if (object.files) { for (k in object.files.list) { if (!validatorNPM.isURL(object.files.list[k].source)) { - reject({success: false, error: 'Invalid file URL'}); + reject({success:false,error:"Invalid file URL"}); return; } - if (!validatorNPM.isWhitelisted(object.files.list[k].name, allowed)) { - reject({success: false, error: 'Invalid file name'}); + if(!validatorNPM.isWhitelisted(object.files.list[k].name, allowed)) { + reject({success:false,error:"Invalid file name"}); return; } } } - console.log('> files ok'); + console.log("> files ok"); - // Description + // description if (object.description && !validatorNPM.isWhitelisted(object.description, allowed)) { - reject({success: false, error: 'Invalid project description'}); + reject({success:false,error:"Invalid project description"}); return; - // Delete object.description; + // delete object.description; } - console.log('> description ok'); + console.log("> description ok"); - // Name + // name if (object.name && !validatorNPM.isWhitelisted(object.name, allowed)) { - reject({success: false, error: 'Invalid name'}); + reject({success:false,error:"Invalid name"}); return; - // Delete object.name; + //delete object.name; } - console.log('> name ok'); + console.log("> name ok"); - // Check that owner and shortname are present + // check that owner and shortname are present if (!object.owner || !object.shortname) { - reject({success: false, error: 'Invalid owner or project shortname, not present'}); + reject({success:false,error:"Invalid owner or project shortname, not present"}); return; } - console.log('> owner and project shortname present'); - - // Check that shortname is alphanumeric - if (!validatorNPM.isAlphanumeric(object.owner) || !validatorNPM.isAlphanumeric(object.shortname)) { - reject({success: false, error: 'Invalid owner or project shortname, not alphanumeric'}); + console.log("> owner and project shortname present") + + // check that shortname is alphanumeric + if(!validatorNPM.isAlphanumeric(object.owner) || !validatorNPM.isAlphanumeric(object.shortname)) { + reject({success:false,error:"Invalid owner or project shortname, not alphanumeric"}); return; } - console.log('> owner and project shortname present'); + console.log("> owner and project shortname present") - // Convenience array for collaborator checks - arr = object.collaborators.list; + // convenience array for collaborator checks + arr=object.collaborators.list; - // Check that the 'anyone' user is present - flag = false; - for (i = 0; i < arr.length; i++) { - if (arr[i].userID === 'anyone') { + // check that the 'anyone' user is present + flag=false; + for(i=0;i Access values ok'); - - // Check that the list of annotations contains at least 1 volume-type entry + console.log("> Access values ok"); + + // check that the list of annotations contains at least 1 volume-type entry flag = false; - for (i = 0; i < object.annotations.list.length; i++) { - if (object.annotations.list[i].type == 'volume') { + for(i=0;i { - let i, - notFound = false; - for (i = 0; i < val.length; i++) { - if (val[i].length === 0) { - notFound = true; + Promise.all(arr).then(function(val) { + var i, notFound=false; + for(i=0;i' + req.user.username + ' (Log Out)') : - ('Log in with GitHub'); - let loggedUser = 'anonymous'; - if (req.isAuthenticated()) { +var project = function(req, res) { + var login= (req.isAuthenticated())? + (""+req.user.username+" (Log Out)") + :("Log in with GitHub"); + var loggedUser = "anonymous"; + if(req.isAuthenticated()) { loggedUser = req.user.username; } else - if (req.isTokenAuthenticated) { + if(req.isTokenAuthenticated) { loggedUser = req.tokenUsername; } - // Store return path in case of login + // store return path in case of login req.session.returnTo = req.originalUrl; + + req.db.get('project').findOne({shortname:req.params.projectName,backup:{$exists:0}},"-_id") + .then(function(json) { + if (json) { + // check that the logged user has access to view this project + if(checkAccess.toProject(json, loggedUser, "view") === false) { + res.status(401).send("Authorization required"); + return; + } - req.db.get('project').findOne({shortname: req.params.projectName, backup: {$exists: 0}}, '-_id') - .then(json => { - if (json) { - // Check that the logged user has access to view this project - if (checkAccess.toProject(json, loggedUser, 'view') === false) { - res.status(401).send('Authorization required'); - return; - } - - json.files.list = []; - res.render('project', { - title: json.name, - projectInfo: JSON.stringify(json), - projectName: json.name, - login - }); - } else { - res.status(404).send('Project Not Found'); - } -}); -}; + json.files.list = []; + res.render('project', { + title: json.name, + projectInfo: JSON.stringify(json), + projectName: json.name, + login: login + }); + } else { + res.status(404).send("Project Not Found"); + } + }); +} /** * @function api_project @@ -235,36 +235,34 @@ const project = function (req, res) { * @param {Object} res Res object from express * @result A json object with project data */ -const api_project = function (req, res) { - let loggedUser = 'anonymous'; - if (req.isAuthenticated()) { +var api_project = function(req, res) { + var loggedUser = "anonymous"; + if(req.isAuthenticated()) { loggedUser = req.user.username; } else - if (req.isTokenAuthenticated) { + if(req.isTokenAuthenticated) { loggedUser = req.tokenUsername; } - req.db.get('project').findOne({shortname: req.params.projectName, backup: {$exists: 0}}, '-_id') - .then(json => { - if (json) { - // Check that the logged user has access to view this project - if (checkAccess.toProject(json, loggedUser, 'view') === false) { - res.status(401).send({error: 'Authorization required'}); - return; - } - - if (req.query.var) { - let i, - arr = req.query.var.split('/'); - for (i in arr) { - json = json[arr[i]]; + req.db.get('project').findOne({shortname:req.params.projectName,backup:{$exists:0}},"-_id") + .then(function(json) { + if(json) { + // check that the logged user has access to view this project + if(checkAccess.toProject(json, loggedUser, "view") === false) { + res.status(401).send({error:"Authorization required"}); + return; } - } - res.send(json); - } else { - res.send(); - } -}); + + if(req.query.var) { + var i,arr=req.query.var.split("/"); + for(i in arr) + json=json[arr[i]]; + } + res.send(json); + } else { + res.send(); + } + }) }; /** @@ -274,26 +272,26 @@ const api_project = function (req, res) { * @param {Object} res Res object from express * @result A json object with project data */ -const api_projectAll = function (req, res) { - let i, page, nItemsPerPage; - let loggedUser = 'anonymous'; - if (req.isAuthenticated()) { +var api_projectAll = function(req, res) { + var i, page, nItemsPerPage; + var loggedUser = "anonymous"; + if(req.isAuthenticated()) { loggedUser = req.user.username; } else - if (req.isTokenAuthenticated) { + if(req.isTokenAuthenticated) { loggedUser = req.tokenUsername; } - if (!req.query.page) { - res.send({error: 'Specify the \'page\' parameter'}); + if(!req.query.page) { + res.send({error:"Specify the 'page' parameter"}); return; } - page = Math.max(0, parseInt(req.query.page)); + page = Math.max(0,parseInt(req.query.page)); nItemsPerPage = 20; - dataSlices.getProjectsSlice(req, page * nItemsPerPage, nItemsPerPage) - .then(values => { + dataSlices.getProjectsSlice(req,page*nItemsPerPage,nItemsPerPage) + .then(function (values) { res.json(values); }); }; @@ -305,19 +303,19 @@ const api_projectAll = function (req, res) { * @param {Object} res Res object from express * @result A json object with project data */ -const api_projectFiles = function (req, res) { - const projShortname = req.params.projectName; - const start = req.query.start; - const length = req.query.length; - const namesFlag = req.query.names; - - console.log('projShortname:', projShortname, 'start:', start, 'length:', length, 'namesFlag:', namesFlag); - dataSlices.getProjectFilesSlice(req, projShortname, start, length, namesFlag) - .then(list => { - res.send(list); +var api_projectFiles = function(req, res) { + var projShortname = req.params.projectName; + var start = req.query.start; + var length = req.query.length; + var namesFlag = req.query.names; + + console.log("projShortname:",projShortname, "start:",start, "length:",length, "namesFlag:",namesFlag); + dataSlices.getProjectFilesSlice(req,projShortname, start, length, namesFlag) + .then(function(list) { + res.send(list); }) - .catch(err => { - console.log('ERROR:', err); + .catch(function(err) { + console.log("ERROR:",err); res.send(); }); }; @@ -328,87 +326,86 @@ const api_projectFiles = function (req, res) { * @param {Object} req Req object from express * @param {Object} res Res object from express */ -const settings = function (req, res) { - const login = (req.isAuthenticated()) ? - ('' + req.user.username + ' (Log Out)') : - ('Log in with GitHub'); - let loggedUser = 'anonymous'; - if (req.isAuthenticated()) { +var settings = function(req, res) { + var login = (req.isAuthenticated()) ? + ("" + req.user.username + " (Log Out)") + : ("Log in with GitHub"); + var loggedUser = "anonymous"; + if(req.isAuthenticated()) { loggedUser = req.user.username; } else - if (req.isTokenAuthenticated) { + if(req.isTokenAuthenticated) { loggedUser = req.tokenUsername; } - // Store return path in case of login + // store return path in case of login req.session.returnTo = req.originalUrl; - req.db.get('project').findOne({shortname: req.params.projectName, backup: {$exists: 0}}, '-_id') - .then(json => { - if (json) { - // Check that the logged user has access to view this project - if (checkAccess.toProject(json, loggedUser, 'view') === false) { - res.status(401).send('Authorization required'); - return; - } - } else { - json = { - name: '', - shortname: req.params.projectName, - url: '', - brainboxURL: '/project/' + req.params.projectName, - created: (new Date()).toJSON(), - owner: loggedUser, - collaborators: { - list: [ - { - userID: 'anyone', - access: { - collaborators: 'view', - annotations: 'edit', - files: 'view' - } + req.db.get('project').findOne({shortname:req.params.projectName,backup:{$exists:0}},"-_id") + .then(function(json) { + if(json) { + // check that the logged user has access to view this project + if(checkAccess.toProject(json, loggedUser, "view") === false) { + res.status(401).send("Authorization required"); + return; + } + } else { + json = { + name: "", + shortname: req.params.projectName, + url: "", + brainboxURL: "/project/"+req.params.projectName, + created: (new Date()).toJSON(), + owner: loggedUser, + collaborators: { + list: [ + { + userID: 'anyone', + access: { + collaborators: 'view', + annotations: 'edit', + files: 'view' + } + } + ] + }, + files: { + list: [] + }, + annotations: { + list: [] } - ] - }, - files: { - list: [] - }, - annotations: { - list: [] + }; } - }; - } - // Empty the files.list: it will be filled progressively from the client - json.files.list = []; + // empty the files.list: it will be filled progressively from the client + json.files.list = []; - // Find username and name for each of the collaborators in the project - let j, - arr1 = []; - for (j = 0; j < json.collaborators.list.length; j++) { - arr1.push(req.db.get('user').findOne({nickname: json.collaborators.list[j].userID, backup: {$exists: 0}}, {name: 1, _id: 0})); - } - Promise.all(arr1) - .then(obj => { - let j; - for (j = 0; j < obj.length; j++) { - json.collaborators.list[j].username = json.collaborators.list[j].userID; - if (obj[j]) { // Name found - json.collaborators.list[j].name = obj[j].name; - } else { // Name not found: set to empty - json.collaborators.list[i].name = ''; + // find username and name for each of the collaborators in the project + var j, arr1 = []; + for(j=0;j' + req.user.username + ' (Log Out)') : - ('Log in with GitHub'); - let loggedUser = 'anonymous'; - if (req.isAuthenticated()) { +var newProject = function(req, res) { + var login = (req.isAuthenticated()) ? + ("" + req.user.username + " (Log Out)") + : ("Log in with GitHub"); + var loggedUser = "anonymous"; + if(req.isAuthenticated()) { loggedUser = req.user.username; } else - if (req.isTokenAuthenticated) { + if(req.isTokenAuthenticated) { loggedUser = req.tokenUsername; } - // Store return path in case of login + // store return path in case of login req.session.returnTo = req.originalUrl; - if (loggedUser === 'anonymous') { + if(loggedUser === "anonymous" ) { var context = { - title: 'BrainBox: New Project', - functionality: 'create a new project', - login + title: "BrainBox: New Project", + functionality: "create a new project", + login: login }; - res.render('askForLogin', context); + res.render('askForLogin',context); } else { var context = { - title: 'BrainBox: New Project', - login + title: "BrainBox: New Project", + login: login }; - res.render('projectNew', context); + res.render('projectNew',context); } }; -function insertMRInames(req, res, list) { - // Insert MRI names, but only if they don't exist - for (let i = 0; i < list.length; i++) { - const name = list[i].name; - const source = list[i].source; - const filename = url.parse(source).pathname.split('/').pop(); - - // It there's no name, continue to the next mri - if (!name) { +function insertMRInames(req,res,list) { + // insert MRI names, but only if they don't exist + for(var i=0;i { - const hash = crypto.createHash('md5').update(so).digest('hex'); - - // If mri exists, and has no name, insert the name - if (!mri) { + + // check if the mri entry already exists + (function(na,so,fi) { // without a closure, only the last name in the list is used and repeated + req.db.get('mri').findOne({source:so,backup:{$exists:0}}) + .then(function (mri) { + var hash = crypto.createHash('md5').update(so).digest('hex'); + + // if mri exists, and has no name, insert the name + if(!mri) { mri = { filename: fi, source: so, - url: '/data/' + hash + '/', + url: "/data/" + hash + "/", included: (new Date()).toJSON(), owner: req.user.username, mri: { @@ -487,31 +483,31 @@ function insertMRInames(req, res, list) { } }; } else { - delete mri._id; + delete mri["_id"]; } - mri.modified = (new Date()).toJSON(); + mri.modified=(new Date()).toJSON(); mri.modifiedBy = req.user.username; - + /* Use this if you want imported names to overwrite existing ones */ mri.name = na; - + /* Use this if you want imported names to be used only if no previous name exists */ /* if(!mri.name) { mri.name=na; } */ - + // sanitise json - mri = JSON.parse(DOMPurify.sanitize(JSON.stringify(mri))); // Sanitize works on strings, not objects + mri=JSON.parse(DOMPurify.sanitize(JSON.stringify(mri))); // sanitize works on strings, not objects // update and insert - req.db.get('mri').update({source: mri.source}, {$set: {backup: true}}, {multi: true}) - .then(() => { + req.db.get('mri').update({source:mri.source},{$set:{backup:true}},{multi:true}) + .then(function () { req.db.get('mri').insert(mri); }); }); - })(name, source, filename); + })(name,source,filename); } } @@ -521,159 +517,159 @@ function insertMRInames(req, res, list) { * @param {Object} req Req object from express * @param {Object} res Res object from express */ -const post_project = function (req, res) { - let loggedUser = 'anonymous'; - if (req.isAuthenticated()) { +var post_project = function(req, res) { + var loggedUser = "anonymous"; + if(req.isAuthenticated()) { loggedUser = req.user.username; } else - if (req.isTokenAuthenticated) { + if(req.isTokenAuthenticated) { loggedUser = req.tokenUsername; } - if (loggedUser == 'anonymous') { - console.log('ERROR not Authenticated'); - res.status(403).json({error: 'error', message: 'User not authenticated'}); + if (loggedUser == "anonymous") + { + console.log("ERROR not Authenticated"); + res.status(403).json({error:"error",message:"User not authenticated"}); return; } - const obj = JSON.parse(DOMPurify.sanitize(req.body.data)); - let k; - + var obj = JSON.parse(DOMPurify.sanitize(req.body.data)); + var k; + /** * @todo Replace .find call by .findOne. if(result.length) should change * to if(result) */ - isProjectObject(req, res, obj) - .then(obj => { - req.db.get('project').findOne({shortname: obj.shortname, backup: {$exists: false}}) - .then(project => { - // Update/insert project - if (project) { - // Project exists, save update - if (checkAccess.toProject(project, loggedUser, 'edit') == false) { - console.log('User does not have edit rights'); - res.status(403).json({error: 'error', message: 'User does not have edit rights'}); + isProjectObject(req,res,obj) + .then(function(obj) { + req.db.get('project').findOne({shortname:obj.shortname, backup:{$exists:false}}) + .then(function (project) { + // update/insert project + if(project) { + // project exists, save update + if(checkAccess.toProject(project, loggedUser, "edit") == false ) { + console.log("User does not have edit rights"); + res.status(403).json({error:"error",message:"User does not have edit rights"}); return; } - console.log('updating...'); - req.db.get('project').update({shortname: obj.shortname}, {$set: {backup: true}}, {multi: true}) - .then(() => { - obj.modified = (new Date()).toJSON(); + console.log("updating..."); + req.db.get('project').update({shortname:obj.shortname},{$set:{backup:true}},{multi:true}) + .then(function () { + obj.modified=(new Date()).toJSON(); obj.modifiedBy = req.user.username; req.db.get('project').insert(obj); - - // Insert MRI names if provided - console.log('insert mri names'); - insertMRInames(req, res, obj.files.list); - - // Reformat file list - console.log('reformat file list'); - for (k = 0; k < obj.files.list.length; k++) { - obj.files.list[k] = obj.files.list[k].source; - } - - console.log('success: true'); - res.json({success: true, message: 'Project settings updated'}); + + // insert MRI names if provided + console.log("insert mri names"); + insertMRInames(req,res,obj.files.list); + + // reformat file list + console.log("reformat file list"); + for(k=0;k { - console.log('ERROR', error); - res.status(300).json({error}); + .catch(function(error) { + console.log("ERROR",error); + res.status(300).json({"error":error}); }); -}; +} /** * @function delete_project * @desc Delete a project * @param {Object} req Req object from express * @param {Object} res Res object from express */ -const delete_project = function (req, res) { - let shortname; - let loggedUser = 'anonymous'; - if (req.isAuthenticated()) { +var delete_project = function(req, res) { + var shortname; + var loggedUser = "anonymous"; + if(req.isAuthenticated()) { loggedUser = req.user.username; } else - if (req.isTokenAuthenticated) { + if(req.isTokenAuthenticated) { loggedUser = req.tokenUsername; } - - if (loggedUser == 'anonymous') { - console.log('The user is not logged in'); - res.json({success: false, message: 'User not authenticated'}); + + if (loggedUser == "anonymous") { + console.log("The user is not logged in"); + res.json({success:false,message:"User not authenticated"}); return; } shortname = req.params.projectName; - req.db.get('project').findOne({shortname, backup: {$exists: 0}}) - .then(project => { - if (project) { - console.log('>> project does exist'); - if (checkAccess.toProject(project, loggedUser, 'remove') == true) { - console.log('>> user does have remove rights'); - - let query = {}, - update = {}; - query['mri.annotations.' + shortname] = {$exists: 1}; - query.backup = {$exists: 0}; - update.$unset = {}; - update.$unset['mri.annotations.' + shortname] = ''; - + req.db.get('project').findOne({shortname: shortname, backup: {$exists: 0}}) + .then(function(project) { + if(project) { + console.log(">> project does exist"); + if(checkAccess.toProject(project, loggedUser, "remove") == true ) { + console.log(">> user does have remove rights"); + + var query = {}, update = {}; + query["mri.annotations."+shortname] = {$exists:1}; + query["backup"] = {$exists:0}; + update["$unset"] = {}; + update["$unset"]["mri.annotations."+shortname] = ""; + Promise.all([ - req.db.get('project').remove({_id: project._id, backup: {$exists: false}}), + req.db.get('project').remove({_id:project._id, backup:{$exists:false}}), req.db.get('mri').update(query, update, {multi: true}), - req.db.get('mri').update({'mri.atlas': {$elemMatch: {project: shortname}}}, {$pull: {'mri.atlas': {project: shortname}}}, {multi: true}) + req.db.get('mri').update({"mri.atlas":{$elemMatch:{project:shortname}}}, {$pull:{"mri.atlas":{project:shortname}}}, {multi: true}) ]) - .then(() => { - console.log('>> project and project-related annotations removed'); - res.json({success: true, message: 'Project deleted'}); + .then(function () { + console.log(">> project and project-related annotations removed"); + res.json({success:true, message:"Project deleted"}); }) - .catch(err => { - console.log('ERROR: cannot remove project or project-related annotations'); - res.json({success: false, message: 'Unable to delete. Try again later'}); + .catch(function(err) { + console.log("ERROR: cannot remove project or project-related annotations"); + res.json({success:false ,message:"Unable to delete. Try again later"}); }); } else { - console.log('WARNING: user does not have remove rights'); - res.json({success: false, message: 'The user is not allowed to delete this project'}); + console.log("WARNING: user does not have remove rights"); + res.json({success:false,message:"The user is not allowed to delete this project"}); } } else { - console.log('WARNING: project does not exist'); - res.json({success: false, message: 'Unable to delete. Project does not exist in the database'}); + console.log("WARNING: project does not exist"); + res.json({success:false,message:"Unable to delete. Project does not exist in the database"}); } - }).catch(err => { - console.log('ERROR: unable to query the db'); - res.json({success: false, message: 'Unable to delete. Try again later'}); + + }).catch(function(err) { + console.log("ERROR: unable to query the db"); + res.json({success:false,message:"Unable to delete. Try again later"}); }); -}; +} + -const projectController = function () { - this.validator = validator; - this.api_projectAll = api_projectAll; - this.api_project = api_project; - this.api_projectFiles = api_projectFiles; - this.project = project; - this.settings = settings; - this.newProject = newProject; +var projectController = function(){ + this.validator = validator; + this.api_projectAll = api_projectAll; + this.api_project = api_project; + this.api_projectFiles = api_projectFiles; + this.project = project; + this.settings = settings; + this.newProject = newProject; this.post_project = post_project; this.delete_project = delete_project; -}; +} -module.exports = new projectController(); +module.exports = new projectController(); \ No newline at end of file diff --git a/controller/user/index.js b/controller/user/index.js index 24b26a43..32364d69 100644 --- a/controller/user/index.js +++ b/controller/user/index.js @@ -1,13 +1,14 @@ -const express = require('express'); -const controller = require('./user.controller'); +var express = require('express'); +var controller = require('./user.controller'); -const router = new express.Router(); +var router = express.Router(); router.get('/json', controller.api_userAll); -router.get('/json/:userName', controller.validator, controller.api_user); -router.get('/json/:userName/files', controller.validator, controller.api_userFiles); -router.get('/json/:userName/atlas', controller.validator, controller.api_userAtlas); -router.get('/json/:userName/projects', controller.validator, controller.api_userProjects); -router.get('/:userName', controller.validator, controller.user); +router.get('/json/:userName', controller.validator , controller.api_user); +router.get('/json/:userName/files', controller.validator , controller.api_userFiles); +router.get('/json/:userName/atlas', controller.validator , controller.api_userAtlas); +router.get('/json/:userName/projects', controller.validator , controller.api_userProjects); +router.get('/:userName', controller.validator , controller.user); -module.exports = router; + +module.exports = router; \ No newline at end of file diff --git a/controller/user/user.controller.js b/controller/user/user.controller.js index 7f698868..c6367e8e 100644 --- a/controller/user/user.controller.js +++ b/controller/user/user.controller.js @@ -1,64 +1,64 @@ -const async = require('async'); -const dateFormat = require('dateformat'); -const checkAccess = require('../../js/checkAccess.js'); -const dataSlices = require('../../js/dataSlices.js'); +var async = require("async"); +var dateFormat = require("dateformat"); +var checkAccess = require("../../js/checkAccess.js"); +var dataSlices = require("../../js/dataSlices.js"); -const validator = function (req, res, next) { - // UserName can be an ip address (for anonymous users) +var validator = function(req, res, next) { + + // userName can be an ip address (for anonymous users) + + /* + req.checkParams('userName', 'incorrect user name').isAlphanumeric(); + var errors = req.validationErrors(); + console.log(errors); + if (errors) { + res.send(errors).status(403).end(); + } else { + return next(); + } + */ + next(); +} - /* - req.checkParams('userName', 'incorrect user name').isAlphanumeric(); - var errors = req.validationErrors(); - console.log(errors); - if (errors) { - res.send(errors).status(403).end(); - } else { - return next(); - } - */ - next(); -}; - -const user = function (req, res) { - const login = (req.isAuthenticated()) ? - ('' + req.user.username + ' (Log Out)') : - ('Log in with GitHub'); - const requestedUser = req.params.userName; +var user = function(req, res) { + var login = (req.isAuthenticated()) ? + ("" + req.user.username + " (Log Out)") + : ("Log in with GitHub"); + var requestedUser = req.params.userName; - // Store return path in case of login + // store return path in case of login req.session.returnTo = req.originalUrl; - req.db.get('user').findOne({nickname: requestedUser}, '-_id') - .then(json => { - if (json) { - const context = { + req.db.get('user').findOne({nickname: requestedUser}, "-_id") + .then(function (json) { + if(json) { + var context = { username: json.name, nickname: json.nickname, - joined: dateFormat(json.joined, 'dddd d mmm yyyy, HH:MM'), + joined: dateFormat(json.joined, "dddd d mmm yyyy, HH:MM"), avatar: json.avatarURL, title: requestedUser, userInfo: JSON.stringify(json), - tab: req.query.tab || 'mri', - login + tab: req.query.tab||"mri", + login: login }; - res.render('user', context); + res.render('user',context); } else { - res.status(404).send('User Not Found'); + res.status(404).send("User Not Found"); } }) - .catch(err => { - console.log('ERROR:', err); - res.status(400).send('Error'); + .catch(function(err) { + console.log("ERROR:",err); + res.status(400).send("Error"); }); }; -const api_user = function (req, res) { - req.db.get('user').findOne({nickname: req.params.userName, backup: {$exists: false}}, '-_id') - .then(json => { +var api_user = function(req, res) { + req.db.get('user').findOne({nickname: req.params.userName, backup: {$exists: false}}, "-_id") + .then(function (json) { if (json) { if (req.query.var) { - let i, - arr = req.query.var.split('/'); + var i, arr = req.query.var.split("/"); for (i in arr) { json = json[arr[i]]; } @@ -70,19 +70,19 @@ const api_user = function (req, res) { }); }; -const api_userAll = function (req, res) { - console.log('api_userAll'); - if (!req.query.page) { - res.json({error: 'The \'pages\' parameter has to be specified'}); +var api_userAll = function(req, res) { + console.log("api_userAll"); + if(!req.query.page) { + res.json({error:"The 'pages' parameter has to be specified"}); return; } - - const page = parseInt(req.query.page); - const nItemsPerPage = 20; - - req.db.get('user').find({backup: {$exists: false}}, {skip: page * nItemsPerPage, limit: nItemsPerPage, fields: {_id: 0}}) - .then(json => { - res.send(json.map(o => { + + var page = parseInt(req.query.page); + var nItemsPerPage = 20; + + req.db.get('user').find({backup: {$exists: false}}, {skip: page*nItemsPerPage, limit: nItemsPerPage, fields:{_id:0}}) + .then(function (json) { + res.send(json.map(function(o) { return o.nickname; })); }); @@ -94,19 +94,19 @@ const api_userAll = function (req, res) { /** * @todo Check access rights for this route */ -const api_userFiles = function (req, res) { - const userName = req.params.userName; - const start = parseInt(req.query.start); - const length = parseInt(req.query.length); - - console.log('userName:', userName, 'start:', start, 'length:', length); - dataSlices.getUserFilesSlice(req, userName, start, length) - .then(result => { - res.send(result); +var api_userFiles = function(req, res) { + var userName = req.params.userName; + var start = parseInt(req.query.start); + var length = parseInt(req.query.length); + + console.log("userName:",userName, "start:",start, "length:",length); + dataSlices.getUserFilesSlice(req,userName, start, length) + .then(function(result) { + res.send(result); }) - .catch(err => { - console.log('ERROR:', err); - res.send({success: false, list: []}); + .catch(function(err) { + console.log("ERROR:",err); + res.send({success:false,list:[]}); }); }; /** @@ -115,51 +115,51 @@ const api_userFiles = function (req, res) { /** * @todo Check access rights for this route */ -const api_userAtlas = function (req, res) { - const userName = req.params.userName; - const start = parseInt(req.query.start); - const length = parseInt(req.query.length); - - console.log('userName:', userName, 'start:', start, 'length:', length); - dataSlices.getUserAtlasSlice(req, userName, start, length) - .then(result => { - res.send(result); +var api_userAtlas = function(req, res) { + var userName = req.params.userName; + var start = parseInt(req.query.start); + var length = parseInt(req.query.length); + + console.log("userName:",userName, "start:",start, "length:",length); + dataSlices.getUserAtlasSlice(req,userName, start, length) + .then(function(result) { + res.send(result); }) - .catch(err => { - console.log('ERROR:', err); - res.send({success: false, list: []}); + .catch(function(err) { + console.log("ERROR:",err); + res.send({success:false,list:[]}); }); -}; +} /** * @function api_userProjects */ /** * @todo Check access rights for this route */ -const api_userProjects = function (req, res) { - const userName = req.params.userName; - const start = parseInt(req.query.start); - const length = parseInt(req.query.length); - - console.log('userName:', userName, 'start:', start, 'length:', length); - dataSlices.getUserProjectsSlice(req, userName, start, length) - .then(result => { - res.send(result); +var api_userProjects = function(req, res) { + var userName = req.params.userName; + var start = parseInt(req.query.start); + var length = parseInt(req.query.length); + + console.log("userName:",userName, "start:",start, "length:",length); + dataSlices.getUserProjectsSlice(req,userName, start, length) + .then(function(result) { + res.send(result); }) - .catch(err => { - console.log('ERROR:', err); - res.send({success: false, list: []}); + .catch(function(err) { + console.log("ERROR:",err); + res.send({success:false, list:[]}); }); -}; +} -const userController = function () { - this.validator = validator; - this.api_user = api_user; - this.api_userAll = api_userAll; - this.api_userFiles = api_userFiles; - this.api_userAtlas = api_userAtlas; - this.api_userProjects = api_userProjects; - this.user = user; -}; +var userController = function(){ + this.validator = validator; + this.api_user = api_user; + this.api_userAll = api_userAll; + this.api_userFiles = api_userFiles; + this.api_userAtlas = api_userAtlas; + this.api_userProjects = api_userProjects; + this.user = user; +} -module.exports = new userController(); +module.exports = new userController(); \ No newline at end of file diff --git a/github-keys.json.example b/github-keys.json.example index ae6939ea..6e24f4cc 100644 --- a/github-keys.json.example +++ b/github-keys.json.example @@ -1,5 +1,5 @@ { - "clientID": "GET YOURS AT https://github.com/settings/applications/new", - "clientSecret": "GET YOURS AT https://github.com/settings/applications/new", - "callbackURL": "http://brainbox.dev/auth/github/callback" + "clientID": "GET YOURS AT https://github.com/settings/applications/new", + "clientSecret": "GET YOURS AT https://github.com/settings/applications/new", + "callbackURL": "http://brainbox.dev/auth/github/callback" } diff --git a/js/atlasMakerServer.js b/js/atlasMakerServer.js index e32ecd92..7fa032ff 100644 --- a/js/atlasMakerServer.js +++ b/js/atlasMakerServer.js @@ -1,1318 +1,1258 @@ -const fs = require('fs'); -const path = require('path'); -const request = require('request'); - -// Get whitelist and blacklist -const useWhitelist = false; -const useBlacklist = true; -const whitelist = JSON.parse(fs.readFileSync(path.resolve(__dirname + '/../whitelist.json'))); -const blacklist = JSON.parse(fs.readFileSync(path.resolve(__dirname + '/../blacklist.json'))); -console.log('Use whitelist:', useWhitelist); +var fs = require('fs'); +var request = require('request'); + +// get whitelist and blacklist +var useWhitelist = false; +var useBlacklist = true; +var whitelist = JSON.parse(fs.readFileSync(__dirname + "/../whitelist.json")); +var blacklist = JSON.parse(fs.readFileSync(__dirname + "/../blacklist.json")); +console.log("Use whitelist:",useWhitelist); console.log(whitelist); -console.log('Use blacklist:', useBlacklist); +console.log("Use blacklist:",useBlacklist); console.log(blacklist); -let http = require('http'), - // Server = http.createServer(), - server = http.createServer((req, res) => { - const ip = req.ip || - req.connection.remoteAddress || - req.socket.remoteAddress || - req.connection.socket.remoteAddress; - }), - url = require('url'), - WebSocketServer = require('ws').Server, - websocket, - port = 8080; - -server.on('upgrade', (req, socket, head) => { - let ip = req.ip || - req.connection.remoteAddress || - req.socket.remoteAddress || - req.connection.socket.remoteAddress; - ip = ip.split(':').pop(); - console.log('UPGRADING SERVER WITH IP', ip); - - if (useWhitelist && !whitelist[ip]) { - console.log('------------------------------> not in whitelist', ip); - setTimeout(() => { - console.log('not in whitelist: end'); +var http = require('http'), + //server = http.createServer(), + server = http.createServer(function(req, res) { + var ip = req.ip + || req.connection.remoteAddress + || req.socket.remoteAddress + || req.connection.socket.remoteAddress; + }), + url = require('url'), + WebSocketServer = require('ws').Server, + websocket, + port = 8080; + +server.on("upgrade", function(req, socket, head) { + var ip = req.ip + || req.connection.remoteAddress + || req.socket.remoteAddress + || req.connection.socket.remoteAddress; + ip = ip.split(":").pop(); + console.log("UPGRADING SERVER WITH IP",ip); + + if(useWhitelist && !whitelist[ip]) { + console.log("------------------------------> not in whitelist", ip); + setTimeout(function() { + console.log("not in whitelist: end"); socket.destroy(); }, 5000); } - + if (useBlacklist && blacklist[ip]) { - console.log('------------------------------> blacklist', ip); - setTimeout(() => { - console.log('blacklist: end'); + console.log("------------------------------> blacklist", ip); + setTimeout(function() { + console.log("blacklist: end"); socket.destroy(); }, 5000); } -}); - -const os = require('os'); -const zlib = require('zlib'); -const fileType = require('file-type'); -const jpeg = require('jpeg-js'); // Jpeg-js library: https://github.com/eugeneware/jpeg-js -const keypress = require('keypress'); -const dateFormat = require('dateformat'); -const async = require('async'); -const Struct = require('struct'); -const child_process = require('child_process'); -const merge = require('merge'); - -const mongo = require('mongodb'); -const monk = require('monk'); +}) -const db = monk('localhost:27017/brainbox'); +var os=require('os'); +var fs=require('fs'); +var zlib=require('zlib'); +var fileType=require('file-type'); +var jpeg=require('jpeg-js'); // jpeg-js library: https://github.com/eugeneware/jpeg-js +var keypress = require('keypress'); +var dateFormat = require('dateformat'); +var async = require('async'); +var Struct = require('struct'); +var child_process = require('child_process'); +var merge = require('merge'); + +var mongo = require('mongodb'); +var monk = require('monk'); +var db = monk('localhost:27017/brainbox'); const createDOMPurify = require('dompurify'); const jsdom = require('jsdom'); - const window = jsdom.jsdom('', { - features: { - FetchExternalResources: false, // Disables resource loading over HTTP / filesystem - ProcessExternalResources: false // Do not execute JS within script blocks - } + features: { + FetchExternalResources: false, // disables resource loading over HTTP / filesystem + ProcessExternalResources: false // do not execute JS within script blocks + } }).defaultView; const DOMPurify = createDOMPurify(window); -const jsonpatch = require('fast-json-patch'); - -const atlasMakerServer = function () { - let debug = 1; - this.dataDirectory = ''; - const Atlases = []; - this.Brains = []; - const US = []; - let uidcounter = 1; - let enterCommands = false; - const UndoStack = []; - let recordWS = false; - let recordedWSTraffic = []; - - const NiiHdr = new Struct() - .word32Sle('sizeof_hdr') // Size of the header. Must be 348 (bytes) - .chars('data_type', 10) // Not used; compatibility with analyze. - .chars('db_name', 18) // Not used; compatibility with analyze. - .word32Sle('extents') // Not used; compatibility with analyze. - .word16Sle('session_error') // Not used; compatibility with analyze. - .word8('regular') // Not used; compatibility with analyze. - .word8('dim_info') // Encoding directions (phase, frequency, slice). - .array('dim', 8, 'word16Sle') // Data array dimensions. - .floatle('intent_p1') // 1st intent parameter. - .floatle('intent_p2') // 2nd intent parameter. - .floatle('intent_p3') // 3rd intent parameter. - .word16Sle('intent_code') // Nifti intent. - .word16Sle('datatype') // Data type. - .word16Sle('bitpix') // Number of bits per voxel. - .word16Sle('slice_start') // First slice index. - .array('pixdim', 8, 'floatle') // Grid spacings (unit per dimension). - .floatle('vox_offset') // Offset into a .nii file. - .floatle('scl_slope') // Data scaling, slope. - .floatle('scl_inter') // Data scaling, offset. - .word16Sle('slice_end') // Last slice index. - .word8('slice_code') // Slice timing order. - .word8('xyzt_units') // Units of pixdim[1..4]. - .floatle('cal_max') // Maximum display intensity. - .floatle('cal_min') // Minimum display intensity. - .floatle('slice_duration') // Time for one slice. - .floatle('toffset') // Time axis shift. - .word32Sle('glmax') // Not used; compatibility with analyze. - .word32Sle('glmin') // Not used; compatibility with analyze. - .chars('descrip', 80) // Any text. - .chars('aux_file', 24) // Auxiliary filename. - .word16Sle('qform_code') // Use the quaternion fields. - .word16Sle('sform_code') // Use of the affine fields. - .floatle('quatern_b') // Quaternion b parameter. - .floatle('quatern_c') // Quaternion c parameter. - .floatle('quatern_d') // Quaternion d parameter. - .floatle('qoffset_x') // Quaternion x shift. - .floatle('qoffset_y') // Quaternion y shift. - .floatle('qoffset_z') // Quaternion z shift. - .array('srow_x', 4, 'floatle') // 1st row affine transform - .array('srow_y', 4, 'floatle') // 2nd row affine transform. - .array('srow_z', 4, 'floatle') // 3rd row affine transform. - .chars('intent_name', 16) // Name or meaning of the data. - .chars('magic', 4); // Magic string. - - const MghHdr = Struct() - .word32Sbe('v') - .word32Sbe('ndim1') - .word32Sbe('ndim2') - .word32Sbe('ndim3') - .word32Sbe('nframes') - .word32Sbe('type') - .word32Sbe('dof') - .word16Sbe('ras_good_flag') - .array('delta', 3, 'floatbe') - .array('Mdc', 9, 'floatbe') - .array('Pxyz_c', 3, 'floatbe'); - - const MghFtr = Struct() - .array('mrparms', 4, 'floatbe'); - - console.log('atlasMakerServer.js'); - console.log('date:', new Date()); - setInterval(() => { - console.log('date:', new Date()); - }, 60 * 60 * 1000); // Time mark every 60 minutes - console.log('free memory', os.freemem()); - - function traceLog(f, l) { - if (l == undefined || debug > l) { - console.log('ams> ' + (f.name) + ' ' + (f.caller ? (f.caller.name || 'annonymous') : 'root')); - } - } - - const bufferTag = function bufferTag(str, sz) { - traceLog(bufferTag); - - const buf = new Buffer(sz).fill(32); - buf.write(str); - return buf; - }; - const niiTag = bufferTag('nii', 8); - const mghTag = bufferTag('mgh', 8); - const jpgTag = bufferTag('jpg', 8); - - const displayAtlases = function displayAtlases() { - traceLog(displayAtlases); - console.log('\n' + Atlases.filter(o => { - return o !== undefined; - }).length + ' Atlases:'); - for (var i in Atlases) { - const sum = numberOfUsersConnectedToAtlas(Atlases[i].dirname, Atlases[i].name); - console.log('Atlases[' + i + '] path:' + Atlases[i].dirname + Atlases[i].name + ', ' + sum + ' users connected'); - } - for (var i in Atlases) { - console.log('atlas', i, Atlases[i]); - } - }; - const displayBrains = function displayBrains() { - traceLog(displayBrains); - console.log('\n' + Brains.filter(o => { - return o !== undefined; - }).length + ' Brains:'); - let i; - for (i in Brains) { - const sum = numberOfUsersConnectedToMRI(Brains[i].path); - console.log('Brains[' + i + '].path=' + Brains[i].path + ', ' + sum + ' users connected'); - } - for (i in Brains) { - console.log('Brains[' + i + ']'); - console.log(' path:', Brains[i].path); - console.log(' data.dim:', Brains[i].data.dim); - console.log(' data.pixdim:', Brains[i].data.pixdim); - console.log('data.vox_offset:', Brains[i].data.vox_offset); - console.log(' data.dir:', Brains[i].data.dir); - console.log(' data.ori:', Brains[i].data.ori); - console.log(' data.s2v:', Brains[i].data.s2v); - console.log(' data.v2w:', Brains[i].data.v2w); - console.log(' data.wori:', Brains[i].data.wori); - console.log(' data.datatype:', Brains[i].data.datatype); - console.log(' data.sum:', Brains[i].data.sum); - console.log(); - } - }; - const displayUsers = function displayUsers() { - traceLog(displayUsers); - console.log('\n' + US.filter(o => { - return o !== undefined; - }).length + ' User Sockets:'); - for (const i in US) { - console.log('US[' + i + '].uid=', US[i].uid); - console.log('US[' + i + ']=', US[i].User); - } - }; - const toggleWebsocketRecording = function toggleWebsocketRecording() { - recordWS = !recordWS; - if (recordWS) { - console.log('recording WebSocket traffic'); - } else { - console.log(JSON.stringify(recordedWSTraffic)); - console.log('finished recording WebSocket traffic'); - recordedWSTraffic = []; - } - }; - keypress(process.stdin); - process.stdin.on('keypress', (ch, key) => { - if (key) { - // Console.log(ch,key); - if (key.name === 'c' && key.ctrl) { - console.log('Exit.'); - process.exit(); - } - if (key.name === 'escape') { - enterCommands = !enterCommands; - console.log('enterCommands: ' + enterCommands); - } - if (key.name === 'backspace') { - process.stdout.write('\b'); - } - if (enterCommands === false) { - if (key.name === 'return') { - console.log(); - } - } - } +var jsonpatch = require('fast-json-patch'); + +var atlasMakerServer = function() { + +var debug = 1; +this.dataDirectory = ""; +var Atlases = []; +this.Brains = []; +var US = []; +var uidcounter = 1; +var enterCommands = false; +var UndoStack = []; +var recordWS = false; +var recordedWSTraffic = []; + +var NiiHdr = new Struct() + .word32Sle('sizeof_hdr') // Size of the header. Must be 348 (bytes) + .chars('data_type',10) // Not used; compatibility with analyze. + .chars('db_name',18) // Not used; compatibility with analyze. + .word32Sle('extents') // Not used; compatibility with analyze. + .word16Sle('session_error') // Not used; compatibility with analyze. + .word8('regular') // Not used; compatibility with analyze. + .word8('dim_info') // Encoding directions (phase, frequency, slice). + .array('dim',8,'word16Sle') // Data array dimensions. + .floatle('intent_p1') // 1st intent parameter. + .floatle('intent_p2') // 2nd intent parameter. + .floatle('intent_p3') // 3rd intent parameter. + .word16Sle('intent_code') // nifti intent. + .word16Sle('datatype') // Data type. + .word16Sle('bitpix') // Number of bits per voxel. + .word16Sle('slice_start') // First slice index. + .array('pixdim',8,'floatle') // Grid spacings (unit per dimension). + .floatle('vox_offset') // Offset into a .nii file. + .floatle('scl_slope') // Data scaling, slope. + .floatle('scl_inter') // Data scaling, offset. + .word16Sle('slice_end') // Last slice index. + .word8('slice_code') // Slice timing order. + .word8('xyzt_units') // Units of pixdim[1..4]. + .floatle('cal_max') // Maximum display intensity. + .floatle('cal_min') // Minimum display intensity. + .floatle('slice_duration') // Time for one slice. + .floatle('toffset') // Time axis shift. + .word32Sle('glmax') // Not used; compatibility with analyze. + .word32Sle('glmin') // Not used; compatibility with analyze. + .chars('descrip',80) // Any text. + .chars('aux_file',24) // Auxiliary filename. + .word16Sle('qform_code') // Use the quaternion fields. + .word16Sle('sform_code') // Use of the affine fields. + .floatle('quatern_b') // Quaternion b parameter. + .floatle('quatern_c') // Quaternion c parameter. + .floatle('quatern_d') // Quaternion d parameter. + .floatle('qoffset_x') // Quaternion x shift. + .floatle('qoffset_y') // Quaternion y shift. + .floatle('qoffset_z') // Quaternion z shift. + .array('srow_x',4,'floatle') // 1st row affine transform + .array('srow_y',4,'floatle') // 2nd row affine transform. + .array('srow_z',4,'floatle') // 3rd row affine transform. + .chars('intent_name',16) // Name or meaning of the data. + .chars('magic',4); // Magic string. +var MghHdr = Struct() + .word32Sbe('v') + .word32Sbe('ndim1') + .word32Sbe('ndim2') + .word32Sbe('ndim3') + .word32Sbe('nframes') + .word32Sbe('type') + .word32Sbe('dof') + .word16Sbe('ras_good_flag') + .array('delta',3,'floatbe') + .array('Mdc',9,'floatbe') + .array('Pxyz_c',3,'floatbe'); +var MghFtr = Struct() + .array('mrparms',4,'floatbe'); + +console.log("atlasMakerServer.js"); +console.log("date:", new Date()); +setInterval(function(){console.log("date:",new Date())},60*60*1000); // time mark every 60 minutes +console.log("free memory",os.freemem()); + +function traceLog(f, l) { + if(l==undefined || debug>l) + console.log("ams> "+(f.name)+" "+(f.caller?(f.caller.name||"annonymous"):"root")); +}; - if (ch) { - if (enterCommands) { - switch (ch) { - case 'a': - displayAtlases(); - break; - case 'b': - displayBrains(); - break; - case 'u': - displayUsers(); - break; - case 'r': - toggleWebsocketRecording(); - break; - case '0': - debug = 0; - console.log('debug level:', debug); - break; - case '1': - debug = 1; - console.log('debug level:', debug); - break; - case '2': - debug = 2; - console.log('debug level:', debug); - break; - case '3': - debug = 3; - console.log('debug level:', debug); - break; - } - } else { - process.stdout.write(ch); +var bufferTag = function bufferTag(str, sz) { + traceLog(bufferTag); + + var buf=new Buffer(sz).fill(32); + buf.write(str); + return buf; +}; +var niiTag=bufferTag("nii",8); +var mghTag=bufferTag("mgh",8); +var jpgTag=bufferTag("jpg",8); + +var displayAtlases = function displayAtlases() { + traceLog(displayAtlases); + console.log("\n"+Atlases.filter(function(o){return o!==undefined}).length+" Atlases:"); + for(var i in Atlases) { + var sum=numberOfUsersConnectedToAtlas(Atlases[i].dirname,Atlases[i].name); + console.log("Atlases["+i+"] path:"+Atlases[i].dirname+Atlases[i].name+", "+sum+" users connected"); + } + for(var i in Atlases) { + console.log("atlas",i,Atlases[i]); + } +}; +var displayBrains = function displayBrains() { + traceLog(displayBrains); + console.log("\n"+Brains.filter(function(o){return o!==undefined}).length+" Brains:"); + var i; + for(i in Brains) { + var sum=numberOfUsersConnectedToMRI(Brains[i].path); + console.log("Brains["+i+"].path="+Brains[i].path+", "+sum+" users connected"); + } + for(i in Brains) { + console.log("Brains["+i+"]"); + console.log(" path:",Brains[i].path); + console.log(" data.dim:",Brains[i].data.dim); + console.log(" data.pixdim:",Brains[i].data.pixdim); + console.log("data.vox_offset:",Brains[i].data.vox_offset); + console.log(" data.dir:",Brains[i].data.dir); + console.log(" data.ori:",Brains[i].data.ori); + console.log(" data.s2v:",Brains[i].data.s2v); + console.log(" data.v2w:",Brains[i].data.v2w); + console.log(" data.wori:",Brains[i].data.wori); + console.log(" data.datatype:",Brains[i].data.datatype); + console.log(" data.sum:",Brains[i].data.sum); + console.log(); + } +}; +var displayUsers = function displayUsers() { + traceLog(displayUsers); + console.log("\n"+US.filter(function(o){return o!==undefined}).length+" User Sockets:"); + for(var i in US) { + console.log("US["+i+"].uid=",US[i].uid); + console.log("US["+i+"]=",US[i].User); + } +} +var toggleWebsocketRecording = function toggleWebsocketRecording() { + recordWS = !recordWS; + if(recordWS) { + console.log("recording WebSocket traffic"); + } else { + console.log(JSON.stringify(recordedWSTraffic)); + console.log("finished recording WebSocket traffic"); + recordedWSTraffic=[]; + } +}; +keypress(process.stdin); +process.stdin.on('keypress', function (ch, key) { + if(key) { + // console.log(ch,key); + if(key.name === 'c' && key.ctrl) { + console.log("Exit."); + process.exit(); + } + if(key.name === 'escape') { + enterCommands = !enterCommands; + console.log("enterCommands: "+enterCommands); + } + if(key.name === 'backspace') { + process.stdout.write('\b'); + } + if(enterCommands === false) { + if(key.name === 'return') + console.log(); + } + } + + if(ch) { + if(enterCommands) { + switch (ch) { + case 'a': + displayAtlases(); + break; + case 'b': + displayBrains(); + break; + case 'u': + displayUsers(); + break; + case 'r': + toggleWebsocketRecording(); + break; + case '0': + debug=0; + console.log("debug level:",debug); + break; + case '1': + debug=1; + console.log("debug level:",debug); + break; + case '2': + debug=2; + console.log("debug level:",debug); + break; + case '3': + debug=3; + console.log("debug level:",debug); + break; } + } else { + process.stdout.write(ch); } - }); - if (process.stdin.isTTY) { - process.stdin.setRawMode(true); } - process.stdin.resume(); +}); +if (process.stdin.isTTY){ + process.stdin.setRawMode(true); +} +process.stdin.resume(); -// ======================================================================================== +//======================================================================================== // Web socket -// ======================================================================================== - const getUserFromSocket = function getUserFromSocket(socket) { - traceLog(getUserFromSocket, 1); - for (const i in US) { - if (socket === US[i].socket) { - return US[i]; - } - } - return -1; - }; - const getUserFromUserId = function getUserFromUserId(uid) { - traceLog(getUserFromUserId, 1); - for (const i in US) { - if (uid === US[i].uid) { - return US[i]; - } - } - return null; - }; - const getUserIdFromSocket = function getUserIdFromSocket(socket) { - traceLog(getUserIdFromSocket); - for (const i in US) { - if (socket === US[i].socket) { - return US[i].uid; - } - } - return null; - }; - const removeUser = function removeUser(socket) { - traceLog(removeUser); - for (const i in US) { - if (socket === US[i].socket) { - delete US[i]; - break; - } - } - }; - var numberOfUsersConnectedToMRI = function numberOfUsersConnectedToMRI(path) { - traceLog(numberOfUsersConnectedToMRI); - let sum = 0; - - if (path == undefined) { - return sum; - } - - for (const i in US) { - if (US[i].User === undefined) { - console.log('ERROR: When counting the number of users connected to MRI, user uid ' + i + ' was not defined'); - continue; - } - if (US[i].User.dirname === undefined) { - console.log('ERROR: A user uid ' + i + ' dirname is unknown'); - continue; - } - if (US[i].User.mri === undefined) { - console.log('ERROR: A user uid ' + i + ' MRI is unknown'); - continue; - } - if (US[i].User.dirname + US[i].User.mri === path) { - sum++; - } - } - /* Sum--; */ - return sum; - }; - const unloadMRI = function unloadMRI(path) { - traceLog(unloadMRI); - - let i; - for (i in Brains) { - if (Brains[i].path === path) { - delete Brains[i]; - console.log(' free memory', os.freemem()); - break; - } - } - }; - - var numberOfUsersConnectedToAtlas = function numberOfUsersConnectedToAtlas(dirname, atlasFilename) { - traceLog(numberOfUsersConnectedToAtlas); - let sum = 0; - - if (dirname === undefined || atlasFilename === undefined) { - return sum; - } +//======================================================================================== +var getUserFromSocket = function getUserFromSocket(socket) { + traceLog(getUserFromSocket,1); + for(var i in US) { + if(socket === US[i].socket) + return US[i]; + } + return -1; +}; +var getUserFromUserId = function getUserFromUserId(uid) { + traceLog(getUserFromUserId,1); + for(var i in US) { + if(uid === US[i].uid) + return US[i]; + } + return null; +}; +var getUserIdFromSocket = function getUserIdFromSocket(socket) { + traceLog(getUserIdFromSocket); + for(var i in US) { + if(socket === US[i].socket) + return US[i].uid; + } + return null; +}; +var removeUser = function removeUser(socket) { + traceLog(removeUser); + for(var i in US) { + if(socket === US[i].socket) { + delete US[i]; + break; + } + } +}; +var numberOfUsersConnectedToMRI = function numberOfUsersConnectedToMRI(path) { + traceLog(numberOfUsersConnectedToMRI); + var sum=0; + + if(path == undefined) + return sum; + + for(var i in US) { + if(US[i].User===undefined) { + console.log("ERROR: When counting the number of users connected to MRI, user uid "+i+" was not defined"); + continue; + } + if(US[i].User.dirname===undefined) { + console.log("ERROR: A user uid "+i+" dirname is unknown"); + continue; + } + if(US[i].User.mri===undefined) { + console.log("ERROR: A user uid "+i+" MRI is unknown"); + continue; + } + if(US[i].User.dirname+US[i].User.mri===path) + sum++; + } + /* sum--; */ + return sum; +}; +var unloadMRI = function unloadMRI(path) { + traceLog(unloadMRI); + + var i; + for(i in Brains) { + if(Brains[i].path===path) { + delete Brains[i]; + console.log(" free memory",os.freemem()); + break; + } + } +} - for (i in US) { - if (US[i].User === undefined) { - console.log('ERROR: When counting the number of users connected to the atlas, user uid ' + i + ' was not defined'); - continue; - } - if (US[i].User.dirname === undefined) { - console.log('ERROR: A user uid ' + i + ' dirname is unknown'); - continue; - } - if (US[i].User.atlasFilename === undefined) { - console.log('ERROR: A user uid ' + i + ' atlasFilename is unknown'); - continue; - } - if (US[i].User.dirname === dirname && US[i].User.atlasFilename === atlasFilename) { - sum++; - } - } - return sum; - }; - const unloadAtlas = function unloadAtlas(dirname, atlasFilename) { - traceLog(unloadAtlas); - - let i; - for (i in Atlases) { - if (Atlases[i].dirname === dirname && Atlases[i].name === atlasFilename) { - saveAtlas(Atlases[i]) - .then(() => { - console.log(' Atlas saved. Unloading it'); +var numberOfUsersConnectedToAtlas = function numberOfUsersConnectedToAtlas(dirname,atlasFilename) { + traceLog(numberOfUsersConnectedToAtlas); + var sum=0; + + if(dirname===undefined || atlasFilename===undefined) + return sum; + + for(i in US) { + if(US[i].User===undefined) { + console.log("ERROR: When counting the number of users connected to the atlas, user uid "+i+" was not defined"); + continue; + } + if(US[i].User.dirname===undefined) { + console.log("ERROR: A user uid "+i+" dirname is unknown"); + continue; + } + if(US[i].User.atlasFilename===undefined) { + console.log("ERROR: A user uid "+i+" atlasFilename is unknown"); + continue; + } + if(US[i].User.dirname===dirname && US[i].User.atlasFilename===atlasFilename) + sum++; + } + return sum; +}; +var unloadAtlas = function unloadAtlas(dirname,atlasFilename) { + traceLog(unloadAtlas); + + var i; + for(i in Atlases) { + if(Atlases[i].dirname===dirname && Atlases[i].name===atlasFilename) { + saveAtlas(Atlases[i]) + .then(function () { + console.log(" Atlas saved. Unloading it"); clearInterval(Atlases[i].timer); delete Atlases[i]; - console.log(' free memory', os.freemem()); + console.log(" free memory",os.freemem()); }); - break; - } - } - }; + break; + } + } +} -/* ----------- */ +/*-----------*/ /* BLACKLIST */ - function verifyClient(info) { - let ip; - - if (info.req.connection.remoteAddress) { - ip = info.req.connection.remoteAddress; - console.log('connection'); - } else if (info.req.socket._peername) { - ip = info.req.socket._peername.address; - console.log('_peername'); - } else { - console.log('DEJANDO PASAR UN PASTEL...'); - return true; - } - - ip = ip.split(':').pop(); - - if (useWhitelist && !whitelist[ip]) { - console.log('==========> REJECT ip not in whitelist ', ip); - return false; - } - - if (useBlacklist && blacklist[ip]) { - console.log('==========> REJECT ip in blacklist', ip); - return false; - } - console.log('==========> ACCEPT ip ', ip); +function verifyClient(info) { + var ip; + + if(info.req.connection.remoteAddress) { + ip = info.req.connection.remoteAddress; + console.log("connection"); + } else if(info.req.socket._peername) { + ip = info.req.socket._peername.address; + console.log("_peername"); + } else { + console.log("DEJANDO PASAR UN PASTEL..."); return true; } -/* ----------- */ + + ip = ip.split(":").pop(); - const initSocketConnection = function initSocketConnection() { - traceLog(initSocketConnection); + if(useWhitelist && !whitelist[ip]) { + console.log("==========> REJECT ip not in whitelist ",ip); + return false; + } + + if(useBlacklist && blacklist[ip]) { + console.log("==========> REJECT ip in blacklist",ip); + return false; + } else { + console.log("==========> ACCEPT ip ",ip); + return true; + } +} +/*-----------*/ - // WS connection - try { - websocket = new WebSocketServer({server, verifyClient}); +var initSocketConnection = function initSocketConnection() { + traceLog(initSocketConnection); + + // WS connection + try { + websocket = new WebSocketServer({server:server, verifyClient: verifyClient}); - websocket.on('connection', function connection_fromInitSocketConnection(s) { - traceLog(connection_fromInitSocketConnection); + websocket.on("connection",function connection_fromInitSocketConnection(s) { + traceLog(connection_fromInitSocketConnection); - /* ----------- */ + /*-----------*/ /* BLACKLIST */ - let ip = s.upgradeReq.connection.remoteAddress; - ip = ip.split(':').pop(); - if (useWhitelist && !whitelist[ip]) { - console.log('--------------------> REJECT ip not in whitelist', ip); + var ip = s.upgradeReq.connection.remoteAddress; + ip = ip.split(":").pop(); + if(useWhitelist && !whitelist[ip]) { + console.log("--------------------> REJECT ip not in whitelist",ip); s.close(); return; - } - if (useBlacklist && blacklist[ip]) { - console.log('--------------------> REJECT ip in blacklist', ip); + } + if(useBlacklist && blacklist[ip]) { + console.log("--------------------> REJECT ip in blacklist",ip); s.close(); return; + } + /*-----------*/ + + console.log(" remote_address",s.upgradeReq.connection.remoteAddress); + var newUS={"uid":"u"+uidcounter++,"socket":s}; + US.push(newUS); + console.log(" User id "+newUS.uid+" connected, total: "+US.filter(function(o){return o!=undefined}).length+" users"); + + // send data from previous users + sendPreviousUserDataMessage(newUS); + + s.on('message',function message_fromInitSocketConnection(msg) { + traceLog(message_fromInitSocketConnection,1); + var sender = this; + var sourceUS=getUserFromSocket(this); + var data={}; + + if(msg instanceof Buffer) { // Handle binary data: a user uploaded an atlas file + data.data=msg; + data.type="atlas"; + } else + data=JSON.parse(msg); + data.uid=sourceUS.uid; + + // websocket traffic recording + if(recordWS) { + if(data.type == "atlas") { + recordedWSTraffic.push({type: 'atlas'}); + } else { + recordedWSTraffic.push(data); + } + } + + if(debug>1) { + console.log(); + console.log("data type:",data.type); + } + + switch(data.type) { + case "userData": + receiveUserDataMessage(data,this); + break; + case "show": + // no action performed + break; + case "paint": + receivePaintMessage(data); + break; + case "requestSlice": + receiveRequestSliceMessage(data,this); + break; + case "requestSlice2": + receiveRequestSlice2Message(data,this); + break; + case "save": + receiveSaveMessage(data,this); + break; + case "saveMetadata": + receiveSaveMetadataMessage(data,this); + break; + case "atlas": + receiveAtlasFromUserMessage(data,this); + break; + case "echo": + console.log("ECHO: '"+data.msg+"' from user "+data.username); + break; + case "userNameQuery": + var result = queryUserName(data) + .then(function(obj){ + data.metadata = obj; + sender.send(JSON.stringify(data)); + }) + .catch(function(){}); + break; + case "projectNameQuery": + var result = queryProjectName(data) + .then(function(obj){ + data.metadata = obj; + sender.send(JSON.stringify(data)); + }) + .catch(function(err){console.log("err:",err)}); + break; + case "similarProjectNamesQuery": + var result = querySimilarProjectNames(data) + .then(function(obj){ + data.metadata = obj; + sender.send(JSON.stringify(data)); + }) + .catch(function(err){console.log("err:",err)}); + break; + case "autocompleteClient": + declareAutocompleteClient(data,this); + break; + default : + break; + } + + // Broadcast + //---------- + var n=0; + + // do not broadcast the following messages + if(data.type === "requestSlice" + || data.type === "requestSlice2" + || (data.type === "userData" && data.description === "sendAtlas")) { + return; } - /* ----------- */ - - console.log(' remote_address', s.upgradeReq.connection.remoteAddress); - const newUS = {uid: 'u' + uidcounter++, socket: s}; - US.push(newUS); - console.log(' User id ' + newUS.uid + ' connected, total: ' + US.filter(o => { - return o != undefined; - }).length + ' users'); - - // Send data from previous users - sendPreviousUserDataMessage(newUS); - - s.on('message', function message_fromInitSocketConnection(msg) { - traceLog(message_fromInitSocketConnection, 1); - const sender = this; - const sourceUS = getUserFromSocket(this); - let data = {}; - - if (msg instanceof Buffer) { // Handle binary data: a user uploaded an atlas file - data.data = msg; - data.type = 'atlas'; - } else { - data = JSON.parse(msg); - } - data.uid = sourceUS.uid; - - // Websocket traffic recording - if (recordWS) { - if (data.type == 'atlas') { - recordedWSTraffic.push({type: 'atlas'}); - } else { - recordedWSTraffic.push(data); - } - } - - if (debug > 1) { - console.log(); - console.log('data type:', data.type); - } - - switch (data.type) { - case 'userData': - receiveUserDataMessage(data, this); - break; - case 'show': - // No action performed - break; - case 'paint': - receivePaintMessage(data); - break; - case 'requestSlice': - receiveRequestSliceMessage(data, this); - break; - case 'requestSlice2': - receiveRequestSlice2Message(data, this); - break; - case 'save': - receiveSaveMessage(data, this); - break; - case 'saveMetadata': - receiveSaveMetadataMessage(data, this); - break; - case 'atlas': - receiveAtlasFromUserMessage(data, this); - break; - case 'echo': - console.log('ECHO: \'' + data.msg + '\' from user ' + data.username); - break; - case 'userNameQuery': - var result = queryUserName(data) - .then(obj => { - data.metadata = obj; - sender.send(JSON.stringify(data)); -}) - .catch(() => {}); - break; - case 'projectNameQuery': - var result = queryProjectName(data) - .then(obj => { - data.metadata = obj; - sender.send(JSON.stringify(data)); -}) - .catch(err => { - console.log('err:', err); -}); - break; - case 'similarProjectNamesQuery': - var result = querySimilarProjectNames(data) - .then(obj => { - data.metadata = obj; - sender.send(JSON.stringify(data)); -}) - .catch(err => { - console.log('err:', err); -}); - break; - case 'autocompleteClient': - declareAutocompleteClient(data, this); - break; - default : - break; - } - - // Broadcast - //---------- - let n = 0; - - // Do not broadcast the following messages - if (data.type === 'requestSlice' || - data.type === 'requestSlice2' || - (data.type === 'userData' && data.description === 'sendAtlas')) { - return; - } - - // Scan through connected users - for (const i in websocket.clients) { - // I-th user - const targetUS = getUserFromSocket(websocket.clients[i]); - - // Do not auto-broadcast - if (sourceUS.uid === targetUS.uid) { - if (debug > 1) { - console.log(' no broadcast to self'); - } - continue; - } - - // Do not broadcast to autocomplete clients - if (sourceUS.autocompleteClient) { - if (debug > 1) { - console.log(' no broadcast to autocomplete clients'); - } - continue; - } - - // Do not broadcast to undefined users - if (sourceUS.User === undefined || targetUS.User === undefined) { - if (debug) { - console.log(' User ' + sourceUS.uid + ': ' + (sourceUS.User === undefined) ? 'undefined' : 'defined'); - } - if (debug) { - console.log(' User ' + targetUS.uid + ': ' + (targetUS.User === undefined) ? 'undefined' : 'defined'); - } - continue; - } - - if ((targetUS.User.projectPage && targetUS.User.projectPage === sourceUS.User.projectPage) || - (targetUS.User.iAtlas === sourceUS.User.iAtlas) || - (data.type === 'userData') || - (data.type === 'chat') - ) { - if (data.type === 'atlas') { - sendAtlasToUser(data.data, websocket.clients[i], false); - } else { - // Sanitise data - const cleanData = DOMPurify.sanitize(JSON.stringify(data)); - try { - websocket.clients[i].send(cleanData); - } catch (err) { - console.log('ERROR:', err); - } - } - } else if (debug > 1) { - console.log(' no broadcast to user ' + targetUS.User.username + ' [uid: ' + targetUS.uid + '] of atlas ' + targetUS.User.specimenName + '/' + targetUS.User.atlasFilename); - } - n++; - } - if (debug > 2) { - console.log(' broadcasted to', n, 'users'); - } - }); - - s.on('close', function close_fromInitSocketConnection(msg) { - traceLog(close_fromInitSocketConnection); - - let i, sum, nusers, sourceUS; - - console.log('A user is disconnecting'); - console.log('There are ' + US.filter(o => { - return o != undefined; - }).length + ' connected'); - - sourceUS = getUserFromSocket(this); - console.log(' The user disconnecting is: ' + sourceUS.uid); - - if (sourceUS.User === undefined) { - console.log(''); - } else if (sourceUS.User.dirname) { - console.log(' User was connected to MRI ' + sourceUS.User.dirname + sourceUS.User.mri, sourceUS.specimenName); - console.log(' User was connected to atlas ' + sourceUS.User.dirname + sourceUS.User.atlasFilename, sourceUS.specimenName); - - // Count how many users remain connected to the MRI after user leaves - sum = numberOfUsersConnectedToMRI(sourceUS.User.dirname + sourceUS.User.mri); - sum -= 1; // Subtract current user - if (sum) { - console.log(' There remain ' + sum + ' users connected to that MRI'); + + // scan through connected users + for(var i in websocket.clients) { + // i-th user + var targetUS=getUserFromSocket(websocket.clients[i]); + + // do not auto-broadcast + if(sourceUS.uid===targetUS.uid) { + if(debug>1) console.log(" no broadcast to self"); + continue; + } + + // do not broadcast to autocomplete clients + if(sourceUS.autocompleteClient) { + if(debug>1) console.log(" no broadcast to autocomplete clients"); + continue; + } + + // do not broadcast to undefined users + if( sourceUS.User===undefined || targetUS.User===undefined) { + if(debug) console.log(" User "+sourceUS.uid+": "+(sourceUS.User===undefined)?"undefined":"defined"); + if(debug) console.log(" User "+targetUS.uid+": "+(targetUS.User===undefined)?"undefined":"defined"); + continue; + } + + if (( targetUS.User.projectPage && targetUS.User.projectPage === sourceUS.User.projectPage) + || (targetUS.User.iAtlas === sourceUS.User.iAtlas) + || (data.type === "userData") + || (data.type === "chat") + ) { + if(data.type==="atlas") { + sendAtlasToUser(data.data,websocket.clients[i],false); + } else { + // sanitise data + const cleanData=DOMPurify.sanitize(JSON.stringify(data)); + try { + websocket.clients[i].send(cleanData); + } catch (err) { + console.log("ERROR:", err); + } + } + } else { + if(debug>1) console.log(" no broadcast to user "+targetUS.User.username+" [uid: "+targetUS.uid+"] of atlas "+targetUS.User.specimenName+"/"+targetUS.User.atlasFilename); + } + n++; + } + if(debug>2) console.log(" broadcasted to",n,"users"); + }); + + s.on('close',function close_fromInitSocketConnection(msg) { + traceLog(close_fromInitSocketConnection); + + var i, sum, nusers, sourceUS; + + console.log("A user is disconnecting"); + console.log("There are "+US.filter(function(o){return o!=undefined}).length+" connected"); + + sourceUS=getUserFromSocket(this); + console.log(" The user disconnecting is: "+sourceUS.uid); + + if(sourceUS.User===undefined) { + console.log(""); + } else { + if(sourceUS.User.dirname) { + console.log(" User was connected to MRI "+ sourceUS.User.dirname+sourceUS.User.mri, sourceUS.specimenName); + console.log(" User was connected to atlas "+ sourceUS.User.dirname+sourceUS.User.atlasFilename, sourceUS.specimenName); + + // count how many users remain connected to the MRI after user leaves + sum=numberOfUsersConnectedToMRI(sourceUS.User.dirname+sourceUS.User.mri); + sum-=1; // subtract current user + if(sum) { + console.log(" There remain "+sum+" users connected to that MRI"); } else { - console.log(' No user connected to MRI ' + - sourceUS.User.dirname + - sourceUS.User.mri + ': unloading it', sourceUS.specimenName); - unloadMRI(sourceUS.User.dirname + sourceUS.User.mri); + console.log(" No user connected to MRI " + + sourceUS.User.dirname + + sourceUS.User.mri+": unloading it", sourceUS.specimenName); + unloadMRI(sourceUS.User.dirname+sourceUS.User.mri); } - // Count how many users remain connected to the atlas after user leaves - sum = numberOfUsersConnectedToAtlas(sourceUS.User.dirname, sourceUS.User.atlasFilename); - sum -= 1; // Subtract current user - if (sum) { - console.log(' There remain ' + sum + ' users connected to that atlas'); + // count how many users remain connected to the atlas after user leaves + sum=numberOfUsersConnectedToAtlas(sourceUS.User.dirname,sourceUS.User.atlasFilename); + sum-=1; // subtract current user + if(sum) { + console.log(" There remain "+sum+" users connected to that atlas"); } else { - console.log(' No user connected to atlas ' + - sourceUS.User.dirname + - sourceUS.User.atlasFilename + ': unloading it', sourceUS.specimenName); - unloadAtlas(sourceUS.User.dirname, sourceUS.User.atlasFilename, sourceUS.specimenName); + console.log(" No user connected to atlas " + + sourceUS.User.dirname + + sourceUS.User.atlasFilename+": unloading it", sourceUS.specimenName); + unloadAtlas(sourceUS.User.dirname,sourceUS.User.atlasFilename, sourceUS.specimenName); } } else { - console.log('', sourceUS.User); + console.log("", sourceUS.User); } - - // Send user disconnect message to remaining users - sendDisconnectMessage(sourceUS.uid); - - // Remove the user from the list - removeUser(this); - - // Display the total number of connected users - console.log(' ' + US.filter(o => { - return o != undefined; - }).length + ' remain connected'); - console.log(); - }); - }); - server.listen(port, () => { - console.log('Listening on ' + server.address().port, server.address()); - }); - } catch (ex) { - console.log('ERROR: Unable to create a server', ex); - } - }; - this.initSocketConnection = initSocketConnection; - - var queryUserName = function queryUserName(data) { - return new Promise((resolve, reject) => { - if (data.metadata && data.metadata.nickname) { - db.get('user') - .find( - {nickname: {$regex: data.metadata.nickname}}, - {fields: ['nickname', 'name'], limit: 10}) - .then(obj => { - resolve(obj); -}); - } else if (data.metadata && data.metadata.name) { - db.get('user') - .find( - {name: {$regex: data.metadata.name}}, - {fields: ['nickname', 'name'], limit: 10}) - .then(obj => { - resolve(obj); -}); - } else { - reject(); - } - }); - }; - var queryProjectName = function queryProjectName(data) { - return new Promise((resolve, reject) => { - if (data.metadata && data.metadata.name) { - db.get('project') + } + + // send user disconnect message to remaining users + sendDisconnectMessage(sourceUS.uid); + + // remove the user from the list + removeUser(this); + + // display the total number of connected users + console.log(" "+US.filter(function(o){return o!=undefined}).length+" remain connected"); + console.log(); + + }); + }); + server.listen(port, function _fromInitSocketConnection() { console.log('Listening on ' + server.address().port,server.address()) }); + } catch (ex) { + console.log("ERROR: Unable to create a server",ex); + } +} +this.initSocketConnection = initSocketConnection; + +var queryUserName = function queryUserName(data){ + return new Promise(function(resolve, reject){ + if (data.metadata && data.metadata.nickname) { + db.get('user') + .find( + {"nickname": {'$regex': data.metadata.nickname}}, + {fields:["nickname", "name"],limit:10}) + .then(function(obj){ + resolve(obj); + }); + } + else if (data.metadata && data.metadata.name) { + db.get('user') + .find( + {"name": {'$regex': data.metadata.name}}, + {fields:["nickname", "name"],limit:10}) + .then(function(obj){ + resolve(obj); + }); + } + else + reject(); + }); +} +var queryProjectName = function queryProjectName(data){ + return new Promise(function(resolve, reject){ + if (data.metadata && data.metadata.name) { + db.get('project') .findOne({ shortname: data.metadata.name, - backup: {$exists: 0} - }, { - fields: ['name', 'shortname'] + backup: {$exists:0} + },{ + fields:["name","shortname"] }) - .then(obj => { - resolve(obj); -}); - } else { - reject(); - } - }); - }; - var querySimilarProjectNames = function querySimilarProjectNames(data) { - return new Promise((resolve, reject) => { - if (data.metadata && data.metadata.projectName) { - db.get('project') + .then(function(obj) { + resolve(obj); + }); + } else + reject(); + }); +} +var querySimilarProjectNames = function querySimilarProjectNames(data){ + return new Promise(function(resolve, reject){ + if (data.metadata && data.metadata.projectName) { + db.get('project') .find({ - shortname: {$regex: data.metadata.projectName}, - backup: {$exists: 0} - }, { - fields: ['name', 'shortname'], - limit: 10 + shortname: {$regex:data.metadata.projectName}, + backup: {$exists:0} + },{ + fields:["name","shortname"], + limit:10 }) - .then(obj => { - resolve(obj); -}); - } else { - reject(); - } - }); - }; - var receivePaintMessage = function receivePaintMessage(data) { - traceLog(receivePaintMessage, 2); - - const msg = data.data; - const sourceUS = getUserFromUserId(data.uid); // User data - const c = msg.c; // Command - const x = msg.x; // X coordinate - const y = msg.y; // Y coordinate - const undoLayer = getCurrentUndoLayer(sourceUS.User); // Current undoLayer for user - - paintxy(sourceUS.uid, c, x, y, sourceUS.User, undoLayer); - }; - var receiveRequestSliceMessage = function receiveRequestSliceMessage(data, user_socket) { - traceLog(receiveRequestSliceMessage, 1); - - // Get slice information from message - const view = data.view; // User view - const slice = parseInt(data.slice); // User slice - - // get User object - const sourceUS = getUserFromUserId(data.uid); - - // Get brainPath from User object - const brainPath = sourceUS.User.dirname + sourceUS.User.mri; - - // Update User object - sourceUS.User.view = view; - sourceUS.User.slice = slice; - if (debug > 1) { - console.log('view, slice:', sourceUS.User.view, sourceUS.User.slice); - } + .then(function(obj) { + resolve(obj); + }); + } else + reject(); + }); +} +var receivePaintMessage = function receivePaintMessage(data) { + traceLog(receivePaintMessage,2); + + var msg=data.data; + var sourceUS=getUserFromUserId(data.uid); // user data + var c=msg.c; // command + var x=msg.x; // x coordinate + var y=msg.y; // y coordinate + var undoLayer=getCurrentUndoLayer(sourceUS.User); // current undoLayer for user + + paintxy(sourceUS.uid,c,x,y,sourceUS.User,undoLayer); +}; +var receiveRequestSliceMessage = function receiveRequestSliceMessage(data,user_socket) { + traceLog(receiveRequestSliceMessage,1); + + // get slice information from message + var view=data.view; // user view + var slice=parseInt(data.slice); // user slice + + // get User object + var sourceUS=getUserFromUserId(data.uid); + + // get brainPath from User object + var brainPath=sourceUS.User.dirname+sourceUS.User.mri; + + // update User object + sourceUS.User.view=view; + sourceUS.User.slice=slice; + if(debug>1) console.log("view, slice:",sourceUS.User.view,sourceUS.User.slice); + + // getBrainAtPath() uses a client-side path, starting with "/data/[md5hash]" + getBrainAtPath(brainPath) + .then(function promise_fromReceiveRequestSliceMessage(data) { + sendSliceToUser(data,view,slice,user_socket); + }); +}; - // GetBrainAtPath() uses a client-side path, starting with "/data/[md5hash]" - getBrainAtPath(brainPath) - .then(data => { - sendSliceToUser(data, view, slice, user_socket); - }); - }; +var receiveRequestSlice2Message = function receiveRequestSlice2Message(data,user_socket) { + traceLog(receiveRequestSlice2Message,1); - var receiveRequestSlice2Message = function receiveRequestSlice2Message(data, user_socket) { - traceLog(receiveRequestSlice2Message, 1); + var view=data.view; // user view + var slice=parseInt(data.slice); // user slice + var sourceUS=getUserFromUserId(data.uid); + var brainPath=sourceUS.User.dirname+sourceUS.User.mri; + var atlasPath=sourceUS.User.dirname+sourceUS.User.atlasFilename; + var i, atlas; - const view = data.view; // User view - const slice = parseInt(data.slice); // User slice - const sourceUS = getUserFromUserId(data.uid); - const brainPath = sourceUS.User.dirname + sourceUS.User.mri; - const atlasPath = sourceUS.User.dirname + sourceUS.User.atlasFilename; - let i, atlas; + sourceUS.User.view=view; + sourceUS.User.slice=slice; + if(debug>1) console.log("view,slice:",sourceUS.User.view,sourceUS.User.slice); - sourceUS.User.view = view; - sourceUS.User.slice = slice; - if (debug > 1) { - console.log('view,slice:', sourceUS.User.view, sourceUS.User.slice); - } - getBrainAtPath(brainPath) - .then(brain => { - for (i in Atlases) { - if (Atlases[i].dirname + Atlases[i].name === atlasPath) { - atlas = Atlases[i]; - break; + getBrainAtPath(brainPath) + .then(function promise_fromReceiveRequestSliceMessage(brain) { + for(i in Atlases) { + if(Atlases[i].dirname+Atlases[i].name === atlasPath) { + atlas = Atlases[i]; + break; + } } - } + + try { + var jpegImageData=drawSlice2(brain,atlas,view,slice); // TEST: to draw the server version of the atlas together with the anatomy + var length=jpegImageData.data.length+jpgTag.length; + var bin=Buffer.concat([jpegImageData.data,jpgTag],length); + user_socket.send(bin, {binary: true,mask:false}); + } catch(e) { + console.log("ERROR: Cannot send slice to user",e); + } + }); - try { - const jpegImageData = drawSlice2(brain, atlas, view, slice); // TEST: to draw the server version of the atlas together with the anatomy - const length = jpegImageData.data.length + jpgTag.length; - const bin = Buffer.concat([jpegImageData.data, jpgTag], length); - user_socket.send(bin, {binary: true, mask: false}); - } catch (e) { - console.log('ERROR: Cannot send slice to user', e); - } - }); - }; +} - var receiveSaveMessage = function receiveSaveMessage(data, user_socket) { - traceLog(receiveSaveMessage); +var receiveSaveMessage = function receiveSaveMessage(data,user_socket) { + traceLog(receiveSaveMessage); - const sourceUS = getUserFromUserId(data.uid); - const brainPath = sourceUS.User.dirname + sourceUS.User.mri; - const atlasPath = sourceUS.User.dirname + sourceUS.User.atlasFilename; - let i, atlas; + var sourceUS=getUserFromUserId(data.uid); + var brainPath=sourceUS.User.dirname+sourceUS.User.mri; + var atlasPath=sourceUS.User.dirname+sourceUS.User.atlasFilename; + var i, atlas; - const time = new Date(); - const modified = time.toJSON(); - const modifiedBy = (sourceUS.User && sourceUS.User.username) ? sourceUS.User.username : 'anonymous'; + var time=new Date(); + var modified=time.toJSON(); + var modifiedBy = (sourceUS.User && sourceUS.User.username) ? sourceUS.User.username : "anonymous"; - for (i in Atlases) { - if (Atlases[i].dirname + Atlases[i].name === atlasPath) { - saveAtlas(Atlases[i]) - .then(() => { - console.log(' Atlas saved'); + for(i in Atlases) { + if(Atlases[i].dirname+Atlases[i].name === atlasPath) { + saveAtlas(Atlases[i]) + .then(function () { + console.log(" Atlas saved"); clearInterval(Atlases[i].timer); broadcastMessage({ - type: 'serverMessage', - msg: 'Atlas saved ' + time + type:'serverMessage', + msg:"Atlas saved "+time }); }); - break; - } + break; } + } /** * @todo Log the save */ - }; - var receiveSaveMetadataMessage = function receiveSaveMetadataMessage(data, user_socket) { - traceLog(receiveSaveMetadataMessage); - - if (debug > 1) { - console.log('metadata type: ' + data.type); - console.log('rnd: ' + data.rnd); - console.log('method: ' + data.method); - console.log('patch: ' + JSON.stringify(data.patch)); - } +} +var receiveSaveMetadataMessage = function receiveSaveMetadataMessage(data,user_socket) { + traceLog(receiveSaveMetadataMessage); + + if(debug>1) { + console.log("metadata type: "+data.type); + console.log("rnd: "+data.rnd); + console.log("method: "+data.method); + console.log("patch: "+JSON.stringify(data.patch)); + } - /** - * @todo Currently metadata is a complete object, but it is also possible to - * send a patch computed using jsonpatch. In the future, only the patch - * method will be used - */ + /** + * @todo Currently metadata is a complete object, but it is also possible to + * send a patch computed using jsonpatch. In the future, only the patch + * method will be used + */ - const sourceUS = getUserFromUserId(data.uid); - let json = data.metadata; - json.modified = (new Date()).toJSON(); - json.modifiedBy = (sourceUS.User && sourceUS.User.username) ? sourceUS.User.username : 'anonymous'; + var sourceUS=getUserFromUserId(data.uid); + var json=data.metadata; + json.modified=(new Date()).toJSON(); + json.modifiedBy = (sourceUS.User && sourceUS.User.username) ? sourceUS.User.username : "anonymous"; - if (data.method == 'patch') { - // Deal with patches + if(data.method == "patch") { + // deal with patches // get original object from db - db.get('mri').findOne({source: json.source, backup: {$exists: 0}}, {_id: 0}) - .then(ret => { - delete ret._id; - + db.get('mri').findOne({source:json.source, backup:{$exists: 0}},{_id:0}) + .then(function (ret) { + + delete ret['_id']; + // DEBUG: console.log("original mri:", JSON.stringify(ret)); - + // apply patch - jsonpatch.apply(ret, data.patch); - + jsonpatch.apply( ret, data.patch ); + // DEBUG: console.log("patched mri:", JSON.stringify(ret)); // sanitise - ret = JSON.parse(DOMPurify.sanitize(JSON.stringify(ret))); // Sanitize works on strings, not objects + ret=JSON.parse(DOMPurify.sanitize(JSON.stringify(ret))); // sanitize works on strings, not objects - db.get('mri').update({source: json.source}, {$set: {backup: true}}, {multi: true}) - .then(() => { + db.get('mri').update({source:json.source},{$set:{backup:true}},{multi:true}) + .then(function () { db.get('mri').insert(ret); // DEBUG: console.log("inserted mri:",JSON.stringify(ret)); }); }); - } else { - // Deal with the complete object + } else { + // deal with the complete object // sanitise json - json = JSON.parse(DOMPurify.sanitize(JSON.stringify(json))); // Sanitize works on strings, not objects + json=JSON.parse(DOMPurify.sanitize(JSON.stringify(json))); // sanitize works on strings, not objects // DEBUG: - if (debug > 1) { - console.log('metadata:', JSON.stringify(json)); - } + if(debug>1) console.log("metadata:", JSON.stringify(json)); - // Mark previous one as backup - db.get('mri').findOne({source: json.source, backup: {$exists: 0}}) - .then(ret => { + // mark previous one as backup + db.get('mri').findOne({source:json.source, backup:{$exists: 0}}) + .then(function (ret) { // DEBUG: console.log("original mri:", JSON.stringify(ret)); - - db.get('mri').update({source: json.source}, {$set: {backup: true}}, {multi: true}) - .then(() => { - if (data.method === 'overwrite') { + + db.get('mri').update({source:json.source},{$set:{backup:true}},{multi:true}) + .then(function () { + if(data.method === "overwrite") { db.get('mri').insert(json); } else { json = merge.recursive(ret, json); - delete json._id; + delete json["_id"]; db.get('mri').insert(json); } // DEBUG: console.log("inserted mri:",JSON.stringify(json)); }); }); - } - }; - var receiveAtlasFromUserMessage = function receiveAtlasFromUserMessage(data, user_socket) { - traceLog(receiveAtlasFromUserMessage); - - zlib.inflate(data.data, (err, atlasData) => { - // Save current atlas - const sourceUS = getUserFromUserId(data.uid); - const iAtlas = sourceUS.User.iAtlas; - const atlas = Atlases[iAtlas]; - saveAtlas(atlas) - .then(() => { - console.log(' Replace current atlas with new atlas'); - atlas.data = atlasData; + } +}; +var receiveAtlasFromUserMessage = function receiveAtlasFromUserMessage(data,user_socket) { + traceLog(receiveAtlasFromUserMessage); + + zlib.inflate(data.data,function (err, atlasData){ + // Save current atlas + var sourceUS=getUserFromUserId(data.uid); + var iAtlas=sourceUS.User.iAtlas; + var atlas=Atlases[iAtlas]; + saveAtlas(atlas) + .then(function () { + console.log(" Replace current atlas with new atlas"); + atlas.data=atlasData; }); - }); - }; + }); +}; - const unloadUnusedBrains = function unloadUnusedBrains() { - traceLog(unloadUnusedBrains); - let i; - for (i in Brains) { - const sum = numberOfUsersConnectedToMRI(Brains[i].path); +var unloadUnusedBrains = function unloadUnusedBrains() { + traceLog(unloadUnusedBrains); + var i; + for(i in Brains) { + var sum=numberOfUsersConnectedToMRI(Brains[i].path); + + if(sum===0) { + console.log(" No user connected to MRI "+Brains[i].path+": unloading it"); + unloadMRI(Brains[i].path); + } + } +}; +var unloadUnusedAtlases = function unloadUnusedAtlases() { + traceLog(unloadUnusedAtlases); + var i; + for(i in Atlases) { + var sum=numberOfUsersConnectedToAtlas(Atlases[i].dirname,Atlases[i].name); + if(sum===0) { + console.log(" No user connected to Atlas "+Atlases[i].dirname+Atlases[i].name+": unloading it"); + unloadAtlas(Atlases[i].dirname,Atlases[i].name); + } + } +}; +var sendSliceToUser = function sendSliceToUser(brain, view, slice, user_socket) { + traceLog(sendSliceToUser,1); + + try { + var jpegImageData=drawSlice(brain,view,slice); + var length=jpegImageData.data.length+jpgTag.length; + var bin=Buffer.concat([jpegImageData.data,jpgTag],length); + user_socket.send(bin, {binary: true,mask:false}); + } catch(e) { + console.log("ERROR: Cannot send slice to user"); + } +} - if (sum === 0) { - console.log(' No user connected to MRI ' + Brains[i].path + ': unloading it'); - unloadMRI(Brains[i].path); +var receiveUserDataMessage = function receiveUserDataMessage(data, user_socket) { + traceLog(receiveUserDataMessage,1); + if(debug>1) console.log(" data.description:", data.description); + + var sourceUS=getUserFromUserId(data.uid); + + var User, + i, + atlasLoadedFlag, + firstConnectionFlag=false, + switchingAtlasFlag=false; + + if(sourceUS.User===undefined) { + firstConnectionFlag=true; + } else if(sourceUS.User.isMRILoaded===false) { + firstConnectionFlag=true; + } + + if(data.description === "allUserData" ) { + // receiving the complete User data object + User=data.user; + User.uid=data.uid; + } else { + User=sourceUS.User; + if(data.description==="sendAtlas") { + // receive an atlas from the user + // 1. Check if the atlas the user is requesting has not been loaded + atlasLoadedFlag=false; + + // check whether user is switching atlas. + switchingAtlasFlag=false; + if(sourceUS.User) { + if((sourceUS.User.atlasFilename!==User.atlasFilename)||(sourceUS.User.dirname!==User.dirname)) { + switchingAtlasFlag=true; + } } - } - }; - const unloadUnusedAtlases = function unloadUnusedAtlases() { - traceLog(unloadUnusedAtlases); - let i; - for (i in Atlases) { - const sum = numberOfUsersConnectedToAtlas(Atlases[i].dirname, Atlases[i].name); - if (sum === 0) { - console.log(' No user connected to Atlas ' + Atlases[i].dirname + Atlases[i].name + ': unloading it'); - unloadAtlas(Atlases[i].dirname, Atlases[i].name); - } - } - }; - var sendSliceToUser = function sendSliceToUser(brain, view, slice, user_socket) { - traceLog(sendSliceToUser, 1); - - try { - const jpegImageData = drawSlice(brain, view, slice); - const length = jpegImageData.data.length + jpgTag.length; - const bin = Buffer.concat([jpegImageData.data, jpgTag], length); - user_socket.send(bin, {binary: true, mask: false}); - } catch (e) { - console.log('ERROR: Cannot send slice to user'); - } - }; - - var receiveUserDataMessage = function receiveUserDataMessage(data, user_socket) { - traceLog(receiveUserDataMessage, 1); - if (debug > 1) { - console.log(' data.description:', data.description); - } - - const sourceUS = getUserFromUserId(data.uid); - - let User, - i, - atlasLoadedFlag, - firstConnectionFlag = false, - switchingAtlasFlag = false; - - if (sourceUS.User === undefined) { - firstConnectionFlag = true; - } else if (sourceUS.User.isMRILoaded === false) { - firstConnectionFlag = true; - } - - if (data.description === 'allUserData') { - // Receiving the complete User data object - User = data.user; - User.uid = data.uid; - } else { - User = sourceUS.User; - if (data.description === 'sendAtlas') { - // Receive an atlas from the user - // 1. Check if the atlas the user is requesting has not been loaded - atlasLoadedFlag = false; - - // Check whether user is switching atlas. - switchingAtlasFlag = false; - if (sourceUS.User) { - if ((sourceUS.User.atlasFilename !== User.atlasFilename) || (sourceUS.User.dirname !== User.dirname)) { - switchingAtlasFlag = true; - } - } - - for (i in Atlases) { - if (Atlases[i].dirname === User.dirname && Atlases[i].name === User.atlasFilename) { - atlasLoadedFlag = true; - break; - } + + for(i in Atlases) { + if(Atlases[i].dirname===User.dirname && Atlases[i].name===User.atlasFilename) { + atlasLoadedFlag=true; + break; } - User.iAtlas = atlasLoadedFlag ? parseInt(i) : Atlases.length; // Value i if it was found, or last available if it wasn't - + } + User.iAtlas=atlasLoadedFlag?parseInt(i):Atlases.length; // value i if it was found, or last available if it wasn't + // 2. Send the atlas to the user (load it if required) - if (atlasLoadedFlag) { - if (firstConnectionFlag || switchingAtlasFlag) { - // Send the new user our data - sendAtlasToUser(Atlases[i].data, user_socket, true); - sourceUS.User.isMRILoaded = true; - } - } else { + if(atlasLoadedFlag) { + if(firstConnectionFlag || switchingAtlasFlag) { + // send the new user our data + sendAtlasToUser(Atlases[i].data,user_socket,true); + sourceUS.User.isMRILoaded=true; + } + } else { // The atlas requested has not been loaded before: // Load the atlas s/he's requesting - addAtlas(User) - .then(atlas => { - sendAtlasToUser(atlas.data, user_socket, true); - sourceUS.User.isMRILoaded = true; + addAtlas(User) + .then(function(atlas) { + sendAtlasToUser(atlas.data,user_socket,true); + sourceUS.User.isMRILoaded=true; }); - } - } else { - // Receive a specific field of the User data object from the user - const changes = JSON.parse(data.description); - for (i in changes) { - User[i] = changes[i]; - } - } - } - - // 3. Update user data - // If the user didn't have a name (wasn't logged in), but now has one, - // display the name in the log - if (User.hasOwnProperty('username')) { - if (sourceUS.User === undefined) { - console.log(' No User yet for id ' + data.uid); - } else if (!sourceUS.User.hasOwnProperty('username')) { - console.log(' User ' + User.username + ', id ' + data.uid + ' logged in'); } + } else { + // receive a specific field of the User data object from the user + var changes = JSON.parse(data.description); + for(i in changes) + User[i]=changes[i]; } - if (sourceUS.hasOwnProperty('User') === false) { - sourceUS.User = {}; - } - for (const prop in User) { - sourceUS.User[prop] = User[prop]; - } + } + + // 3. Update user data + // If the user didn't have a name (wasn't logged in), but now has one, + // display the name in the log + if(User.hasOwnProperty('username')) { + if(sourceUS.User===undefined) { + console.log(" No User yet for id "+data.uid); + } else if(!sourceUS.User.hasOwnProperty('username')) { + console.log(" User "+User.username+", id "+data.uid+" logged in"); + } + } + if(sourceUS.hasOwnProperty('User')===false) { + sourceUS.User={}; + } + for(var prop in User) { + sourceUS.User[prop]=User[prop]; + } /* - // 4. Update number of users connected to atlas - if(firstConnectionFlag) { - var sumAtlas=0, - sumMRI=0; - for(i in US) { - if(US[i].User.dirname===User.dirname && US[i].User.atlasFilename===User.atlasFilename) { - sumAtlas++; - } - if(US[i].User.dirname===User.dirname && US[i].User.mri===User.mri) { - sumMRI++; - } - } - console.log(sumMRI+" user"+((sumMRI===1)?" is":"s are")+" requesting MRI "+User.dirname+User.mri); - console.log(sumAtlas+" user"+((sumAtlas===1)?" is":"s are")+" requesting atlas "+User.dirname+User.atlasFilename); - } + // 4. Update number of users connected to atlas + if(firstConnectionFlag) { + var sumAtlas=0, + sumMRI=0; + for(i in US) { + if(US[i].User.dirname===User.dirname && US[i].User.atlasFilename===User.atlasFilename) { + sumAtlas++; + } + if(US[i].User.dirname===User.dirname && US[i].User.mri===User.mri) { + sumMRI++; + } + } + console.log(sumMRI+" user"+((sumMRI===1)?" is":"s are")+" requesting MRI "+User.dirname+User.mri); + console.log(sumAtlas+" user"+((sumAtlas===1)?" is":"s are")+" requesting atlas "+User.dirname+User.atlasFilename); + } */ - // 5. Unload unused data (the check is only done if new data has been added) - if (data.description === 'sendAtlas') { - unloadUnusedBrains(); - unloadUnusedAtlases(); - } - }; - - var declareAutocompleteClient = function declareAutocompleteClient(data, user_socket) { - traceLog(declareAutocompleteClient, 0); - - const sourceUS = getUserFromUserId(data.uid); - - let User, - i, - atlasLoadedFlag, - firstConnectionFlag = false, - switchingAtlasFlag = false; + // 5. Unload unused data (the check is only done if new data has been added) + if(data.description==="sendAtlas") { + unloadUnusedBrains(); + unloadUnusedAtlases(); + } +} - sourceUS.User = { - autocompleteClient: true, - uid: data.uid - }; - }; +var declareAutocompleteClient = function declareAutocompleteClient(data, user_socket) { + traceLog(declareAutocompleteClient,0); + + var sourceUS=getUserFromUserId(data.uid); + + var User, + i, + atlasLoadedFlag, + firstConnectionFlag=false, + switchingAtlasFlag=false; + + sourceUS.User = { + autocompleteClient: true, + uid: data.uid + }; +} /* - Send new user information to old users, + send new user information to old users, and old users information to new user. */ - var sendPreviousUserDataMessage = function sendPreviousUserDataMessage(newUS) { - traceLog(sendPreviousUserDataMessage); - - let i, - n = 0; - for (i in US) { - if (US[i].socket == newUS.socket) { - continue; - } - const msg = JSON.stringify({type: 'userData', user: US[i].User, uid: US[i].uid, description: 'allUserData'}); - newUS.socket.send(msg); - n++; - } - if (debug) { - console.log(' send user data from ' + n + ' users'); - } - }; - var sendAtlasToUser = function sendAtlasToUser(atlasdata, user_socket, flagCompress) { - traceLog(sendAtlasToUser); - - if (flagCompress) { - console.log('atlasdata', atlasdata.length); - zlib.gzip(atlasdata, (err, atlasdatagz) => { - console.log('atlasdatagz', atlasdatagz.length); - try { - user_socket.send(Buffer.concat([atlasdatagz, niiTag]), {binary: true, mask: false}); - } catch (e) { - console.log(''); - } - }); - } else { - try { - user_socket.send(Buffer.concat([atlasdata, niiTag]), {binary: true, mask: false}); - } catch (e) { - console.log(''); - } - } - }; - const broadcastPaintVolumeMessage = function broadcastPaintVolumeMessage(msg, User) { - traceLog(broadcastPaintVolumeMessage); - - try { - var n = 0, - i, - msg = JSON.stringify({type: 'paintvol', data: msg}); - for (i in US) { - if (US[i].User != undefined && - US[i].User.iAtlas != User.iAtlas) { - continue; - } - US[i].socket.send(msg); +var sendPreviousUserDataMessage = function sendPreviousUserDataMessage(newUS) { + traceLog(sendPreviousUserDataMessage); + + var i,n=0; + for(i in US) { + if(US[i].socket==newUS.socket) + continue; + var msg=JSON.stringify({type:"userData",user:US[i].User,uid:US[i].uid,description:"allUserData"}); + newUS.socket.send(msg); + n++; + } + if(debug) console.log(" send user data from "+n+" users"); +}; +var sendAtlasToUser = function sendAtlasToUser(atlasdata, user_socket, flagCompress) { + traceLog(sendAtlasToUser); + + if(flagCompress) { + console.log("atlasdata",atlasdata.length); + zlib.gzip(atlasdata, function (err,atlasdatagz) { + console.log("atlasdatagz",atlasdatagz.length); + try { + user_socket.send(Buffer.concat([atlasdatagz,niiTag]), {binary: true, mask: false}); + } catch(e) { + console.log(""); + } + }); + } else { + try { + user_socket.send(Buffer.concat([atlasdata,niiTag]), {binary: true, mask: false}); + } catch(e) { + console.log(""); + } + } +}; +var broadcastPaintVolumeMessage = function broadcastPaintVolumeMessage(msg, User) { + traceLog(broadcastPaintVolumeMessage); + + try { + var n=0,i,msg=JSON.stringify({"type":"paintvol","data":msg}); + for(i in US) { + if( US[i].User!=undefined && + US[i].User.iAtlas!=User.iAtlas ) + continue; + US[i].socket.send(msg); + n++; + } + if(debug) console.log(" paintVolume message broadcasted to "+n+" users"); + + } catch (ex) { + console.log("ERROR: Unable to broadcastPaintVolumeMessage",ex); + } +}; +var sendDisconnectMessage = function sendDisconnectMessage(uid) { + traceLog(sendDisconnectMessage); + + broadcastMessage({ + type:"disconnect", + uid:uid + },uid); +} +var broadcastMessage = function broadcastMessage(msg, uid) { + traceLog(broadcastMessage); + + try { + var n=0,i; + for(i in US) { + if(US[i].uid != uid ) { + US[i].socket.send(JSON.stringify(msg)); n++; } - if (debug) { - console.log(' paintVolume message broadcasted to ' + n + ' users'); - } - } catch (ex) { - console.log('ERROR: Unable to broadcastPaintVolumeMessage', ex); - } - }; - var sendDisconnectMessage = function sendDisconnectMessage(uid) { - traceLog(sendDisconnectMessage); - - broadcastMessage({ - type: 'disconnect', - uid - }, uid); - }; - var broadcastMessage = function broadcastMessage(msg, uid) { - traceLog(broadcastMessage); - - try { - let n = 0, - i; - for (i in US) { - if (US[i].uid != uid) { - US[i].socket.send(JSON.stringify(msg)); - n++; - } - } - if (debug) { - console.log(' message broadcasted to ' + n + ' users', msg); - } - } catch (ex) { - console.log('ERROR: Unable to broadcast message', ex, msg); - } - }; + } + if(debug) console.log(" message broadcasted to "+n+" users", msg); + } catch (ex) { + console.log("ERROR: Unable to broadcast message",ex,msg); + } + +} -// ======================================================================================== +//======================================================================================== // Load & Save -// ======================================================================================== +//======================================================================================== /** * @func addAtlas * input: A User structure providing information about the requested atlas * process: an atlas is obtained, and added to the Atlases[] array if it * wasn't already loaded. - * output: an atlas (mri structure) + * output: an atlas (mri structure) */ - var addAtlas = function addAtlas(User) { - traceLog(addAtlas); - - // Console.log("User:",User); - - const atlas = { - name: User.atlasFilename, - specimen: User.specimenName, - dirname: User.dirname, - dim: User.dim - }; - console.log(' User requests atlas ' + atlas.name + ' from ' + atlas.dirname, atlas.specimen); +var addAtlas = function addAtlas(User) { + traceLog(addAtlas); + + //console.log("User:",User); + + var atlas = { + name:User.atlasFilename, + specimen:User.specimenName, + dirname:User.dirname, + dim:User.dim + }; + console.log(" User requests atlas "+atlas.name+" from "+atlas.dirname, atlas.specimen); - const pr = new Promise((resolve, reject) => { - loadAtlas(User) - .then(atlas => { + var pr = new Promise(function promise_fromAddAtlas(resolve,reject) { + loadAtlas(User) + .then(function (atlas) { Atlases.push(atlas); - User.iAtlas = Atlases.indexOf(atlas); - atlas.timer = setInterval(() => { - saveAtlas(atlas); - }, 60 * 60 * 1000); // 60 minutes - + User.iAtlas=Atlases.indexOf(atlas); + atlas.timer=setInterval(function () {saveAtlas(atlas)},60*60*1000); // 60 minutes + resolve(atlas); }); - }); - - return pr; - }; - var getBrainAtPath = function getBrainAtPath(brainPath) { - traceLog(getBrainAtPath, 1); - + }); + + return pr; +}; +var getBrainAtPath = function getBrainAtPath(brainPath) { + traceLog(getBrainAtPath,1); + /* - GetBrainAtPath + getBrainAtPath input: A client-side path identifying the requested brain process: a brain is obtained, and added to the Brains[] array if it wasn't already loaded. - output: a brain (mri structure) + output: a brain (mri structure) */ - let i; - for (i in Brains) { - if (Brains[i].path === brainPath) { - if (debug > 1) { - console.log(' brain already loaded'); - } - return Promise.resolve(Brains[i].data); - } - } - - if (debug) { - console.log(' Loading brain at', brainPath); - } - const pr = new Promise(function promise_fromGetBrainAtPath(resolve, reject) { - loadMRI(this.dataDirectory + brainPath) - .then(mri => { - const brain = {path: brainPath, data: mri}; + var i; + for(i in Brains) { + if(Brains[i].path===brainPath) { + if(debug>1) console.log(" brain already loaded"); + return Promise.resolve(Brains[i].data); + } + } + + if(debug) { + console.log(" Loading brain at",brainPath); + } + var pr = new Promise(function promise_fromGetBrainAtPath(resolve, reject) { + loadMRI(this.dataDirectory+brainPath) + .then(function _fromGetBrainAtPath(mri) { + var brain={path:brainPath,data:mri}; Brains.push(brain); - resolve(mri); // Callback: sendSliceToUser + resolve(mri); // callback: sendSliceToUser }) - .catch(err => { - console.log('ERROR: getBrainAtPath cannot load brain. Corrupted file?', err); + .catch(function (err) { + console.log("ERROR: getBrainAtPath cannot load brain. Corrupted file?",err); reject(err); }); - }); - - return pr; - }; - this.getBrainAtPath = getBrainAtPath; + }); + + return pr; +} +this.getBrainAtPath = getBrainAtPath; /** * @func loadAtlas @@ -1320,509 +1260,491 @@ const atlasMakerServer = function () { * if it was already downloaded but not yet loaded, or created if it's a * new atlas. * @param {Object} User A User object providing information about the requested atlas - * @return an atlas (mri structure) + * @return an atlas (mri structure) */ - var loadAtlas = function loadAtlas(User) { - traceLog(loadAtlas); - - // Console.log("User from loadAtlas:",User); - - const pr = new Promise(function promise_fromloadAtlas(resolve, reject) { - const path = this.dataDirectory + User.dirname + User.atlasFilename; - - if (User.dirname == undefined) { - console.log('ERROR: Rejecting loadAtlas from undefined User.dirname:', User); - reject(); - return; - } - if (User.atlasFilename == undefined) { - console.log('ERROR: Rejecting loadAtlas from undefined User.atlasFilename:', User); - reject(); - return; - } - - if (!fs.existsSync(path)) { +var loadAtlas = function loadAtlas(User) { + traceLog(loadAtlas); + + + // console.log("User from loadAtlas:",User); + + var pr = new Promise(function promise_fromloadAtlas(resolve,reject) { + var path=this.dataDirectory+User.dirname+User.atlasFilename; + + if(User.dirname == undefined) { + console.log("ERROR: Rejecting loadAtlas from undefined User.dirname:",User); + reject(); + return; + } + if(User.atlasFilename == undefined) { + console.log("ERROR: Rejecting loadAtlas from undefined User.atlasFilename:",User); + reject(); + return; + } + + if(!fs.existsSync(path)) { // Create new empty atlas - console.log(' Atlas ' + path + ' does not exists. Create a new one'); - const brainPath = User.dirname + User.mri; - getBrainAtPath(brainPath) - .then(mri => { + console.log(" Atlas "+path+" does not exists. Create a new one"); + var brainPath=User.dirname+User.mri; + getBrainAtPath(brainPath) + .then(function _fromLoadAtlas(mri) { createNifti(mri) - .then(newAtlas => { + .then(function (newAtlas) { newAtlas.name = User.atlasFilename; newAtlas.dirname = User.dirname; - - // Log atlas creation + + // log atlas creation db.get('log').insert({ - key: 'createAtlas', - value: DOMPurify.sanitize(JSON.stringify({atlasDirectory: User.dirname, atlasFilename: User.atlasFilename})), + key: "createAtlas", + value: DOMPurify.sanitize(JSON.stringify({atlasDirectory:User.dirname,atlasFilename:User.atlasFilename})), username: User.username, date: (new Date()).toJSON() }); resolve(newAtlas); - }) - .catch(err => { - console.log('ERROR Cannot create nifti', err); + }) + .catch(function (err) { + console.log("ERROR Cannot create nifti",err); reject(err); + return; }); }) - .catch(err => { - console.log('ERROR Cannot get template brain for new atlas', err); + .catch(function (err) { + console.log("ERROR Cannot get template brain for new atlas", err); reject(err); + return; }); - } else { + } else { // Load existing atlas - console.log(' Atlas found. Loading it'); - loadMRI(path) - .then(loadedAtlas => { + console.log(" Atlas found. Loading it"); + loadMRI(path) + .then(function (loadedAtlas) { loadedAtlas.name = User.atlasFilename; loadedAtlas.dirname = User.dirname; - - // Cast atlas data to 8bits - switch (filetypeFromFilename(User.atlasFilename)) { - case 'nii.gz': + + // cast atlas data to 8bits + switch(filetypeFromFilename(User.atlasFilename)) { + case "nii.gz": createNifti(loadedAtlas) - .then(atlas8bit => { - for (i = 0; i < loadedAtlas.dim[0] * loadedAtlas.dim[1] * loadedAtlas.dim[2]; i++) { - atlas8bit.data[i] = loadedAtlas.data[i]; - } - loadedAtlas.data = atlas8bit.data; - loadedAtlas.hdr = atlas8bit.hdr; + .then(function(atlas8bit) { + for(i=0;i { - console.log('ERROR Cannot read nifti', err); + .catch(function (err) { + console.log("ERROR Cannot read nifti", err); reject(err); + return; }); - } - }); - return pr; - }; - function filetypeFromFilename(path) { - if (path.match(/.nii.gz$/)) { - return 'nii.gz'; - } else - if (path.match(/.mgz$/)) { - return 'mgz'; - } + } + }); + return pr; +}; +function filetypeFromFilename(path) { + if(path.match(/.nii.gz$/)) { + return "nii.gz"; + } else + if(path.match(/.mgz$/)) { + return "mgz"; + } else { + return; } - var loadMRI = function loadMRI(path) { - traceLog(loadMRI); - console.log('path:', path); +} +var loadMRI = function loadMRI(path) { + traceLog(loadMRI); + console.log("path:",path); /* - LoadMRI + loadMRI input: path to an mri file, .nii.gz and .mgz formats are recognised output: an mri structure */ - const pr = new Promise((resolve, reject) => { - switch (filetypeFromFilename(path)) { - case 'nii.gz': - console.log('reading nii'); - readNifti(path) - .then(mri => { + var pr = new Promise(function promise_fromLoadMRI(resolve, reject) { + switch(filetypeFromFilename(path)) { + case "nii.gz": + console.log("reading nii"); + readNifti(path) + .then(function (mri) { resolve(mri); }) - .catch(err => { - console.log('ERROR reading nii.gz file:', err); + .catch(function (err) { + console.log("ERROR reading nii.gz file:",err); reject(); + return; }); - break; - case 'mgz': - console.log('reading mgz'); - readMGZ(path) - .then(mri => { + break; + case "mgz": + console.log("reading mgz"); + readMGZ(path) + .then(function(mri) { resolve(mri); }) - .catch(err => { - console.log('ERROR reading mgz file:', err); + .catch(function (err) { + console.log("ERROR reading mgz file:",err); reject(); + return; }); - break; - default: - console.log('ERROR: nothing we can read'); - reject(); - - } - }); - - return pr; - }; - this.loadMRI = loadMRI; + break; + default: + console.log("ERROR: nothing we can read"); + reject(); + return; + } + }); - var readNifti = function readNifti(path) { - traceLog(readNifti); + return pr; +} +this.loadMRI = loadMRI; +var readNifti = function readNifti(path) { + traceLog(readNifti); + /* - ReadNifti + readNifti input: path to a .nii.gz file output: an mri structure */ - const pr = new Promise((resolve, reject) => { - try { - const niigz = fs.readFileSync(path); - console.log('niigz length:', niigz.length); - - zlib.gunzip(niigz, (err, nii) => { - var i, j, tmp, sum, - mri = {}; - - // Standard nii header - try { - NiiHdr.allocate(); - console.log('nii length:', nii.length); - NiiHdr._setBuff(nii); - const h = JSON.parse(JSON.stringify(NiiHdr.fields)); - - const sizeof_hdr = h.sizeof_hdr; - mri.dim = [h.dim[1], h.dim[2], h.dim[3]]; - mri.pixdim = [h.pixdim[1], h.pixdim[2], h.pixdim[3]]; - mri.vox_offset = h.vox_offset; - - // Nrrd-compatible header, computes space directions and space origin - console.log('sform code:', h.sform_code); - if (h.sform_code > 0) { - mri.dir = [ + var pr = new Promise(function (resolve, reject) { + try { + var niigz=fs.readFileSync(path); + console.log("niigz length:",niigz.length); + + zlib.gunzip(niigz, function (err, nii) { + var i, j, tmp, sum, mri={}; + + // standard nii header + try { + NiiHdr.allocate(); + console.log("nii length:",nii.length); + NiiHdr._setBuff(nii); + var h=JSON.parse(JSON.stringify(NiiHdr.fields)); + + var sizeof_hdr=h.sizeof_hdr; + mri.dim=[h.dim[1],h.dim[2],h.dim[3]]; + mri.pixdim=[h.pixdim[1],h.pixdim[2],h.pixdim[3]]; + mri.vox_offset=h.vox_offset; + + // nrrd-compatible header, computes space directions and space origin + console.log("sform code:",h.sform_code); + if(h.sform_code>0) { + mri.dir = [ [h.srow_x[0], h.srow_y[0], h.srow_z[0]], [h.srow_x[1], h.srow_y[1], h.srow_z[1]], [h.srow_x[2], h.srow_y[2], h.srow_z[2]] - ]; - mri.ori = [h.srow_x[3], h.srow_y[3], h.srow_z[3]]; - } else { - mri.dir = [[mri.pixdim[0], 0, 0], [0, mri.pixdim[1], 0], [0, 0, mri.pixdim[2]]]; - mri.ori = [0, 0, 0]; - } - } catch (err) { - console.log('ERROR Cannot read nifti header:', err); - reject('ERROR Cannot read nifti header: ' + err); - return; + ]; + mri.ori = [h.srow_x[3], h.srow_y[3], h.srow_z[3]]; + } else { + mri.dir=[[mri.pixdim[0],0,0],[0,mri.pixdim[1],0],[0,0,mri.pixdim[2]]]; + mri.ori=[0,0,0]; } + } catch(err) { + console.log("ERROR Cannot read nifti header:", err); + reject("ERROR Cannot read nifti header: " + err); + return; + } + + // compute the transformation from voxel space to screen space + computeS2VTransformation(mri); - // Compute the transformation from voxel space to screen space - computeS2VTransformation(mri); - - // Test if the transformation looks incorrect. Reset it if it does - // testS2VTransformation(mri); + // test if the transformation looks incorrect. Reset it if it does + //testS2VTransformation(mri); // manually parsed information - mri.hdr = nii.slice(0, 352); - mri.hdrSz = 352; - mri.datatype = nii.readUInt16LE(70); - - console.log('reading datatype', mri.datatype); - console.log('dim:', mri.dim[0], mri.dim[1], mri.dim[2]); - switch (mri.datatype) { - case 2: // UCHAR - mri.data = nii.slice(mri.vox_offset); - break; - case 4: // SHORT - tmp = nii.slice(mri.vox_offset); - mri.data = new Int16Array(mri.dim[0] * mri.dim[1] * mri.dim[2]); - for (j = 0; j < mri.dim[0] * mri.dim[1] * mri.dim[2]; j++) { - mri.data[j] = tmp.readInt16LE(j * 2); - } - break; - case 8: // INT - tmp = nii.slice(mri.vox_offset); - mri.data = new Uint32Array(mri.dim[0] * mri.dim[1] * mri.dim[2]); - for (j = 0; j < mri.dim[0] * mri.dim[1] * mri.dim[2]; j++) { - mri.data[j] = tmp.readUInt32LE(j * 4); - } - break; - case 16: // FLOAT - tmp = nii.slice(mri.vox_offset); - mri.data = new Float32Array(mri.dim[0] * mri.dim[1] * mri.dim[2]); - for (j = 0; j < mri.dim[0] * mri.dim[1] * mri.dim[2]; j++) { - mri.data[j] = tmp.readFloatLE(j * 4); - } - break; - case 256: // INT8 - tmp = nii.slice(mri.vox_offset); - mri.data = new Int8Array(mri.dim[0] * mri.dim[1] * mri.dim[2]); - for (j = 0; j < mri.dim[0] * mri.dim[1] * mri.dim[2]; j++) { - mri.data[j] = tmp.readInt8(j); - } - break; - case 512: // UINT16 - tmp = nii.slice(mri.vox_offset); - mri.data = new Uint16Array(mri.dim[0] * mri.dim[1] * mri.dim[2]); - for (j = 0; j < mri.dim[0] * mri.dim[1] * mri.dim[2]; j++) { - mri.data[j] = tmp.readUInt16LE(j * 2); - } - break; - default: { - reject('ERROR: Unknown dataType: ' + mri.datatype); - return; - } - } - - // Compute sum, min and max - var i, - sum = 0, - min, max; - min = mri.data[0]; - max = min; - for (i = 0; i < mri.dim[0] * mri.dim[1] * mri.dim[2]; i++) { - sum += mri.data[i]; - - if (mri.data[i] < min) { - min = mri.data[i]; - } - if (mri.data[i] > max) { - max = mri.data[i]; - } + mri.hdr=nii.slice(0,352); + mri.hdrSz=352; + mri.datatype=nii.readUInt16LE(70); + + console.log("reading datatype",mri.datatype); + console.log("dim:",mri.dim[0],mri.dim[1],mri.dim[2]); + switch(mri.datatype) { + case 2: // UCHAR + mri.data=nii.slice(mri.vox_offset); + break; + case 4: // SHORT + tmp=nii.slice(mri.vox_offset); + mri.data=new Int16Array(mri.dim[0]*mri.dim[1]*mri.dim[2]); + for(j=0;jmax) max=mri.data[i]; + } + mri.sum=sum; + mri.min=min; + mri.max=max; - var readMGZ = function readMGZ(path) { - traceLog(readMGZ); + resolve(mri); + }); + } catch(e) { + reject("ERROR Cannot uncompress nifti file:" + e); + } + }); + return pr; +}; +this.readNifti = readNifti; +var readMGZ = function readMGZ(path) { + traceLog(readMGZ); + /* - ReadMGZ + readMGZ input: path to a .mgz file output: an mri structure */ - const pr = new Promise((resolve, reject) => { - try { - child_process.execFile('gunzip', ['-c', path], {encoding: 'binary', maxBuffer: 200 * 1024 * 1024}, (err, stdout) => { - const mgh = new Buffer(stdout, 'binary'); - var i, j, tmp, sum, - mri = {}; - let sz, bpv; - let hdr_sz = 284, - ftrSz; - MghHdr.allocate(); - MghHdr._setBuff(mgh); - const h = JSON.parse(JSON.stringify(MghHdr.fields)); - + var pr = new Promise(function (resolve, reject) { + try { + child_process.execFile("gunzip", ["-c",path], {encoding: 'binary', maxBuffer: 200*1024*1024}, function(err, stdout) { + var mgh = new Buffer(stdout, 'binary'); + var i, j, tmp, sum, mri = {}; + var sz, bpv; + var hdr_sz=284, ftrSz; + MghHdr.allocate(); + MghHdr._setBuff(mgh); + var h=JSON.parse(JSON.stringify(MghHdr.fields)); + // Test Header - if (h.v < 1 || h.v > 100) { - console.log('ERROR: Wrong MGH Header', h); - reject('ERROR: Wrong MGH Header', h); - return; - } - + if(h.v<1 || h.v>100) { + console.log("ERROR: Wrong MGH Header",h); + reject("ERROR: Wrong MGH Header",h); + return; + } + // Equations from freesurfer/matlab/load_mgh.m - const Pcrs_c = [h.ndim1 / 2, h.ndim2 / 2, h.ndim3 / 2]; - const D = [[h.delta[0], 0, 0], [0, h.delta[1], 0], [0, 0, h.delta[2]]]; - const MdcD = [ - [h.Mdc[0] * h.delta[0], h.Mdc[3] * h.delta[1], h.Mdc[6] * h.delta[2]], - [h.Mdc[1] * h.delta[0], h.Mdc[4] * h.delta[1], h.Mdc[7] * h.delta[2]], - [h.Mdc[2] * h.delta[0], h.Mdc[5] * h.delta[1], h.Mdc[8] * h.delta[2]] - ]; - const Pxyz_0 = subVecVec(h.Pxyz_c, mulMatVec(MdcD, Pcrs_c)); - const M = [ - h.Mdc[0] * h.delta[0], h.Mdc[3] * h.delta[1], h.Mdc[6] * h.delta[2], Pxyz_0[0], - h.Mdc[1] * h.delta[0], h.Mdc[4] * h.delta[1], h.Mdc[7] * h.delta[2], Pxyz_0[1], - h.Mdc[2] * h.delta[0], h.Mdc[5] * h.delta[1], h.Mdc[8] * h.delta[2], Pxyz_0[2], - 0, 0, 0, 1 - ]; - - mri.dim = [h.ndim1, h.ndim2, h.ndim3]; - mri.pixdim = [h.delta[0], h.delta[1], h.delta[2]]; - mri.dir = [[M[0], -M[1], -M[2]], [M[4], -M[5], -M[6]], [M[8], -M[9], -M[10]]]; - mri.ori = [M[3], M[7], M[11]]; - - // Compute the transformation from voxel space to screen space - computeS2VTransformation(mri); - - // Test if the transformation looks incorrect. Reset it if it does - // testS2VTransformation(mri); - - sz = mri.dim[0] * mri.dim[1] * mri.dim[2]; - bpv = [1, 4, 0, 4, 2][h.type]; // Bytes per voxel - console.log('sz:', sz); - console.log('bpv:', bpv, 'type:', h.type); - - // Keep the header - mri.hdr = mgh.slice(0, hdr_sz); - mri.hdrSz = hdr_sz; - - // Keep the footer - ftrSz = mgh.length - hdr_sz - sz * bpv; - mri.ftr = mgh.slice(hdr_sz + sz * bpv); - - // Print info - console.log(' mgh.length:', mgh.length); - console.log(' hdr_sz:', hdr_sz); - console.log(' sz*bpv:', sz * bpv); - console.log(' ftrSz:', ftrSz); - console.log('mri.ftr.length:', mri.ftr.length); - - switch (h.type) { - case 0: // MGHUCHAR - mri.data = mgh.slice(hdr_sz, -ftrSz); - break; - case 1: // MGHINT - tmp = mgh.slice(hdr_sz, -ftrSz); - mri.data = new Uint32Array(sz); - for (j = 0; j < sz; j++) { - mri.data[j] = tmp.readUInt32BE(j * 4); - } - break; - case 3: // MGHFLOAT - tmp = mgh.slice(hdr_sz, -ftrSz); - mri.data = new Float32Array(sz); - for (j = 0; j < sz; j++) { - mri.data[j] = tmp.readFloatBE(j * 4); - } - break; - case 4: // MGHSHORT - tmp = mgh.slice(hdr_sz, -ftrSz); - mri.data = new Int16Array(sz); - for (j = 0; j < sz; j++) { - mri.data[j] = tmp.readInt16BE(j * 2); - } - break; - default: - console.log('ERROR: Unknown dataType: ' + h.type); - } - - var i, - sum = 0, - min, max; - min = mri.data[0]; - max = min; - for (i = 0; i < sz; i++) { - sum += mri.data[i]; - - if (mri.data[i] < min) { - min = mri.data[i]; - } - if (mri.data[i] > max) { - max = mri.data[i]; - } - } - mri.sum = sum; - mri.min = min; - mri.max = max; + var Pcrs_c = [h.ndim1/2,h.ndim2/2,h.ndim3/2]; + var D = [[h.delta[0],0,0],[0,h.delta[1],0],[0,0,h.delta[2]]]; + var MdcD = [ + [h.Mdc[0]*h.delta[0], h.Mdc[3]*h.delta[1], h.Mdc[6]*h.delta[2]], + [h.Mdc[1]*h.delta[0], h.Mdc[4]*h.delta[1], h.Mdc[7]*h.delta[2]], + [h.Mdc[2]*h.delta[0], h.Mdc[5]*h.delta[1], h.Mdc[8]*h.delta[2]] + ]; + var Pxyz_0 = subVecVec(h.Pxyz_c,mulMatVec(MdcD,Pcrs_c)); + var M = [ + h.Mdc[0]*h.delta[0], h.Mdc[3]*h.delta[1], h.Mdc[6]*h.delta[2], Pxyz_0[0], + h.Mdc[1]*h.delta[0], h.Mdc[4]*h.delta[1], h.Mdc[7]*h.delta[2], Pxyz_0[1], + h.Mdc[2]*h.delta[0], h.Mdc[5]*h.delta[1], h.Mdc[8]*h.delta[2], Pxyz_0[2], + 0, 0, 0, 1 + ]; + + mri.dim=[h.ndim1,h.ndim2,h.ndim3]; + mri.pixdim=[h.delta[0],h.delta[1],h.delta[2]]; + mri.dir=[[M[0],-M[1],-M[2]],[M[4],-M[5],-M[6]],[M[8],-M[9],-M[10]]]; + mri.ori=[M[3],M[7],M[11]]; + + // compute the transformation from voxel space to screen space + computeS2VTransformation(mri); + + // test if the transformation looks incorrect. Reset it if it does + //testS2VTransformation(mri); + + sz = mri.dim[0]*mri.dim[1]*mri.dim[2]; + bpv = [1, 4, 0, 4, 2][h.type]; // bytes per voxel + console.log("sz:",sz); + console.log("bpv:",bpv,"type:",h.type); + + // keep the header + mri.hdr=mgh.slice(0,hdr_sz); + mri.hdrSz=hdr_sz; + + // keep the footer + ftrSz = mgh.length-hdr_sz-sz*bpv; + mri.ftr=mgh.slice(hdr_sz+sz*bpv); + + // print info + console.log(" mgh.length:", mgh.length); + console.log(" hdr_sz:", hdr_sz); + console.log(" sz*bpv:", sz*bpv); + console.log(" ftrSz:", ftrSz); + console.log("mri.ftr.length:", mri.ftr.length); + + switch(h.type) { + case 0: // MGHUCHAR + mri.data=mgh.slice(hdr_sz,-ftrSz); + break; + case 1: // MGHINT + tmp=mgh.slice(hdr_sz,-ftrSz); + mri.data=new Uint32Array(sz); + for(j=0;jmax) max=mri.data[i]; + } + mri.sum=sum; + mri.min=min; + mri.max=max; + + resolve(mri); + }); + } catch(e) { + reject("ERROR Cannot uncompress mgz file: "+e); + } + }); + + return pr; +}; - var saveAtlas = function saveAtlas(atlas) { - traceLog(saveAtlas); +var saveAtlas = function saveAtlas(atlas) { + traceLog(saveAtlas); /* - SaveAtlas + saveAtlas input: an mri structure process: a .nii.gz or .mgz file is saved at the position indicated in the mri structure output: success message */ - if (atlas && atlas.dim) { - if (atlas.data == undefined) { - console.log('ERROR: [saveAtlas] atlas in Atlas array has no data'); - if (debug) { - console.log('atlas:', atlas); - } - return Promise.reject('ERROR: [saveAtlas] atlas in Atlas array has no data'); - } - let i, - sum = 0; - for (i = 0; i < atlas.dim[0] * atlas.dim[1] * atlas.dim[2]; i++) { - sum += atlas.data[i]; - } - if (sum == atlas.sum) { - console.log(' Atlas', atlas.dirname, atlas.name, - 'no change, no save, freemem', os.freemem()); - return Promise.resolve('Done. No save required'); - } - atlas.sum = sum; - - const hdrSz = atlas.hdrSz; - const dataSz = atlas.data.length; - let ftrSz; - let mri; - - if (atlas.ftr) { - ftrSz = atlas.ftr.length; - } else { - ftrSz = 0; - } - - mri = new Buffer(atlas.dim[0] * atlas.dim[1] * atlas.dim[2] + hdrSz + ftrSz); - - console.log(' sum:', sum); - console.log('header size:', hdrSz); - console.log(' data size:', atlas.dim[0] * atlas.dim[1] * atlas.dim[2]); - console.log('footer size:', ftrSz); - console.log(' dim:', atlas.dim); - console.log(' Atlas', atlas.dirname, atlas.name, - 'hdr+data+ftr length', atlas.data.length + hdrSz + ftrSz, - 'buff length', mri.length); - - atlas.hdr.copy(mri); - atlas.data.copy(mri, hdrSz); - if (ftrSz) { - atlas.ftr.copy(mri, hdrSz + dataSz); - } - - var pr = new Promise((resolve, reject) => { - zlib.gzip(mri, function (err, mrigz) { - const ms = Number(new Date()); - const path1 = this.dataDirectory + atlas.dirname + atlas.name; - const path2 = this.dataDirectory + atlas.dirname + ms + '_' + atlas.name; - fs.rename(path1, path2, () => { - fs.writeFileSync(path1, mrigz); - resolve('Atlas saved'); - }); - }); - }); - } else { - return Promise.reject('ERROR: No atlas to save'); - } - - return pr; - }; - - var createNifti = function createNifti(templateMRI) { - traceLog(createNifti); + if(atlas && atlas.dim ) { + if(atlas.data==undefined) { + console.log("ERROR: [saveAtlas] atlas in Atlas array has no data"); + if(debug) console.log("atlas:",atlas); + return Promise.reject("ERROR: [saveAtlas] atlas in Atlas array has no data"); + } else { + var i,sum=0; + for(i=0;i= 0; i--) { - undoLayer = UndoStack[i]; - if (undoLayer === undefined) { - break; - } - if (undoLayer.User.username === User.username && - undoLayer.User.atlasFilename === User.atlasFilename && - undoLayer.User.specimenName === User.specimenName) { - found = true; - break; - } - } - if (!found) { - // There was no undoLayer for this user. This may be the - // first user's action. Create an appropriate undoLayer for it. - console.log(' No previous undo layer for ' + User.username + ', ' + User.atlasFilename + ', ' + User.specimenName + ': Create and push one'); - undoLayer = pushUndoLayer(User); - } - return undoLayer; - }; - const undo = function undo(User) { - traceLog(undo); - - let undoLayer; - var i, action, - found = false; - - // Find latest undo layer for user - for (i = UndoStack.length - 1; i >= 0; i--) { - undoLayer = UndoStack[i]; - if (undoLayer === undefined) { - break; - } - if (undoLayer.User.username === User.username && - undoLayer.User.atlasFilename === User.atlasFilename && - undoLayer.User.specimenName === User.specimenName && - undoLayer.actions.length > 0) { - found = true; - UndoStack.splice(i, 1); // Remove layer from UndoStack - if (debug) { - console.log(' Found undo layer for ' + User.username + ', ' + User.specimenName + ', ' + User.atlasFilename + ', with ' + undoLayer.actions.length + ' actions'); - } - break; - } - } - if (!found) { - // There was no undoLayer for this user. - if (debug) { - console.log(' No undo layers for user ' + User.username + ' in ' + User.specimenName + ', ' + User.atlasFilename); - } - return; - } - - // Undo latest actions - /* - undoLayer.actions is a sparse array, with many undefined values. - Here I take each of the values in actions, and add them to arr. - Each element of arr is an array of 2 elements, index and value. - */ - const arr = []; - let msg; - const atlas = Atlases[User.iAtlas]; - const vol = atlas.data; - let val; - - for (const j in undoLayer.actions) { - var i = parseInt(j); - val = undoLayer.actions[i]; - arr.push([i, val]); - - // The actual undo having place: - vol[i] = val; - - if (debug >= 3) { - console.log(' Undo:', i % User.dim[0], parseInt(i / User.dim[0]) % User.dim[1], parseInt(i / User.dim[0] / User.dim[1]) % User.dim[2]); - } - } - msg = {data: arr}; - broadcastPaintVolumeMessage(msg, User); - - if (debug) { - console.log(' ' + UndoStack.length + ' undo layers remaining (all users)'); - } - }; + if(debug) console.log(" Number of layers: "+UndoStack.length); + + return undoLayer; +}; +var getCurrentUndoLayer = function getCurrentUndoLayer(User) { + traceLog(getCurrentUndoLayer, 1); + + var i,undoLayer,found=false; + + for(i=UndoStack.length-1;i>=0;i--) { + undoLayer=UndoStack[i]; + if(undoLayer===undefined) + break; + if( undoLayer.User.username===User.username && + undoLayer.User.atlasFilename===User.atlasFilename && + undoLayer.User.specimenName===User.specimenName) { + found=true; + break; + } + } + if(!found) { + // There was no undoLayer for this user. This may be the + // first user's action. Create an appropriate undoLayer for it. + console.log(" No previous undo layer for "+User.username+", "+User.atlasFilename+", "+User.specimenName+": Create and push one"); + undoLayer=pushUndoLayer(User); + } + return undoLayer; +}; +var undo = function undo(User) { + traceLog(undo); + + var undoLayer; + var i,action,found=false; + + // find latest undo layer for user + for(i=UndoStack.length-1;i>=0;i--) { + undoLayer=UndoStack[i]; + if(undoLayer===undefined) + break; + if( undoLayer.User.username===User.username && + undoLayer.User.atlasFilename===User.atlasFilename && + undoLayer.User.specimenName===User.specimenName && + undoLayer.actions.length>0) { + found=true; + UndoStack.splice(i,1); // remove layer from UndoStack + if(debug) console.log(" Found undo layer for "+User.username+", "+User.specimenName+", "+User.atlasFilename+", with "+undoLayer.actions.length+" actions"); + break; + } + } + if(!found) { + // There was no undoLayer for this user. + if(debug) console.log(" No undo layers for user "+User.username+" in "+User.specimenName+", "+User.atlasFilename); + return; + } + + // undo latest actions + /* + undoLayer.actions is a sparse array, with many undefined values. + Here I take each of the values in actions, and add them to arr. + Each element of arr is an array of 2 elements, index and value. + */ + var arr=[]; + var msg; + var atlas=Atlases[User.iAtlas]; + var vol=atlas.data; + var val; + + for(var j in undoLayer.actions) { + var i=parseInt(j); + val=undoLayer.actions[i]; + arr.push([i,val]); + + // The actual undo having place: + vol[i]=val; + + if(debug>=3) console.log(" Undo:",i%User.dim[0],parseInt(i/User.dim[0])%User.dim[1],parseInt(i/User.dim[0]/User.dim[1])%User.dim[2]); + } + msg={"data":arr}; + broadcastPaintVolumeMessage(msg,User); + + if(debug) console.log(" "+UndoStack.length+" undo layers remaining (all users)"); +} -// ======================================================================================== +//======================================================================================== // Painting -// ======================================================================================== - var paintxy = function paintxy(u, c, x, y, User, undoLayer) { - traceLog(paintxy, 3); +//======================================================================================== +var paintxy = function paintxy(u, c, x, y, User, undoLayer) { + traceLog(paintxy,3); /* - From 'User' we know slice, atlas, vol, view, dim. - [issue: undoLayer also has a User field. Maybe only undoLayer should be kept?] + From 'User' we know slice, atlas, vol, view, dim. + [issue: undoLayer also has a User field. Maybe only undoLayer should be kept?] */ - const atlas = Atlases[User.iAtlas]; - if (atlas.data === undefined) { - console.log('ERROR: No atlas to draw into'); - return; - } - - const coord = {x, y, z: User.slice}; - if (User.x0 < 0) { - User.x0 = coord.x; - User.y0 = coord.y; - } - - switch (c) { - case 'me': - case 'mf': - User.x0 = coord.x; - User.y0 = coord.y; - break; - case 'le': // Line, erasing - line(coord.x, coord.y, 0, User, undoLayer); - User.x0 = coord.x; - User.y0 = coord.y; - break; - case 'lf': // Line, painting - line(coord.x, coord.y, User.penValue, User, undoLayer); - User.x0 = coord.x; - User.y0 = coord.y; - break; - case 'e': // Fill, erasing - fill(coord.x, coord.y, coord.z, 0, User, undoLayer); - User.x0 = -1; - break; - case 'f': // Fill, painting - fill(coord.x, coord.y, coord.z, User.penValue, User, undoLayer); - User.x0 = -1; - break; - case 'mu': // Mouse up (touch ended) - pushUndoLayer(User); - User.x0 = -1; - break; - case 'u': - undo(User); - User.x0 = -1; - break; - } - }; - const paintVoxel = function paintVoxel(mx, my, mz, User, vol, val, undoLayer) { - traceLog(paintVoxel, 3); - const view = User.view; - const atlas = Atlases[User.iAtlas]; - let x, y, z; - const sdim = User.s2v.sdim; - - switch (view) { - case 'sag': x = mz; y = mx; z = sdim[2] - 1 - my; break; // Sagital - case 'cor': x = mx; y = mz; z = sdim[2] - 1 - my; break; // Coronal - case 'axi': x = mx; y = sdim[1] - 1 - my; z = mz; break; // Axial - } - - let s, v; - s = [x, y, z]; - i = S2I(s, User); - if (vol[i] != val) { - undoLayer.actions[i] = vol[i]; - vol[i] = val; - } - }; - const sliceXYZ2index = function sliceXYZ2index(mx, my, mz, User) { - traceLog(sliceXYZ2index, 3); - - const view = User.view; - const atlas = Atlases[User.iAtlas]; - const dim = atlas.dim; - let x, y, z; - let i = -1; - const sdim = User.s2v.sdim; - - switch (view) { - case 'sag': x = mz; y = mx; z = sdim[2] - 1 - my; break; // Sagital - case 'cor': x = mx; y = mz; z = sdim[2] - 1 - my; break; // Coronal - case 'axi': x = mx; y = sdim[1] - 1 - my; z = mz; break; // Axial - } - let s; - s = [x, y, z]; - i = S2I(s, User); - - return i; - }; - var line = function line(x, y, val, User, undoLayer) { - traceLog(line, 3); - - // Bresenham's line algorithm adapted from - // http://stackoverflow.com/questions/4672279/bresenham-algorithm-in-javascript - - const atlas = Atlases[User.iAtlas]; - const vol = atlas.data; - const dim = atlas.dim; - let x1 = User.x0; // Screen coords - let y1 = User.y0; // Screen coords - const z = User.slice; // Screen coords - const view = User.view; // View: sag, cor or axi - const sdim = User.s2v.sdim; - const x2 = x; - const y2 = y; - let i; - let brain_W, brain_H; - - if (Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2) > 20 * 20) { - console.log('WARNING: long line from', x1, y1, 'to', x2, y2); - console.log('User.uid:', User.uid); - displayUsers(); - console.log('END WARNING'); - } + var atlas=Atlases[User.iAtlas]; + if(atlas.data===undefined) { + console.log("ERROR: No atlas to draw into"); + return; + } + + var coord={"x":x,"y":y,"z":User.slice}; + if(User.x0<0) { + User.x0=coord.x; + User.y0=coord.y; + } + + switch(c) { + case 'me': + case 'mf': + User.x0=coord.x; + User.y0=coord.y; + break; + case 'le': // Line, erasing + line(coord.x,coord.y,0,User,undoLayer); + User.x0=coord.x; + User.y0=coord.y; + break; + case 'lf': // Line, painting + line(coord.x,coord.y,User.penValue,User,undoLayer); + User.x0=coord.x; + User.y0=coord.y; + break; + case 'e': // Fill, erasing + fill(coord.x,coord.y,coord.z,0,User,undoLayer); + User.x0=-1; + break; + case 'f': // Fill, painting + fill(coord.x,coord.y,coord.z,User.penValue,User,undoLayer); + User.x0=-1; + break; + case 'mu': // Mouse up (touch ended) + pushUndoLayer(User); + User.x0=-1; + break; + case 'u': + undo(User); + User.x0=-1; + break; + } +}; +var paintVoxel = function paintVoxel(mx, my, mz, User, vol, val, undoLayer) { + traceLog(paintVoxel,3); + var view=User.view; + var atlas=Atlases[User.iAtlas]; + var x,y,z; + var sdim=User.s2v.sdim; + + switch(view) { + case 'sag': x=mz; y=mx; z=sdim[2]-1-my;break; // sagital + case 'cor': x=mx; y=mz; z=sdim[2]-1-my;break; // coronal + case 'axi': x=mx; y=sdim[1]-1-my; z=mz;break; // axial + } + + var s,v; + s=[x,y,z]; + i=S2I(s,User); + if(vol[i]!=val) { + undoLayer.actions[i]=vol[i]; + vol[i]=val; + } +}; +var sliceXYZ2index = function sliceXYZ2index(mx, my, mz, User) { + traceLog(sliceXYZ2index,3); + + var view=User.view; + var atlas=Atlases[User.iAtlas]; + var dim=atlas.dim; + var x,y,z; + var i=-1; + var sdim=User.s2v.sdim; + + switch(view) { + case 'sag': x=mz; y=mx; z=sdim[2]-1-my;break; // sagital + case 'cor': x=mx; y=mz; z=sdim[2]-1-my;break; // coronal + case 'axi': x=mx; y=sdim[1]-1-my; z=mz;break; // axial + } + var s; + s=[x,y,z]; + i=S2I(s,User); + + return i; +}; +var line = function line(x, y, val, User, undoLayer) { + traceLog(line,3); + + // Bresenham's line algorithm adapted from + // http://stackoverflow.com/questions/4672279/bresenham-algorithm-in-javascript + + var atlas=Atlases[User.iAtlas]; + var vol=atlas.data; + var dim=atlas.dim; + var x1=User.x0; // screen coords + var y1=User.y0; // screen coords + var z=User.slice; // screen coords + var view=User.view; // view: sag, cor or axi + var sdim=User.s2v.sdim; + var x2=x; + var y2=y; + var i; + var brain_W, brain_H; + + if(Math.pow(x1-x2,2)+Math.pow(y1-y2,2)>20*20) { + console.log("WARNING: long line from",x1,y1,"to",x2,y2); + console.log("User.uid:",User.uid); + displayUsers(); + console.log("END WARNING"); + } // Define differences and error check - const dx = Math.abs(x2 - x1); - const dy = Math.abs(y2 - y1); - const sx = (x1 < x2) ? 1 : -1; - const sy = (y1 < y2) ? 1 : -1; - let err = dx - dy; - - switch (view) { - case 'sag': brain_W = sdim[1]; brain_H = sdim[2]; break; // Sagital - case 'cor': brain_W = sdim[0]; brain_H = sdim[2]; break; // Coronal - case 'axi': brain_W = sdim[0]; brain_H = sdim[1]; break; // Axial - } - - for (j = 0; j < Math.min(User.penSize, brain_W - x1); j++) { - for (k = 0; k < Math.min(User.penSize, brain_H - y1); k++) { - paintVoxel(x1 + j, y1 + k, z, User, vol, val, undoLayer); - } - } - - while (!((x1 === x2) && (y1 === y2))) { - const e2 = err << 1; - if (e2 > -dy) { - err -= dy; - x1 += sx; - } - if (e2 < dx) { - err += dx; - y1 += sy; - } - for (j = 0; j < Math.min(User.penSize, brain_W - x1); j++) { - for (k = 0; k < Math.min(User.penSize, brain_H - y1); k++) { -paintVoxel(x1 + j, y1 + k, z, User, vol, val, undoLayer); + var dx = Math.abs(x2 - x1); + var dy = Math.abs(y2 - y1); + var sx = (x1 < x2) ? 1 : -1; + var sy = (y1 < y2) ? 1 : -1; + var err = dx - dy; + + switch(view) { + case 'sag': brain_W=sdim[1]; brain_H=sdim[2]; break; // sagital + case 'cor': brain_W=sdim[0]; brain_H=sdim[2]; break; // coronal + case 'axi': brain_W=sdim[0]; brain_H=sdim[1]; break; // axial + } + + for(j=0;j -dy) { + err -= dy; + x1 += sx; + } + if (e2 < dx) { + err += dx; + y1 += sy; + } + for(j=0;j0) { + if(Q.length>max) + max=Q.length; + n=Q.shift(); + if(vol[sliceXYZ2index(n.x,n.y,z,User)]!=bval) + continue; + left=n.x; + right=n.x; + y=n.y; + while (left-1>=0 && vol[sliceXYZ2index(left-1,y,z,User)]==bval) { + left--; + } + while (right+1=0 && vol[sliceXYZ2index(x,y-1,z,User)]==bval) + Q.push({x:x,y:y-1}); + if(y+1 0) { - if (Q.length > max) { - max = Q.length; - } - n = Q.shift(); - if (vol[sliceXYZ2index(n.x, n.y, z, User)] != bval) { - continue; - } - left = n.x; - right = n.x; - y = n.y; - while (left - 1 >= 0 && vol[sliceXYZ2index(left - 1, y, z, User)] == bval) { - left--; - } - while (right + 1 < brain_W && vol[sliceXYZ2index(right + 1, y, z, User)] == bval) { - right++; - } - for (x = left; x <= right; x++) { - paintVoxel(x, y, z, User, vol, val, undoLayer); - if (y - 1 >= 0 && vol[sliceXYZ2index(x, y - 1, z, User)] == bval) { - Q.push({x, y: y - 1}); - } - if (y + 1 < brain_H && vol[sliceXYZ2index(x, y + 1, z, User)] == bval) { - Q.push({x, y: y + 1}); - } - } - } - console.log('Max array size for fill:', max); - }; /* - Serve brain slices + Serve brain slices */ - var mulMatVec = function mulMatVec(m, v) { - traceLog(mulMatVec, 3); - return [ - m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2], - m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2], - m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2] - ]; - }; - const invMat = function invMat(m) { - traceLog(invMat, 3); - - let det; - const w = [[], [], []]; - - det = m[0][1] * m[1][2] * m[2][0] + m[0][2] * m[1][0] * m[2][1] + m[0][0] * m[1][1] * m[2][2] - m[0][2] * m[1][1] * m[2][0] - m[0][0] * m[1][2] * m[2][1] - m[0][1] * m[1][0] * m[2][2]; - - w[0][0] = (m[1][1] * m[2][2] - m[1][2] * m[2][1]) / det; - w[0][1] = (m[0][2] * m[2][1] - m[0][1] * m[2][2]) / det; - w[0][2] = (m[0][1] * m[1][2] - m[0][2] * m[1][1]) / det; - - w[1][0] = (m[1][2] * m[2][0] - m[1][0] * m[2][2]) / det; - w[1][1] = (m[0][0] * m[2][2] - m[0][2] * m[2][0]) / det; - w[1][2] = (m[0][2] * m[1][0] - m[0][0] * m[1][2]) / det; - - w[2][0] = (m[1][0] * m[2][1] - m[1][1] * m[2][0]) / det; - w[2][1] = (m[0][1] * m[2][0] - m[0][0] * m[2][1]) / det; - w[2][2] = (m[0][0] * m[1][1] - m[0][1] * m[1][0]) / det; - - return w; - }; - var subVecVec = function subVecVec(a, b) { - traceLog(subVecVec, 3); - - return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; - }; - const addVecVec = function addVecVec(a, b) { - traceLog(addVecVec, 3); - - return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; - }; - var computeS2VTransformation = function computeS2VTransformation(mri) { - traceLog(computeS2VTransformation); - /* - The basic transformation is - w = v2w * v + wori - - Where: - w: world coordinates - wori: origin of the world coordinates - v: voxel coordinates - v2w: rotation matrix from v to w - - In what follows: - v refers to native voxel coordinates - w refers to world coordinates - s refers screen pixel coordinates - */ - const wori = mri.ori; - // Space directions are transposed! - const v2w = [[], [], []]; - // For(j in mri.dir) for(i in mri.dir[j]) v2w[i][j]=mri.dir[j][i]; // transpose - for (j in mri.dir) { - for (i in mri.dir[j]) { - v2w[i][j] = mri.dir[i][j]; - } - } // Do not transpose - const wpixdim = subVecVec(mulMatVec(v2w, [1, 1, 1]), mulMatVec(v2w, [0, 0, 0])); - // Min and max world coordinates - const wvmax = addVecVec(mulMatVec(v2w, [mri.dim[0] - 1, mri.dim[1] - 1, mri.dim[2] - 1]), wori); - const wvmin = addVecVec(mulMatVec(v2w, [0, 0, 0]), wori); - const wmin = [Math.min(wvmin[0], wvmax[0]), Math.min(wvmin[1], wvmax[1]), Math.min(wvmin[2], wvmax[2])]; - const wmax = [Math.max(wvmin[0], wvmax[0]), Math.max(wvmin[1], wvmax[1]), Math.max(wvmin[2], wvmax[2])]; - const w2s = [[1 / Math.abs(wpixdim[0]), 0, 0], [0, 1 / Math.abs(wpixdim[1]), 0], [0, 0, 1 / Math.abs(wpixdim[2])]]; - - // Console.log(["v2w",v2w, "wori",wori, "wpixdim",wpixdim, "wvmax",wvmax, "wvmin",wvmin, "wmin",wmin, "wmax",wmax, "w2s",w2s]); - - var i = v2w[0]; - var j = v2w[1]; - const k = v2w[2]; - let mi = {i: 0, v: 0}; i.map((o, n) => { - if (Math.abs(o) > Math.abs(mi.v)) { - mi = {i: n, v: o}; - } - }); - let mj = {i: 0, v: 0}; j.map((o, n) => { - if (Math.abs(o) > Math.abs(mj.v)) { - mj = {i: n, v: o}; - } - }); - let mk = {i: 0, v: 0}; k.map((o, n) => { - if (Math.abs(o) > Math.abs(mk.v)) { - mk = {i: n, v: o}; - } - }); - - mri.s2v = { - // Old s2v fields - s2w: invMat(w2s), - sdim: [], - sori: [-wmin[0] / Math.abs(wpixdim[0]), -wmin[1] / Math.abs(wpixdim[1]), -wmin[2] / Math.abs(wpixdim[2])], - w2v: invMat(v2w), - wori, - - // New s2v transformation - x: mi.i, // Correspondence between space coordinate x and voxel coordinate i - y: mj.i, // Same for y - z: mk.i, // Same for z - dx: (mi.v > 0) ? 1 : (-1), // Direction of displacement in space coordinate x with displacement in voxel coordinate i - dy: (mj.v > 0) ? 1 : (-1), // Same for y - dz: (mk.v > 0) ? 1 : (-1), // Same for z - X: (mi.v > 0) ? 0 : (mri.dim[0] - 1), // Starting value for space coordinate x when voxel coordinate i starts - Y: (mj.v > 0) ? 0 : (mri.dim[1] - 1), // Same for y - Z: (mk.v > 0) ? 0 : (mri.dim[2] - 1) // Same for z - }; - mri.v2w = v2w; - mri.wori = wori; - mri.s2v.sdim[mi.i] = mri.dim[0]; - mri.s2v.sdim[mj.i] = mri.dim[1]; - mri.s2v.sdim[mk.i] = mri.dim[2]; - }; - const testS2VTransformation = function testS2VTransformation(mri) { - traceLog(testS2VTransformation); - - /* - Check the S2V transformation to see if it looks correct. - If it does not, reset it - */ - let doReset = false; - - console.log(' Transformation TEST:'); - - if (debug) { - process.stdout.write(' 1. transformation volume: '); - } - const vv = mri.dim[0] * mri.dim[1] * mri.dim[2]; - const vs = mri.s2v.sdim[0] * mri.s2v.sdim[1] * mri.s2v.sdim[2]; - const diff = (vs - vv) / vv; - if (Math.abs(diff) > 0.001) { - doReset = true; - if (debug) { - console.log(' fail. Voxel volume:', vv, 'Screen volume:', vs, 'Difference (%):', diff); - } - } else if (debug) { - console.log(' ok'); - } - - if (debug) { - process.stdout.write(' 2. transformation origin: '); - } - if (mri.s2v.sori[0] < 0 || mri.s2v.sori[0] > mri.s2v.sdim[0] || - mri.s2v.sori[1] < 0 || mri.s2v.sori[1] > mri.s2v.sdim[1] || - mri.s2v.sori[2] < 0 || mri.s2v.sori[2] > mri.s2v.sdim[2]) { - doReset = true; - if (debug) { - console.log(' fail'); - } - } else if (debug) { - console.log(' ok'); - } - - if (doReset) { - console.log(' FAIL: TRANSFORMATION WILL BE RESET'); - console.log(mri.dir); - console.log(mri.ori); - mri.dir = [[mri.pixdim[0], 0, 0], [0, -mri.pixdim[1], 0], [0, 0, -mri.pixdim[2]]]; - mri.ori = [0, mri.dim[1] - 1, mri.dim[2] - 1]; - computeS2VTransformation(mri); - - if (debug > 2) { - console.log('dir', mri.dir); - console.log('ori', mri.ori); - console.log('s2v', mri.s2v); - } - } else { - console.log(' ok'); - } - }; - var S2I = function S2I(s, mri) { - traceLog(S2I, 3); - - const s2v = mri.s2v; - const v = [s2v.X + s2v.dx * s[s2v.x], s2v.Y + s2v.dy * s[s2v.y], s2v.Z + s2v.dz * s[s2v.z]]; - index = v[0] + v[1] * mri.dim[0] + v[2] * mri.dim[0] * mri.dim[1]; - return index; - }; - var drawSlice = function drawSlice(brain, view, slice) { - traceLog(drawSlice, 1); - - let x, y, i, j; - let brain_W, brain_H, brain_D; - let ys, ya, yc; - let val; - let s, - s2v = brain.s2v; - - switch (view) { - case 'sag': brain_W = s2v.sdim[1]; brain_H = s2v.sdim[2]; brain_D = s2v.sdim[0]; break; // Sagital - case 'cor': brain_W = s2v.sdim[0]; brain_H = s2v.sdim[2]; brain_D = s2v.sdim[1]; break; // Coronal - case 'axi': brain_W = s2v.sdim[0]; brain_H = s2v.sdim[1]; brain_D = s2v.sdim[2]; break; // Axial - } - - const frameData = new Buffer(brain_W * brain_H * 4); - - j = 0; - switch (view) { - case 'sag':ys = slice; break; - case 'cor':yc = slice; break; - case 'axi':ya = slice; break; - } - - for (y = 0; y < brain_H; y++) { - for (x = 0; x < brain_W; x++) { - switch (view) { - case 'sag': s = [ys, x, s2v.sdim[2] - 1 - y]; break; - case 'cor': s = [x, yc, s2v.sdim[2] - 1 - y]; break; - case 'axi': s = [x, s2v.sdim[1] - 1 - y, ya]; break; - } - i = S2I(s, brain); - - val = 255 * (brain.data[i] - brain.min) / (brain.max - brain.min); - frameData[4 * j + 0] = val; // Red - frameData[4 * j + 1] = val; // Green - frameData[4 * j + 2] = val; // Blue - frameData[4 * j + 3] = 0xFF; // Alpha - ignored in JPEGs - j++; - } - } - - const rawImageData = { - data: frameData, - width: brain_W, - height: brain_H - }; - return jpeg.encode(rawImageData, 99); - }; - - const colormap = [{r: 0, g: 0, b: 0}]; - for (var i = 1; i < 256; i++) { - colormap.push({ - r: 100 + Math.random() * 155, - g: 100 + Math.random() * 155, - b: 100 + Math.random() * 155 - }); - } - var drawSlice2 = function drawSlice2(brain, atlas, view, slice) { - traceLog(drawSlice2, 1); - - let x, y, i, j; - let brain_W, brain_H, brain_D; - let ys, ya, yc; - let val, rgb; - let s, - s2v = brain.s2v, - t = 0.5; - - switch (view) { - case 'sag': brain_W = s2v.sdim[1]; brain_H = s2v.sdim[2]; brain_D = s2v.sdim[0]; break; // Sagital - case 'cor': brain_W = s2v.sdim[0]; brain_H = s2v.sdim[2]; brain_D = s2v.sdim[1]; break; // Coronal - case 'axi': brain_W = s2v.sdim[0]; brain_H = s2v.sdim[1]; brain_D = s2v.sdim[2]; break; // Axial - } - - const frameData = new Buffer(brain_W * brain_H * 4); - - j = 0; - switch (view) { - case 'sag':ys = slice; break; - case 'cor':yc = slice; break; - case 'axi':ya = slice; break; - } - - for (y = 0; y < brain_H; y++) { - for (x = 0; x < brain_W; x++) { - switch (view) { - case 'sag': s = [ys, x, s2v.sdim[2] - 1 - y]; break; - case 'cor': s = [x, yc, s2v.sdim[2] - 1 - y]; break; - case 'axi': s = [x, s2v.sdim[1] - 1 - y, ya]; break; - } - i = S2I(s, brain); - - // Brain data - val = (brain.data[i] - brain.min) / (brain.max - brain.min); - - // Atlas data - if (atlas.data[i]) { - rgb = colormap[atlas.data[i]]; - if (!rgb || !rgb.r) { - frameData[4 * j + 0] = 0; - frameData[4 * j + 0] = 255; - frameData[4 * j + 0] = 0; - } else { - frameData[4 * j + 0] = t * rgb.r + (1 - t) * rgb.r * val; // Red - frameData[4 * j + 1] = t * rgb.g + (1 - t) * rgb.g * val; // Green - frameData[4 * j + 2] = t * rgb.b + (1 - t) * rgb.b * val; // Blue - } - } else { - frameData[4 * j + 0] = 255 * val; // Red - frameData[4 * j + 1] = 255 * val; // Green - frameData[4 * j + 2] = 255 * val; // Blue - } - frameData[4 * j + 3] = 0xFF; // Alpha - ignored in JPEGs - j++; - } - } +var mulMatVec = function mulMatVec(m, v) { + traceLog(mulMatVec,3); + return [ + m[0][0]*v[0]+m[0][1]*v[1]+m[0][2]*v[2], + m[1][0]*v[0]+m[1][1]*v[1]+m[1][2]*v[2], + m[2][0]*v[0]+m[2][1]*v[1]+m[2][2]*v[2] + ]; +}; +var invMat = function invMat(m) { + traceLog(invMat,3); + + var det; + var w=[[],[],[]]; + + det=m[0][1]*m[1][2]*m[2][0] + m[0][2]*m[1][0]*m[2][1] + m[0][0]*m[1][1]*m[2][2] - m[0][2]*m[1][1]*m[2][0] - m[0][0]*m[1][2]*m[2][1] - m[0][1]*m[1][0]*m[2][2]; + + w[0][0]=(m[1][1]*m[2][2] - m[1][2]*m[2][1])/det; + w[0][1]=(m[0][2]*m[2][1] - m[0][1]*m[2][2])/det; + w[0][2]=(m[0][1]*m[1][2] - m[0][2]*m[1][1])/det; + + w[1][0]=(m[1][2]*m[2][0] - m[1][0]*m[2][2])/det; + w[1][1]=(m[0][0]*m[2][2] - m[0][2]*m[2][0])/det; + w[1][2]=(m[0][2]*m[1][0] - m[0][0]*m[1][2])/det; + + w[2][0]=(m[1][0]*m[2][1] - m[1][1]*m[2][0])/det; + w[2][1]=(m[0][1]*m[2][0] - m[0][0]*m[2][1])/det; + w[2][2]=(m[0][0]*m[1][1] - m[0][1]*m[1][0])/det; + + return w; +}; +var subVecVec = function subVecVec(a, b) { + traceLog(subVecVec,3); + + return [a[0]-b[0],a[1]-b[1],a[2]-b[2]]; +}; +var addVecVec = function addVecVec(a, b) { + traceLog(addVecVec,3); + + return [a[0]+b[0],a[1]+b[1],a[2]+b[2]]; +}; +var computeS2VTransformation = function computeS2VTransformation(mri) { + traceLog(computeS2VTransformation); + /* + The basic transformation is + w = v2w * v + wori + + Where: + w: world coordinates + wori: origin of the world coordinates + v: voxel coordinates + v2w: rotation matrix from v to w + + In what follows: + v refers to native voxel coordinates + w refers to world coordinates + s refers screen pixel coordinates + */ + var wori=mri.ori; + // space directions are transposed! + var v2w=[[],[],[]]; + //for(j in mri.dir) for(i in mri.dir[j]) v2w[i][j]=mri.dir[j][i]; // transpose + for(j in mri.dir) for(i in mri.dir[j]) v2w[i][j]=mri.dir[i][j]; // do not transpose + var wpixdim=subVecVec(mulMatVec(v2w,[1,1,1]),mulMatVec(v2w,[0,0,0])); + // min and max world coordinates + var wvmax=addVecVec(mulMatVec(v2w,[mri.dim[0]-1,mri.dim[1]-1,mri.dim[2]-1]),wori); + var wvmin=addVecVec(mulMatVec(v2w,[0,0,0]),wori); + var wmin=[Math.min(wvmin[0],wvmax[0]),Math.min(wvmin[1],wvmax[1]),Math.min(wvmin[2],wvmax[2])]; + var wmax=[Math.max(wvmin[0],wvmax[0]),Math.max(wvmin[1],wvmax[1]),Math.max(wvmin[2],wvmax[2])]; + var w2s=[[1/Math.abs(wpixdim[0]),0,0],[0,1/Math.abs(wpixdim[1]),0],[0,0,1/Math.abs(wpixdim[2])]]; + + // console.log(["v2w",v2w, "wori",wori, "wpixdim",wpixdim, "wvmax",wvmax, "wvmin",wvmin, "wmin",wmin, "wmax",wmax, "w2s",w2s]); + + var i=v2w[0]; + var j=v2w[1]; + var k=v2w[2]; + var mi={i:0,v:0};i.map(function(o,n){if(Math.abs(o)>Math.abs(mi.v)) mi={i:n,v:o}}); + var mj={i:0,v:0};j.map(function(o,n){if(Math.abs(o)>Math.abs(mj.v)) mj={i:n,v:o}}); + var mk={i:0,v:0};k.map(function(o,n){if(Math.abs(o)>Math.abs(mk.v)) mk={i:n,v:o}}); + + mri.s2v = { + // old s2v fields + s2w: invMat(w2s), + sdim: [], + sori: [-wmin[0]/Math.abs(wpixdim[0]),-wmin[1]/Math.abs(wpixdim[1]),-wmin[2]/Math.abs(wpixdim[2])], + w2v: invMat(v2w), + wori: wori, + + // new s2v transformation + x: mi.i, // correspondence between space coordinate x and voxel coordinate i + y: mj.i, // same for y + z: mk.i, // same for z + dx: (mi.v>0)?1:(-1), // direction of displacement in space coordinate x with displacement in voxel coordinate i + dy: (mj.v>0)?1:(-1), // same for y + dz: (mk.v>0)?1:(-1), // same for z + X: (mi.v>0)?0:(mri.dim[0]-1), // starting value for space coordinate x when voxel coordinate i starts + Y: (mj.v>0)?0:(mri.dim[1]-1), // same for y + Z: (mk.v>0)?0:(mri.dim[2]-1) // same for z + }; + mri.v2w=v2w; + mri.wori=wori; + mri.s2v.sdim[mi.i] = mri.dim[0]; + mri.s2v.sdim[mj.i] = mri.dim[1]; + mri.s2v.sdim[mk.i] = mri.dim[2]; +}; +var testS2VTransformation = function testS2VTransformation(mri) { + traceLog(testS2VTransformation); + + /* + check the S2V transformation to see if it looks correct. + If it does not, reset it + */ + var doReset=false; + + console.log(" Transformation TEST:"); + + if(debug) process.stdout.write(" 1. transformation volume: "); + var vv=mri.dim[0]*mri.dim[1]*mri.dim[2]; + var vs=mri.s2v.sdim[0]*mri.s2v.sdim[1]*mri.s2v.sdim[2]; + var diff=(vs-vv)/vv; + if(Math.abs(diff)>0.001) { + doReset=true; + if(debug) console.log(" fail. Voxel volume:",vv,"Screen volume:",vs,"Difference (%):",diff); + } else { + if(debug) console.log(" ok"); + } + + if(debug) process.stdout.write(" 2. transformation origin: "); + if( mri.s2v.sori[0]<0||mri.s2v.sori[0]>mri.s2v.sdim[0] || + mri.s2v.sori[1]<0||mri.s2v.sori[1]>mri.s2v.sdim[1] || + mri.s2v.sori[2]<0||mri.s2v.sori[2]>mri.s2v.sdim[2]) { + doReset=true; + if(debug) console.log(" fail"); + } else { + if(debug) console.log(" ok"); + } + + if(doReset) { + console.log(" FAIL: TRANSFORMATION WILL BE RESET"); + console.log(mri.dir); + console.log(mri.ori); + mri.dir=[[mri.pixdim[0],0,0],[0,-mri.pixdim[1],0],[0,0,-mri.pixdim[2]]]; + mri.ori=[0,mri.dim[1]-1,mri.dim[2]-1]; + computeS2VTransformation(mri); + + if(debug>2) { + console.log("dir",mri.dir); + console.log("ori",mri.ori); + console.log("s2v",mri.s2v); + } + } else { + console.log(" ok"); + } +}; +var S2I = function S2I(s, mri) { + traceLog(S2I,3); + + var s2v = mri.s2v; + var v = [s2v.X+s2v.dx*s[s2v.x],s2v.Y+s2v.dy*s[s2v.y],s2v.Z+s2v.dz*s[s2v.z]]; + index = v[0] + v[1]*mri.dim[0] + v[2]*mri.dim[0]*mri.dim[1]; + return index; +}; +var drawSlice = function drawSlice(brain, view, slice) { + traceLog(drawSlice,1); + + var x,y,i,j; + var brain_W, brain_H, brain_D; + var ys,ya,yc; + var val; + var s,s2v=brain.s2v; + + switch(view) { + case 'sag': brain_W=s2v.sdim[1]; brain_H=s2v.sdim[2]; brain_D=s2v.sdim[0]; break; // sagital + case 'cor': brain_W=s2v.sdim[0]; brain_H=s2v.sdim[2]; brain_D=s2v.sdim[1]; break; // coronal + case 'axi': brain_W=s2v.sdim[0]; brain_H=s2v.sdim[1]; brain_D=s2v.sdim[2]; break; // axial + } + + var frameData = new Buffer(brain_W * brain_H * 4); + + j=0; + switch(view) { + case 'sag':ys=slice; break; + case 'cor':yc=slice; break; + case 'axi':ya=slice; break; + } + + for(y=0;y l) { - console.log('ca> ' + (f.name) + ' ' + (f.caller ? (f.caller.name || 'annonymous') : 'root')); - } -} + if(l==undefined || debug>l) + console.log("ca> "+(f.name)+" "+(f.caller?(f.caller.name||"annonymous"):"root")); +}; -const accessStringToLevel = function accessStringToLevel(string) { - let level = accessLevels.indexOf(string); - if (level < 0) { +var accessStringToLevel = function accessStringToLevel(string) { + var level = accessLevels.indexOf(string); + if( level < 0 ) level = 0; - } return level; -}; -const accessLevelToString = function accessLevelToString(level) { +} +var accessLevelToString = function accessLevelToString(level) { level = parseInt(level); - if (level < 0) { + if(level<0) level = 0; - } - if (level >= accessLevels.length) { + if(level>=accessLevels.length) level = accessLevels.length - 1; - } return accessLevels[level]; -}; +} /** * @func toFileByAllProjects * @desc Check the access a user has to an MRI file based on a list of projects. The MRI @@ -33,65 +29,60 @@ const accessLevelToString = function accessLevelToString(level) { * @param {Object} user The user whose access is being decided * @param {Object} access The access level requested */ -const toFileByAllProjects = function toFileByAllProjects(mri, projects, user, access) { - traceLog(toFileByAllProjects, 1); - - let p, - requestedLevel = accessLevels.indexOf(access); - - for (p in projects) { - if (projects[p].files.list.indexOf(mri.source) >= 0) { - // Verify that the user 'anyone' exists and store their access rights - var i, anyone, - found = false; - for (i = 0; i < projects[p].collaborators.list.length; i++) { - if (projects[p].collaborators.list[i].userID === 'anyone') { - anyone = projects[p].collaborators.list[i]; +var toFileByAllProjects = function toFileByAllProjects(mri, projects, user, access) { + traceLog(toFileByAllProjects,1); + + var p, requestedLevel = accessLevels.indexOf(access); + + for(p in projects) { + if(projects[p].files.list.indexOf(mri.source)>=0) { + + // verify that the user 'anyone' exists and store their access rights + var i, anyone, found = false; + for(i=0;i collaboratorAccessLevel) { - if (debug > 1) { - console.log('WARNING: Collaborator access refused from project', projects[p].shortname); - } + + // check if user has collaborator access + var accessOk = false; + // collaborators=projects[p].files.access.whitelist; + collaborators=projects[p].collaborators.list; + for(c in collaborators) { + if(collaborators[c].userID === user) { + var collaboratorAccessLevel = accessLevels.indexOf(collaborators[c].access.files); + if(requestedLevel>collaboratorAccessLevel) { + if(debug>1) console.log("WARNING: Collaborator access refused from project",projects[p].shortname); return false; + } else { + accessOk = true; } - accessOk = true; - break; } } - if (accessOk) { + if(accessOk) { continue; } - // Check if user has public access - if (requestedLevel > publicLevel) { - if (debug > 1) { - console.log('WARNING: Public access refused from project', projects[p].shortname); - } + // check if user has public access + if(requestedLevel>publicLevel) { + if(debug>1) console.log("WARNING: Public access refused from project",projects[p].shortname); return false; } } @@ -108,62 +99,59 @@ const toFileByAllProjects = function toFileByAllProjects(mri, projects, user, ac * @param {Object} user The user whose access is being decided * @param {Object} access The access level requested */ -const toFileByOneProject = function toFileByOneProject(mri, projects, user, access) { +var toFileByOneProject = function toFileByOneProject(mri, projects, user, access) { traceLog(toFileByOneProject); - let p, - requestedLevel = accessLevels.indexOf(access); - - for (p in projects) { - if (projects[p].files.list.indexOf(mri.source) >= 0) { - // Verify that the user 'anyone' exists and store their access rights - var i, anyone, - found = false; - for (i = 0; i < projects[p].collaborators.list.length; i++) { - if (projects[p].collaborators.list[i].userID === 'anyone') { - anyone = projects[p].collaborators.list[i]; + var p, requestedLevel = accessLevels.indexOf(access); + + for(p in projects) { + if(projects[p].files.list.indexOf(mri.source)>=0) { + + // verify that the user 'anyone' exists and store their access rights + var i, anyone, found = false; + for(i=0;i 1) { - console.log('WARNING: No project grants access to MRI'); - } + if(debug>1) console.log("WARNING: No project grants access to MRI"); return false; }; @@ -178,45 +166,44 @@ const toFileByOneProject = function toFileByOneProject(mri, projects, user, acce var maxAccessToFileByProjects = function maxAccessToFileByProjects(mri, projects, user) { traceLog(maxAccessToFileByProjects); - let p, - maxLevel = 0; + var p, maxLevel = 0; - for (p in projects) { - if (projects[p].files.list.indexOf(mri.source) >= 0) { - // Check owner level - if (user === projects[p].owner) { - // Owner grants the maximum access level + for(p in projects) { + if(projects[p].files.list.indexOf(mri.source)>=0) { + + // check owner level + if(user === projects[p].owner) { + // owner grants the maximum access level // no need to look further - return 'remove'; + return "remove"; } - // Check public level + // check public level // verify that user 'anyone' exists and store their access rights - var i, anyone, - found = false; - for (i = 0; i < projects[p].collaborators.list.length; i++) { - if (projects[p].collaborators.list[i].userID === 'anyone') { - anyone = projects[p].collaborators.list[i]; + var i, anyone, found = false; + for(i=0;i= 0) { - // Check owner level - if (user === projects[p].owner) { - if (minLevel > accessLevels.indexOf('remove')) { - minLevel = accessLevels.indexOf('remove'); - } + for(p in projects) { + if(projects[p].files.list.indexOf(mri.source)>=0) { + + // check owner level + if(user === projects[p].owner) { + if(minLevel > accessLevels.indexOf("remove")) + minLevel = accessLevels.indexOf("remove"); } - // Check public level + // check public level // verify that user 'anyone' exists and store their access rights - var i, anyone, - found = false; - for (i = 0; i < projects[p].collaborators.list.length; i++) { - if (projects[p].collaborators.list[i].userID === 'anyone') { - anyone = projects[p].collaborators.list[i]; + var i, anyone, found = false; + for(i=0;i publicLevel) { + var publicLevel = accessLevels.indexOf(anyone.access.files); + if(minLevel > publicLevel ) { minLevel = publicLevel; } - - // Check if user has collaborator access + + // check if user has collaborator access var c, collaborators; - collaborators = projects[p].collaborators.list; - for (c in collaborators) { - if (collaborators[c].userID === user) { - const collaboratorAccessLevel = accessLevels.indexOf(collaborators[c].access.files); - if (minLevel > collaboratorAccessLevel) { + collaborators=projects[p].collaborators.list; + for(c in collaborators) { + if(collaborators[c].userID === user) { + var collaboratorAccessLevel = accessLevels.indexOf(collaborators[c].access.files); + if(minLevel > collaboratorAccessLevel) { minLevel = collaboratorAccessLevel; } } @@ -298,45 +283,44 @@ const minAccessToFileByProjects = function minAccessToFileByProjects(mri, projec var maxAccessToFileByProjects = function maxAccessToFileByProjects(mri, projects, user) { traceLog(maxAccessToFileByProjects); - let p, - maxLevel = 0; + var p, maxLevel = 0; - for (p in projects) { - if (projects[p].files.list.indexOf(mri.source) >= 0) { - // Check owner level - if (user === projects[p].owner) { - // Owner grants the maximum access level + for(p in projects) { + if(projects[p].files.list.indexOf(mri.source)>=0) { + + // check owner level + if(user === projects[p].owner) { + // owner grants the maximum access level // no need to look further - return 'remove'; + return "remove"; } - // Check public level + // check public level // verify that user 'anyone' exists and store their access rights - var i, anyone, - found = false; - for (i = 0; i < projects[p].collaborators.list.length; i++) { - if (projects[p].collaborators.list[i].userID === 'anyone') { - anyone = projects[p].collaborators.list[i]; + var i, anyone, found = false; + for(i=0;i collaboratorAccessLevel) { - if (debug > 1) { - console.log('WARNING: Collaborator access refused to project', project.shortname); - } + // check if user has collaborator access + //collaborators=project.files.access.whitelist; + collaborators=project.collaborators.list; + for(c in collaborators) { + if(collaborators[c].userID === user) { + var collaboratorAccessLevel = accessLevels.indexOf(collaborators[c].access.files); + if(requestedLevel>collaboratorAccessLevel) { + if(debug>1) console.log("WARNING: Collaborator access refused to project",project.shortname); return false; + } else { + console.log("access granted: collaborator"); + return true; } - console.log('access granted: collaborator'); - return true; } } - // Check if user has public access - if (requestedLevel > publicLevel) { - if (debug > 1) { - console.log('WARNING: Public access refused to project', project.shortname); - } + // check if user has public access + if(requestedLevel>publicLevel) { + if(debug>1) console.log("WARNING: Public access refused to project",project.shortname); return false; } - console.log('access granted: anyone'); + console.log("access granted: anyone"); return true; }; @@ -446,24 +424,22 @@ const toProject = function toProject(project, user, access) { * @func filterAnnotationsByProjects * @desc Set access to volume annotations */ -const filterAnnotationsByProjects = function filterAnnotationsByProjects(mri, projects, user) { - let i, j; - if (!mri.mri || !mri.mri.atlas) { +var filterAnnotationsByProjects = function filterAnnotationsByProjects(mri, projects, user) { + var i, j; + if(!mri.mri || !mri.mri.atlas) return; - } - if (!projects) { + if(!projects) return; - } - for (i = mri.mri.atlas.length - 1; i >= 0; i--) { - for (j = 0; j < projects.length; j++) { - if (projects[j] && projects[j].shortname == mri.mri.atlas[i].project) { - const access = toAnnotationByProject(projects[j], user); - const level = accessStringToLevel(access); - // Check for 'view' access (level > 0) - if (level > 0) { + for(i=mri.mri.atlas.length-1;i>=0;i--) { + for(j=0;j 0) + if(level > 0) { mri.mri.atlas[i].access = access; } else { - mri.mri.atlas.splice(i, 1); + mri.mri.atlas.splice(i,1); } break; } @@ -471,7 +447,8 @@ const filterAnnotationsByProjects = function filterAnnotationsByProjects(mri, pr } }; -const checkAccess = function () { + +var checkAccess = function () { this.accessStringToLevel = accessStringToLevel; this.accessLevelToString = accessLevelToString; this.toFileByAllProjects = toFileByAllProjects; @@ -483,4 +460,4 @@ const checkAccess = function () { this.filterAnnotationsByProjects = filterAnnotationsByProjects; }; -module.exports = new checkAccess(); +module.exports = new checkAccess(); \ No newline at end of file diff --git a/js/dataSlices.js b/js/dataSlices.js index b22d5966..991eb43a 100644 --- a/js/dataSlices.js +++ b/js/dataSlices.js @@ -1,7 +1,6 @@ console.log('dataSlices.js'); -const dateFormat = require('dateformat'); - -const checkAccess = require(__dirname + '/checkAccess.js'); +var dateFormat = require("dateformat"); +var checkAccess = require(__dirname+"/checkAccess.js"); /** * @func getUserFilesSlice @@ -10,63 +9,59 @@ const checkAccess = require(__dirname + '/checkAccess.js'); * @param {integer} start Start index of the file slice * @param {integer} length Number of files to include in the slice */ -const getUserFilesSlice = function getUserFilesSlice(req, requestedUser, start, length) { - console.log('getUserFilesSlice. Start, end:', start, length); - let loggedUser = 'anonymous'; - if (req.isAuthenticated()) { +var getUserFilesSlice = function getUserFilesSlice(req,requestedUser,start,length) { + console.log('getUserFilesSlice. Start, end:',start,length); + var loggedUser = "anonymous"; + if(req.isAuthenticated()) { loggedUser = req.user.username; } else - if (req.isTokenAuthenticated) { + if(req.isTokenAuthenticated) { loggedUser = req.tokenUsername; } - return new Promise((resolve, reject) => { + return new Promise(function (resolve, reject) { Promise.all([ req.db.get('mri') - .find({owner: requestedUser, backup: {$exists: false}}, {skip: start, limit: length}), + .find({owner: requestedUser, backup: {$exists: false}},{skip:start,limit:length}), req.db.get('project').find({ $or: [ {owner: requestedUser}, - {'collaborators.list': {$elemMatch: {userID: requestedUser}}} + {"collaborators.list": {$elemMatch:{userID:requestedUser}}} ], backup: {$exists: false} }) ]) - .then(values => { - let unfilteredMRI = values[0], + .then(function(values) { + var unfilteredMRI = values[0], unfilteredProjects = values[1], - mri = [], - mriFiles = []; - - // Filter for view access - for (i in unfilteredMRI) { - if (checkAccess.toFileByAllProjects(unfilteredMRI[i], unfilteredProjects, loggedUser, 'view')) { + mri = [], mriFiles = []; + + // filter for view access + for(i in unfilteredMRI) + if(checkAccess.toFileByAllProjects(unfilteredMRI[i],unfilteredProjects,loggedUser,"view")) mri.push(unfilteredMRI[i]); - } - } - - mri.map(o => { - const obj = { + + mri.map(function (o) { + var obj = { url: o.source, name: o.name, - included: dateFormat(o.included, 'd mmm yyyy, HH:MM') + included: dateFormat(o.included, "d mmm yyyy, HH:MM") }; - if (o.dim) { - obj.volDimensions = o.dim.join(' x '); + if(o.dim) { + obj.volDimensions = o.dim.join(" x "); mriFiles.push(obj); } }); - if (mri.length > 0) { - resolve({success: true, list: mriFiles}); - } else { - resolve({success: false, list: []}); - } + if(mri.length>0) + resolve({success:true, list:mriFiles}); + else + resolve({success:false, list:[]}); }) - .catch(err => { - console.log('ERROR:', err); + .catch(function(err) { + console.log("ERROR:",err); reject(); - }); + }) }); }; @@ -77,63 +72,59 @@ const getUserFilesSlice = function getUserFilesSlice(req, requestedUser, start, * @param {integer} start Start index of the file slice * @param {integer} length Number of files to include in the slice */ -const getUserAtlasSlice = function getUserAtlasSlice(req, requestedUser, start, length) { - let loggedUser = 'anonymous'; - if (req.isAuthenticated()) { +var getUserAtlasSlice = function getUserAtlasSlice(req,requestedUser,start,length) { + var loggedUser = "anonymous"; + if(req.isAuthenticated()) { loggedUser = req.user.username; } else - if (req.isTokenAuthenticated) { + if(req.isTokenAuthenticated) { loggedUser = req.tokenUsername; } - return new Promise((resolve, reject) => { + return new Promise(function (resolve, reject) { Promise.all([ req.db.get('mri') - .find({'mri.atlas': {$elemMatch: {owner: requestedUser}}, backup: {$exists: false}}, {skip: start, limit: length}), + .find({"mri.atlas": {$elemMatch: {owner: requestedUser}}, backup: {$exists: false}}, {skip:start,limit:length}), req.db.get('project').find({ $or: [ {owner: requestedUser}, - {'collaborators.list': {$elemMatch: {userID: requestedUser}}} + {"collaborators.list": {$elemMatch:{userID:requestedUser}}} ], backup: {$exists: false} }) ]) - .then(values => { - let unfilteredAtlas = values[0], + .then(function(values) { + var unfilteredAtlas = values[0], unfilteredProjects = values[1], - atlas = [], - atlasFiles = []; - - // Filter for view access - for (i in unfilteredAtlas) { - if (checkAccess.toFileByAllProjects(unfilteredAtlas[i], unfilteredProjects, loggedUser, 'view')) { + atlas = [], atlasFiles = []; + + // filter for view access + for(i in unfilteredAtlas) + if(checkAccess.toFileByAllProjects(unfilteredAtlas[i],unfilteredProjects,loggedUser,"view")) atlas.push(unfilteredAtlas[i]); - } - } - atlas.map(o => { - let i; + atlas.map(function (o) { + var i; for (i in o.mri.atlas) { atlasFiles.push({ url: o.source, parentName: o.name, - name: o.mri.atlas[i].name || '', - project: o.mri.atlas[i].project || '', - projectURL: '/project/' + o.mri.atlas[i].project || '', - modified: dateFormat(o.mri.atlas[i].modified, 'd mmm yyyy, HH:MM') + name: o.mri.atlas[i].name||"", + project: o.mri.atlas[i].project||"", + projectURL: '/project/'+o.mri.atlas[i].project||"", + modified: dateFormat(o.mri.atlas[i].modified, "d mmm yyyy, HH:MM") }); } }); - - if (atlas.length > 0) { - resolve({success: true, list: atlasFiles}); - } else { - resolve({success: false, list: []}); - } + + if(atlas.length>0) + resolve({success:true, list:atlasFiles}); + else + resolve({success:false, list:[]}); }) - .catch(err => { - console.log('ERROR:', err); + .catch(function(err) { + console.log("ERROR:",err); reject(); - }); + }) }); }; @@ -144,54 +135,49 @@ const getUserAtlasSlice = function getUserAtlasSlice(req, requestedUser, start, * @param {integer} start Start index of the file slice * @param {integer} length Number of files to include in the slice */ -const getUserProjectsSlice = function getUserProjectsSlice(req, requestedUser, start, length) { - let loggedUser = 'anonymous'; - if (req.isAuthenticated()) { +var getUserProjectsSlice = function getUserProjectsSlice(req,requestedUser,start,length) { + var loggedUser = "anonymous"; + if(req.isAuthenticated()) { loggedUser = req.user.username; } else - if (req.isTokenAuthenticated) { + if(req.isTokenAuthenticated) { loggedUser = req.tokenUsername; } - return new Promise((resolve, reject) => { + return new Promise(function (resolve, reject) { req.db.get('project').find({ $or: [ {owner: requestedUser}, - {'collaborators.list': {$elemMatch: {userID: requestedUser}}} + {"collaborators.list": {$elemMatch:{userID:requestedUser}}} ], backup: {$exists: false} - }, {skip: start, limit: length}) - .then(unfilteredProjects => { - let projects = []; - - // Filter for view access - for (i in unfilteredProjects) { - if (checkAccess.toProject(unfilteredProjects[i], loggedUser, 'view')) { + },{skip:start,limit:length}) + .then(function(unfilteredProjects) { + var projects = []; + + // filter for view access + for(i in unfilteredProjects) + if(checkAccess.toProject(unfilteredProjects[i],loggedUser,"view")) projects.push(unfilteredProjects[i]); - } - } - projects = projects.map(o => { - return { - project: o.shortname, - projectName: o.name, - projectURL: o.brainboxURL, - numFiles: o.files.list.length, - numCollaborators: o.collaborators.list.length, - owner: o.owner, - modified: dateFormat(o.modified, 'd mmm yyyy, HH:MM') - }; - }); - - if (projects.length > 0) { - resolve({success: true, list: projects}); - } else { - resolve({success: false, list: []}); - } + projects = projects.map(function (o) {return { + project: o.shortname, + projectName: o.name, + projectURL: o.brainboxURL, + numFiles: o.files.list.length, + numCollaborators: o.collaborators.list.length, + owner: o.owner, + modified: dateFormat(o.modified, "d mmm yyyy, HH:MM") + }; }); + + if(projects.length>0) + resolve({success:true, list:projects}); + else + resolve({success:false, list:[]}); }) - .catch(err => { - console.log('ERROR:', err); + .catch(function(err) { + console.log("ERROR:",err); reject(); - }); + }) }); }; @@ -203,75 +189,73 @@ const getUserProjectsSlice = function getUserProjectsSlice(req, requestedUser, s * @param {integer} length Number of files to include in the slice * @param {boolean} namesFlag Whether to append only the name of each MRI or the complete structure */ -const getProjectFilesSlice = function getProjectFilesSlice(req, projShortname, start, length, namesFlag) { - let loggedUser = 'anonymous'; - if (req.isAuthenticated()) { +var getProjectFilesSlice = function getProjectFilesSlice(req, projShortname, start, length, namesFlag) { + var loggedUser = "anonymous"; + if(req.isAuthenticated()) { loggedUser = req.user.username; } else - if (req.isTokenAuthenticated) { + if(req.isTokenAuthenticated) { loggedUser = req.tokenUsername; } + + return new Promise(function (resolve, reject) { + start = parseInt(start); + length = parseInt(length); + req.db.get('project') + .findOne({shortname:projShortname,backup:{$exists:0}},"-_id") + .then(function(project) { + // check access + if(checkAccess.toProject(project, loggedUser, "view") === false) { + var msg = "User "+loggedUser+" is not allowed to view project "+projShortname; + console.log("ERROR:",msg); + reject(msg); + return; + } - return new Promise((resolve, reject) => { - start = parseInt(start); - length = parseInt(length); - req.db.get('project') - .findOne({shortname: projShortname, backup: {$exists: 0}}, '-_id') - .then(project => { - // Check access - if (checkAccess.toProject(project, loggedUser, 'view') === false) { - const msg = 'User ' + loggedUser + ' is not allowed to view project ' + projShortname; - console.log('ERROR:', msg); - reject(msg); - return; - } - - if (project) { - let list = project.files.list, - newList = [], - arr = []; - let i; - start = Math.min(start, list.length); - length = Math.min(length, list.length - start); - for (i = start; i < start + length; i++) { - arr.push(req.db.get('mri').findOne({source: list[i], backup: {$exists: 0}}, {_id: 0})); - } - Promise.all(arr) - .then(mris => { - let j; - for (j = 0; j < mris.length; j++) { - if (mris[j]) { - // Check j-th mri annotation access - checkAccess.filterAnnotationsByProjects(mris[j], [project], loggedUser); - - // Append to list - if (namesFlag) { - newList[j] = {source: mris[j].source, name: mris[j].name}; - } else { - newList[j] = mris[j]; - } - } else { - newList[j] = { - source: list[start + j], - name: '' - }; - } - } - resolve(newList); -}) - .catch(err => { - console.log('ERROR:', err); - reject(); -}); - } else { - console.log('project is empty'); - } -}) - .catch(err => { - console.log('ERROR:', err); - reject(); -}); - }); + if (project) { + var list = project.files.list, newList = [], arr = []; + var i; + start = Math.min(start, list.length); + length = Math.min(length, list.length-start); + for(i=start;i { + return new Promise(function (resolve, reject) { Promise.all([ req.db.get('mri') - .find({backup: {$exists: false}}, {fields: {source: 1, _id: 0}}, {skip: start, limit: length}), + .find({backup: {$exists: false}},{fields:{source:1,_id:0}},{skip:start,limit:length}), req.db.get('project').find({backup: {$exists: false}}) ]) - .then(values => { - let unfilteredMRI = values[0], + .then(function(values) { + var unfilteredMRI = values[0], unfilteredProjects = values[1], - mri = [], - mriFiles = [], - i; + mri = [], mriFiles = [], i; - // Filter for view access - for (i = 0; i < unfilteredMRI.length; i++) { - if (checkAccess.toFileByAllProjects(unfilteredMRI[i], unfilteredProjects, loggedUser, 'view')) { + // filter for view access + for(i=0;i { + mri.map(function (o) { mriFiles.push(o.source); }); - // Constrain start and length to available data + // constrain start and length to available data start = Math.min(start, mriFiles.length); - length = Math.min(length, mriFiles.length - start); - mriFiles = mriFiles.slice(start, start + length); + length = Math.min(length, mriFiles.length-start); + mriFiles = mriFiles.slice(start,start+length); resolve(mriFiles); }) - .catch(err => { - console.log('ERROR:', err); + .catch(function(err) { + console.log("ERROR:",err); reject(); - }); + }) }); }; @@ -333,54 +313,50 @@ const getFilesSlice = function getFilesSlice(req, start, length) { * @param {integer} start Start index of the file slice * @param {integer} length Number of files to include in the slice */ -const getProjectsSlice = function getProjectsSlice(req, start, length) { - let loggedUser = 'anonymous'; - if (req.isAuthenticated()) { +var getProjectsSlice = function getProjectsSlice(req,start,length) { + var loggedUser = "anonymous"; + if(req.isAuthenticated()) { loggedUser = req.user.username; } else - if (req.isTokenAuthenticated) { + if(req.isTokenAuthenticated) { loggedUser = req.tokenUsername; } - return new Promise((resolve, reject) => { + return new Promise(function (resolve, reject) { req.db.get('project') - .find({backup: {$exists: false}}, {skip: start, limit: length}) - .then(unfilteredProjects => { - let projects = []; - - // Filter for view access - for (i in unfilteredProjects) { - if (checkAccess.toProject(unfilteredProjects[i], loggedUser, 'view')) { + .find({backup: {$exists: false}},{skip:start,limit:length}) + .then(function(unfilteredProjects) { + var projects = []; + + // filter for view access + for(i in unfilteredProjects) + if(checkAccess.toProject(unfilteredProjects[i],loggedUser,"view")) projects.push(unfilteredProjects[i]); - } - } - // Constrain start and length to available data + // constrain start and length to available data start = Math.min(start, projects.length); - length = Math.min(length, projects.length - start); - - projects = projects.slice(start, start + length); - - projects = projects.map(o => { - return { - project: o.shortname, - projectName: o.name, - numFiles: o.files.list.length, - numCollaborators: o.collaborators.list.length, - owner: o.owner - }; - }); - + length = Math.min(length, projects.length-start); + + projects = projects.slice(start,start+length); + + projects = projects.map(function (o) {return { + project: o.shortname, + projectName: o.name, + numFiles: o.files.list.length, + numCollaborators: o.collaborators.list.length, + owner: o.owner + }; }); + resolve(projects); }) - .catch(err => { - console.log('ERROR:', err); + .catch(function(err) { + console.log("ERROR:",err); reject(); - }); + }) }); }; -const dataSlices = function () { +var dataSlices = function () { this.getUserFilesSlice = getUserFilesSlice; this.getUserAtlasSlice = getUserAtlasSlice; this.getUserProjectsSlice = getUserProjectsSlice; diff --git a/js/mri-loader.js b/js/mri-loader.js index 12bdde6f..f437426e 100644 --- a/js/mri-loader.js +++ b/js/mri-loader.js @@ -1,176 +1,176 @@ -// const fs = require('fs'); -// const zlib = require('zlib'); -// const fileType = require('file-type'); +var fs = require('fs'); +var zlib = require('zlib'); +var fileType=require("file-type"); -const MRILoader = function () { +var MRILoader = function() { /* -Var loadNifti = function(nii) { - var vox_offset=352; - var sizeof_hdr=nii.readUInt32LE(0); - var dimensions=nii.readUInt16LE(40); - - var mri={}; - mri.hdr=nii.slice(0,vox_offset); - mri.dim=[]; - mri.dim[0]=nii.readUInt16LE(42); - mri.dim[1]=nii.readUInt16LE(44); - mri.dim[2]=nii.readUInt16LE(46); - mri.datatype=nii.readUInt16LE(70); - mri.pixdim=[]; - mri.pixdim[0]=nii.readFloatLE(80); - mri.pixdim[1]=nii.readFloatLE(84); - mri.pixdim[2]=nii.readFloatLE(88); - vox_offset=nii.readFloatLE(108); - - var tmp; - switch(mri.datatype) { - case 2: // UCHAR - mri.data=nii.slice(vox_offset); - break; - case 4: // SHORT - tmp=nii.slice(vox_offset); - mri.data=new Int16Array(mri.dim[0]*mri.dim[1]*mri.dim[2]); - for(var j=0;jmax) max=brain.data[i]; - } - brain.sum=sum; - brain.min=min; - brain.max=max; - - callback(brain); + var brain=loadNifti(nii); + + var i,sum=0,min,max; + min=brain.data[0]; + max=min; + for(i=0;imax) max=brain.data[i]; + } + brain.sum=sum; + brain.min=min; + brain.max=max; + + callback(brain); } var loadBrainMGZ = function(data,callback) { - var hdr_sz=284; - var brain={}; - var datatype; - - brain.dim=[]; - brain.dim[0]=data.readInt32BE(4); - brain.dim[1]=data.readInt32BE(8); - brain.dim[2]=data.readInt32BE(12); - datatype=data.readInt32BE(20); - brain.pixdim=[]; - brain.pixdim[0]=data.readFloatBE(30); - brain.pixdim[1]=data.readFloatBE(34); - brain.pixdim[2]=data.readFloatBE(38); - - var tmp - switch(datatype) { - case 0: // MGHUCHAR - brain.data=data.slice(hdr_sz); - break; - case 1: // MGHINT - tmp=data.slice(hdr_sz); - brain.data=new Uint32Array(brain.dim[0]*brain.dim[1]*brain.dim[2]); - for(var j=0;jmax) max=brain.data[i]; - } - brain.sum=sum; - brain.min=min; - brain.max=max; - callback(brain); + var hdr_sz=284; + var brain={}; + var datatype; + + brain.dim=[]; + brain.dim[0]=data.readInt32BE(4); + brain.dim[1]=data.readInt32BE(8); + brain.dim[2]=data.readInt32BE(12); + datatype=data.readInt32BE(20); + brain.pixdim=[]; + brain.pixdim[0]=data.readFloatBE(30); + brain.pixdim[1]=data.readFloatBE(34); + brain.pixdim[2]=data.readFloatBE(38); + + var tmp + switch(datatype) { + case 0: // MGHUCHAR + brain.data=data.slice(hdr_sz); + break; + case 1: // MGHINT + tmp=data.slice(hdr_sz); + brain.data=new Uint32Array(brain.dim[0]*brain.dim[1]*brain.dim[2]); + for(var j=0;jmax) max=brain.data[i]; + } + brain.sum=sum; + brain.min=min; + brain.max=max; + callback(brain); } this.loadBrain = function(path,callback) { - if(!fs.existsSync(path)) { - console.log("ERROR: File does not exist:",path); //modify call error object - return; - } else { - var datagz; - try { - datagz=fs.readFileSync(path); - var ft=fileType(datagz); - var ext=path.split('.').pop(); - - switch(ft.ext) { - case 'gz': { - switch(ext) { - case 'gz': - zlib.gunzip(datagz,function(err,nii){if(err) console.log("ERROR:",err);loadBrainNifti(nii,callback)}); //modify call error object - break; - case 'mgz': - zlib.gunzip(datagz,function(err,nii){if(err) console.log("ERROR:",err);loadBrainMGZ(nii,callback)}); //modify call error object - break; - } - break; - } - case 'zip': - zlib.inflate(datagz,function(err,nii){if(err) console.log("ERROR:",err);loadBrainNifti(nii,callback)}); //modify call error object - break; - default: - switch(ext) { - case 'nii': - loadBrainNifti(datagz,callback); - break; - case 'mgh': - loadBrainMGZ(datagz, callback); - break; - } - break; //modify call error object - } - } catch(e) { - console.log(new Date(),"ERROR: Cannot read brain data"); //modify call error object - } - } - return null; + if(!fs.existsSync(path)) { + console.log("ERROR: File does not exist:",path); //modify call error object + return; + } else { + var datagz; + try { + datagz=fs.readFileSync(path); + var ft=fileType(datagz); + var ext=path.split('.').pop(); + + switch(ft.ext) { + case 'gz': { + switch(ext) { + case 'gz': + zlib.gunzip(datagz,function(err,nii){if(err) console.log("ERROR:",err);loadBrainNifti(nii,callback)}); //modify call error object + break; + case 'mgz': + zlib.gunzip(datagz,function(err,nii){if(err) console.log("ERROR:",err);loadBrainMGZ(nii,callback)}); //modify call error object + break; + } + break; + } + case 'zip': + zlib.inflate(datagz,function(err,nii){if(err) console.log("ERROR:",err);loadBrainNifti(nii,callback)}); //modify call error object + break; + default: + switch(ext) { + case 'nii': + loadBrainNifti(datagz,callback); + break; + case 'mgh': + loadBrainMGZ(datagz, callback); + break; + } + break; //modify call error object + } + } catch(e) { + console.log(new Date(),"ERROR: Cannot read brain data"); //modify call error object + } + } + return null; } */ diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 588713b0..00000000 --- a/package-lock.json +++ /dev/null @@ -1,5741 +0,0 @@ -{ - "name": "brainbox", - "version": "0.1.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "abab": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.3.tgz", - "integrity": "sha1-uB3l9ydOxOdW15fNg08wNkJyTl0=" - }, - "accepts": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.1.4.tgz", - "integrity": "sha1-1xyW99QdD+2iw4zRToonwEFY30o=", - "requires": { - "mime-types": "2.0.14", - "negotiator": "0.4.9" - } - }, - "acorn": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" - }, - "acorn-globals": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", - "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=", - "requires": { - "acorn": "4.0.13" - } - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "3.3.0" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, - "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "dev": true, - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - }, - "ajv-keywords": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", - "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", - "dev": true - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "optional": true - }, - "ansi-align": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", - "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", - "dev": true, - "requires": { - "string-width": "2.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - } - } - }, - "ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "anymatch": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", - "dev": true, - "requires": { - "micromatch": "2.3.11", - "normalize-path": "2.1.1" - } - }, - "append-field": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-0.1.0.tgz", - "integrity": "sha1-bdxY+gg8e8VF08WZWygwzCNm1Eo=" - }, - "argparse": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", - "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", - "dev": true, - "requires": { - "sprintf-js": "1.0.3" - } - }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "1.1.0" - } - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", - "dev": true - }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" - }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "requires": { - "array-uniq": "1.0.3" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "async": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", - "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==", - "requires": { - "lodash": "4.17.4" - } - }, - "async-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", - "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "basic-auth": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.0.tgz", - "integrity": "sha1-ERstn/jk5tE2uMhOpeCWy4c1Fjc=" - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "binary-extensions": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.10.0.tgz", - "integrity": "sha1-muuabF6IY4qtFx4Wf1kAq+JINdA=", - "dev": true - }, - "bindings": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", - "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=", - "dev": true, - "optional": true - }, - "bluebird": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=" - }, - "body-parser": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.8.4.tgz", - "integrity": "sha1-1JfgS8E7P5qL2McLsM3Bby4CiJg=", - "requires": { - "bytes": "1.0.0", - "depd": "0.4.5", - "iconv-lite": "0.4.4", - "media-typer": "0.3.0", - "on-finished": "2.1.0", - "qs": "2.2.4", - "raw-body": "1.3.0", - "type-is": "1.5.7" - } - }, - "boom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", - "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", - "requires": { - "hoek": "4.2.0" - } - }, - "boxen": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.2.2.tgz", - "integrity": "sha1-Px1AMsMP/qnUsCwyLq8up0HcvOU=", - "dev": true, - "requires": { - "ansi-align": "2.0.0", - "camelcase": "4.1.0", - "chalk": "2.1.0", - "cli-boxes": "1.0.0", - "string-width": "2.1.1", - "term-size": "1.2.0", - "widest-line": "1.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.0" - } - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "chalk": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", - "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.4.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - }, - "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - } - } - }, - "brace-expansion": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", - "dev": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "1.8.2", - "preserve": "0.2.0", - "repeat-element": "1.1.2" - } - }, - "bson": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.0.4.tgz", - "integrity": "sha1-k8ENOeqltYQVy8QFLz5T5WKwtyw=" - }, - "buf-compare": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buf-compare/-/buf-compare-1.0.1.tgz", - "integrity": "sha1-/vKNqLgROgoNtEMLC2Rntpcws0o=", - "dev": true - }, - "buffer-shims": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", - "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=" - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" - }, - "busboy": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", - "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", - "requires": { - "dicer": "0.2.5", - "readable-stream": "1.1.14" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } - } - }, - "bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", - "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=" - }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "0.2.0" - } - }, - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - }, - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" - }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "requires": { - "camelcase": "2.1.1", - "map-obj": "1.0.1" - } - }, - "capture-stack-trace": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", - "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "chokidar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", - "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", - "dev": true, - "requires": { - "anymatch": "1.3.2", - "async-each": "1.0.1", - "fsevents": "1.1.2", - "glob-parent": "2.0.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "2.0.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.0" - } - }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, - "cli-boxes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", - "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", - "dev": true - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "1.0.1" - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "color-convert": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", - "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "combined-stream": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", - "requires": { - "delayed-stream": "1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.2.7", - "typedarray": "0.0.6" - } - }, - "configstore": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.1.tgz", - "integrity": "sha512-5oNkD/L++l0O6xGXxb1EWS7SivtjfGQlRyxJsYgE0Z495/L81e2h4/d3r969hoPXuFItzNOKMtsXgYG4c7dYvw==", - "dev": true, - "requires": { - "dot-prop": "4.2.0", - "graceful-fs": "4.1.11", - "make-dir": "1.0.0", - "unique-string": "1.0.0", - "write-file-atomic": "2.3.0", - "xdg-basedir": "3.0.0" - } - }, - "connect-livereload": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/connect-livereload/-/connect-livereload-0.5.4.tgz", - "integrity": "sha1-gBV9E3HJ83zBQDmrGJWXDRGdw7w=", - "dev": true - }, - "contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", - "dev": true - }, - "content-type-parser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-type-parser/-/content-type-parser-1.0.1.tgz", - "integrity": "sha1-w+VpiMU8ZRJ/tG1AMqOpACRv3JQ=" - }, - "cookie": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz", - "integrity": "sha1-5zSlwUF/zkctWu+Cw4HKu2TRpDU=" - }, - "cookie-parser": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.3.5.tgz", - "integrity": "sha1-nXVVcPtdF4kHcSJ6AjFNm+fPg1Y=", - "requires": { - "cookie": "0.1.3", - "cookie-signature": "1.0.6" - } - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "core-assert": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/core-assert/-/core-assert-0.2.1.tgz", - "integrity": "sha1-+F4s+b/tKPdzzIs/pcW2m9wC/j8=", - "dev": true, - "requires": { - "buf-compare": "1.0.1", - "is-error": "2.2.1" - } - }, - "core-js": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz", - "integrity": "sha1-rmh03GaTd4m4B1T/VCjfZoGcpQs=", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "crc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.0.0.tgz", - "integrity": "sha1-0R6X7ESoROXrFadPoseHXQqsSyI=" - }, - "create-error-class": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", - "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", - "dev": true, - "requires": { - "capture-stack-trace": "1.0.0" - } - }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "4.1.1", - "shebang-command": "1.2.0", - "which": "1.3.0" - }, - "dependencies": { - "lru-cache": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", - "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", - "dev": true, - "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" - } - } - } - }, - "cryptiles": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", - "requires": { - "boom": "5.2.0" - }, - "dependencies": { - "boom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", - "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", - "requires": { - "hoek": "4.2.0" - } - } - } - }, - "crypto": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/crypto/-/crypto-0.0.3.tgz", - "integrity": "sha1-RwqBuGvkxe4XrMggeh9TFa4g27A=" - }, - "crypto-random-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", - "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", - "dev": true - }, - "cssom": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz", - "integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=" - }, - "cssstyle": { - "version": "0.2.37", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz", - "integrity": "sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=", - "requires": { - "cssom": "0.3.2" - } - }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "requires": { - "array-find-index": "1.0.2" - } - }, - "d": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "dev": true, - "requires": { - "es5-ext": "0.10.35" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "1.0.0" - } - }, - "dateformat": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", - "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", - "requires": { - "get-stdin": "4.0.1", - "meow": "3.7.0" - } - }, - "debug": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.0.0.tgz", - "integrity": "sha1-ib2d9nMrUSVrxnBTQrugLtEhMe8=", - "requires": { - "ms": "0.6.2" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" - }, - "deep-assign": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-1.0.0.tgz", - "integrity": "sha1-sJJ0O+hCfcYh6gBnzex+cN0Z83s=", - "dev": true, - "requires": { - "is-obj": "1.0.1" - } - }, - "deep-extend": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", - "dev": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" - }, - "deep-strict-equal": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/deep-strict-equal/-/deep-strict-equal-0.2.0.tgz", - "integrity": "sha1-SgeBR6irV/ag1PVUckPNIvROtOQ=", - "dev": true, - "requires": { - "core-assert": "0.2.1" - } - }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "dev": true, - "requires": { - "globby": "5.0.0", - "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.0", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "rimraf": "2.6.2" - }, - "dependencies": { - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true, - "requires": { - "array-union": "1.0.2", - "arrify": "1.0.1", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - } - } - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "depd": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/depd/-/depd-0.4.5.tgz", - "integrity": "sha1-GmZLUziLSmVz6K5ntfdnxpPKl/E=" - }, - "destroy": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.3.tgz", - "integrity": "sha1-tDO0ck5x/YVR2YhRdIUcX8N34sk=" - }, - "detect-indent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", - "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=", - "dev": true - }, - "dicer": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", - "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", - "requires": { - "readable-stream": "1.1.14", - "streamsearch": "0.1.2" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } - } - }, - "doctrine": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", - "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", - "dev": true, - "requires": { - "esutils": "2.0.2", - "isarray": "1.0.0" - } - }, - "dompurify": { - "version": "0.8.9", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-0.8.9.tgz", - "integrity": "sha1-/OwCH5F7UfQqeK9x14dae6UU/WQ=" - }, - "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", - "dev": true, - "requires": { - "is-obj": "1.0.1" - } - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, - "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "ee-first": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.0.5.tgz", - "integrity": "sha1-jJshKJjYzZ8alDZlDOe+ICyen/A=" - }, - "enhance-visitors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/enhance-visitors/-/enhance-visitors-1.0.0.tgz", - "integrity": "sha1-qpRdBdpGVnKh69OP7i7T2oUY6Vo=", - "dev": true, - "requires": { - "lodash": "4.17.4" - } - }, - "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "requires": { - "is-arrayish": "0.2.1" - } - }, - "es5-ext": { - "version": "0.10.35", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.35.tgz", - "integrity": "sha1-GO6FjOajxFx9eekcFfzKnsVoSU8=", - "dev": true, - "requires": { - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.35", - "es6-symbol": "3.1.1" - } - }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.35", - "es6-iterator": "2.0.3", - "es6-set": "0.1.5", - "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" - } - }, - "es6-promise": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", - "integrity": "sha1-7FYjOGgDKQkgcXDDlEjiREndH8Q=" - }, - "es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.35", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" - } - }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.35" - } - }, - "es6-weak-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", - "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.35", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" - } - }, - "escape-html": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.1.tgz", - "integrity": "sha1-GBoobq05ejmpKFfPsdQwUuNWv/A=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", - "requires": { - "esprima": "2.7.3", - "estraverse": "1.9.3", - "esutils": "2.0.2", - "optionator": "0.8.2", - "source-map": "0.2.0" - } - }, - "escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", - "dev": true, - "requires": { - "es6-map": "0.1.5", - "es6-weak-map": "2.0.2", - "esrecurse": "4.2.0", - "estraverse": "4.2.0" - }, - "dependencies": { - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - } - } - }, - "eslint": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", - "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", - "dev": true, - "requires": { - "babel-code-frame": "6.26.0", - "chalk": "1.1.3", - "concat-stream": "1.6.0", - "debug": "2.6.9", - "doctrine": "2.0.0", - "escope": "3.6.0", - "espree": "3.5.1", - "esquery": "1.0.0", - "estraverse": "4.2.0", - "esutils": "2.0.2", - "file-entry-cache": "2.0.0", - "glob": "7.1.2", - "globals": "9.18.0", - "ignore": "3.3.5", - "imurmurhash": "0.1.4", - "inquirer": "0.12.0", - "is-my-json-valid": "2.16.1", - "is-resolvable": "1.0.0", - "js-yaml": "3.10.0", - "json-stable-stringify": "1.0.1", - "levn": "0.3.0", - "lodash": "4.17.4", - "mkdirp": "0.5.1", - "natural-compare": "1.4.0", - "optionator": "0.8.2", - "path-is-inside": "1.0.2", - "pluralize": "1.2.1", - "progress": "1.1.8", - "require-uncached": "1.0.3", - "shelljs": "0.7.8", - "strip-bom": "3.0.0", - "strip-json-comments": "2.0.1", - "table": "3.8.3", - "text-table": "0.2.0", - "user-home": "2.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } - } - }, - "eslint-config-xo": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/eslint-config-xo/-/eslint-config-xo-0.18.2.tgz", - "integrity": "sha1-ChVxIIdWGZKec1/9axhcQeihh68=", - "dev": true - }, - "eslint-formatter-pretty": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-formatter-pretty/-/eslint-formatter-pretty-1.3.0.tgz", - "integrity": "sha512-5DY64Y1rYCm7cfFDHEGUn54bvCnK+wSUVF07N8oXeqUJFSd+gnYOTXbzelQ1HurESluY6gnEQPmXOIkB4Wa+gA==", - "dev": true, - "requires": { - "ansi-escapes": "2.0.0", - "chalk": "2.1.0", - "log-symbols": "2.1.0", - "plur": "2.1.2", - "string-width": "2.1.1" - }, - "dependencies": { - "ansi-escapes": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-2.0.0.tgz", - "integrity": "sha1-W65SvkJIeN2Xg+iRDj/Cki6DyBs=", - "dev": true - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.0" - } - }, - "chalk": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", - "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.4.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - }, - "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - } - } - }, - "eslint-import-resolver-node": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz", - "integrity": "sha512-yUtXS15gIcij68NmXmP9Ni77AQuCN0itXbCc/jWd8C6/yKZaSNXicpC8cgvjnxVdmfsosIXrjpzFq7GcDryb6A==", - "dev": true, - "requires": { - "debug": "2.6.9", - "resolve": "1.4.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-module-utils": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz", - "integrity": "sha512-jDI/X5l/6D1rRD/3T43q8Qgbls2nq5km5KSqiwlyUbGo5+04fXhMKdCPhjwbqAa6HXWaMxj8Q4hQDIh7IadJQw==", - "dev": true, - "requires": { - "debug": "2.6.9", - "pkg-dir": "1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-plugin-ava": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-ava/-/eslint-plugin-ava-4.2.2.tgz", - "integrity": "sha512-a4QDn9dyiFuwtQSQMDLDyklpf3/uQ7eT3+fVs0U/7cFPQF8IvhK3HpFCTd5iDGC7hljMDU9PFIUP+3Se4LV7fg==", - "dev": true, - "requires": { - "arrify": "1.0.1", - "deep-strict-equal": "0.2.0", - "enhance-visitors": "1.0.0", - "espree": "3.5.1", - "espurify": "1.7.0", - "import-modules": "1.1.0", - "multimatch": "2.1.0", - "pkg-up": "2.0.0" - } - }, - "eslint-plugin-import": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.7.0.tgz", - "integrity": "sha512-HGYmpU9f/zJaQiKNQOVfHUh2oLWW3STBrCgH0sHTX1xtsxYlH1zjLh8FlQGEIdZSdTbUMaV36WaZ6ImXkenGxQ==", - "dev": true, - "requires": { - "builtin-modules": "1.1.1", - "contains-path": "0.1.0", - "debug": "2.6.9", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "0.3.1", - "eslint-module-utils": "2.1.1", - "has": "1.0.1", - "lodash.cond": "4.5.2", - "minimatch": "3.0.4", - "read-pkg-up": "2.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "dev": true, - "requires": { - "esutils": "2.0.2", - "isarray": "1.0.0" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "2.0.0" - } - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "strip-bom": "3.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "2.3.0" - } - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "2.0.0", - "normalize-package-data": "2.4.0", - "path-type": "2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "2.1.0", - "read-pkg": "2.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } - } - }, - "eslint-plugin-no-use-extend-native": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/eslint-plugin-no-use-extend-native/-/eslint-plugin-no-use-extend-native-0.3.12.tgz", - "integrity": "sha1-OtmgDC3yO11/f2vpFVCYWkq3Aeo=", - "dev": true, - "requires": { - "is-get-set-prop": "1.0.0", - "is-js-type": "2.0.0", - "is-obj-prop": "1.0.0", - "is-proto-prop": "1.0.0" - } - }, - "eslint-plugin-promise": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.6.0.tgz", - "integrity": "sha512-YQzM6TLTlApAr7Li8vWKR+K3WghjwKcYzY0d2roWap4SLK+kzuagJX/leTetIDWsFcTFnKNJXWupDCD6aZkP2Q==", - "dev": true - }, - "eslint-plugin-unicorn": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-2.1.2.tgz", - "integrity": "sha1-md/+n0dzsEvDk1an/r1k3XACdLw=", - "dev": true, - "requires": { - "import-modules": "1.1.0", - "lodash.camelcase": "4.3.0", - "lodash.kebabcase": "4.1.1", - "lodash.snakecase": "4.1.1", - "lodash.upperfirst": "4.3.1" - } - }, - "espree": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.1.tgz", - "integrity": "sha1-DJiLirRttTEAoZVK5LqZXd0n2H4=", - "dev": true, - "requires": { - "acorn": "5.1.2", - "acorn-jsx": "3.0.1" - }, - "dependencies": { - "acorn": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.2.tgz", - "integrity": "sha512-o96FZLJBPY1lvTuJylGA9Bk3t/GKPPJG8H0ydQQl01crzwJgspa4AEIq/pVTXigmK0PHVQhiAtn8WMBLL9D2WA==", - "dev": true - } - } - }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" - }, - "espurify": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/espurify/-/espurify-1.7.0.tgz", - "integrity": "sha1-HFz2y8zDLm9jk4C9T5kfq5up0iY=", - "dev": true, - "requires": { - "core-js": "2.5.1" - } - }, - "esquery": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", - "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", - "dev": true, - "requires": { - "estraverse": "4.2.0" - }, - "dependencies": { - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", - "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", - "dev": true, - "requires": { - "estraverse": "4.2.0", - "object-assign": "4.1.1" - }, - "dependencies": { - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - } - } - }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=" - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" - }, - "etag": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.4.0.tgz", - "integrity": "sha1-MFCZFhWFdwfAQRnQdbogiOBwEiU=", - "requires": { - "crc": "3.0.0" - } - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.35" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "cross-spawn": "5.1.0", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" - } - }, - "exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", - "dev": true - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "0.1.1" - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, - "requires": { - "fill-range": "2.2.3" - } - }, - "express": { - "version": "4.9.8", - "resolved": "https://registry.npmjs.org/express/-/express-4.9.8.tgz", - "integrity": "sha1-82D1lrrqu9DlIjtgPW61eNnS0Q0=", - "requires": { - "accepts": "1.1.4", - "cookie": "0.1.2", - "cookie-signature": "1.0.5", - "debug": "2.0.0", - "depd": "0.4.5", - "escape-html": "1.0.1", - "etag": "1.4.0", - "finalhandler": "0.2.0", - "fresh": "0.2.4", - "media-typer": "0.3.0", - "merge-descriptors": "0.0.2", - "methods": "1.1.0", - "on-finished": "2.1.0", - "parseurl": "1.3.1", - "path-to-regexp": "0.1.3", - "proxy-addr": "1.0.10", - "qs": "2.2.4", - "range-parser": "1.0.3", - "send": "0.9.3", - "serve-static": "1.6.5", - "type-is": "1.5.7", - "utils-merge": "1.0.0", - "vary": "1.0.1" - }, - "dependencies": { - "cookie": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.2.tgz", - "integrity": "sha1-cv7D0k5Io0Mgc9kMEmQgBQYQBLE=" - }, - "cookie-signature": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.5.tgz", - "integrity": "sha1-oSLj8VA+yg9TVXlbBxG7I2jUUPk=" - } - } - }, - "express-session": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.15.6.tgz", - "integrity": "sha512-r0nrHTCYtAMrFwZ0kBzZEXa1vtPVrw0dKvGSrKP4dahwBQ1BJpF2/y1Pp4sCD/0kvxV4zZeclyvfmw0B4RMJQA==", - "requires": { - "cookie": "0.3.1", - "cookie-signature": "1.0.6", - "crc": "3.4.4", - "debug": "2.6.9", - "depd": "1.1.1", - "on-headers": "1.0.1", - "parseurl": "1.3.2", - "uid-safe": "2.1.5", - "utils-merge": "1.0.1" - }, - "dependencies": { - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - }, - "crc": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.4.4.tgz", - "integrity": "sha1-naHpgOO9RPxck79as9ozeNheRms=" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - } - } - }, - "express-validator": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-2.21.0.tgz", - "integrity": "sha1-9fwvn6npqFeGNPEOhrpaQ0a5b08=", - "requires": { - "bluebird": "3.4.7", - "lodash": "4.16.6", - "validator": "5.7.0" - }, - "dependencies": { - "lodash": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.6.tgz", - "integrity": "sha1-0iyaxmAojzhD4Wun0rXQbMon13c=" - }, - "validator": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-5.7.0.tgz", - "integrity": "sha1-eoelgUa2laxIYHEUHAxJ1n2gXlw=" - } - } - }, - "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" - }, - "fast-json-patch": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-1.2.2.tgz", - "integrity": "sha1-03fZfGkR290qHIC/rNoEik+Du/k=" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "1.0.5", - "object-assign": "4.1.1" - } - }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "dev": true, - "requires": { - "flat-cache": "1.3.0", - "object-assign": "4.1.1" - } - }, - "file-type": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", - "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=" - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true - }, - "fill-range": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", - "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", - "dev": true, - "requires": { - "is-number": "2.1.0", - "isobject": "2.1.0", - "randomatic": "1.1.7", - "repeat-element": "1.1.2", - "repeat-string": "1.6.1" - } - }, - "finalhandler": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.2.0.tgz", - "integrity": "sha1-eUCCQksX9qSyoO2jn522lI7kvo0=", - "requires": { - "debug": "2.0.0", - "escape-html": "1.0.1" - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" - } - }, - "flat-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", - "dev": true, - "requires": { - "circular-json": "0.3.3", - "del": "2.2.2", - "graceful-fs": "4.1.11", - "write": "0.2.1" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "1.0.2" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", - "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.17" - }, - "dependencies": { - "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" - }, - "mime-types": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", - "requires": { - "mime-db": "1.30.0" - } - } - } - }, - "forwarded": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", - "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=" - }, - "fresh": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.2.4.tgz", - "integrity": "sha1-NYJJkgbJcjcUGQ7ddLRgT+tKYUw=" - }, - "fs": { - "version": "0.0.1-security", - "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", - "integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ=" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz", - "integrity": "sha512-Sn44E5wQW4bTHXvQmvSHwqbuiXtduD6Rrjm2ZtUEGbyrig+nUH3t/QD4M4/ZXViY556TBpRgZkHLDx3JxPwxiw==", - "dev": true, - "optional": true, - "requires": { - "nan": "2.7.0", - "node-pre-gyp": "0.6.36" - }, - "dependencies": { - "abbrev": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "ajv": { - "version": "4.11.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "aproba": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.2.9" - } - }, - "asn1": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "assert-plus": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "asynckit": { - "version": "0.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws-sign2": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws4": { - "version": "1.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "balanced-match": { - "version": "0.4.2", - "bundled": true, - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "block-stream": { - "version": "0.0.9", - "bundled": true, - "dev": true, - "requires": { - "inherits": "2.0.3" - } - }, - "boom": { - "version": "2.10.1", - "bundled": true, - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, - "brace-expansion": { - "version": "1.1.7", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "0.4.2", - "concat-map": "0.0.1" - } - }, - "buffer-shims": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "caseless": { - "version": "0.12.0", - "bundled": true, - "dev": true, - "optional": true - }, - "co": { - "version": "4.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "combined-stream": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "requires": { - "delayed-stream": "1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "cryptiles": { - "version": "2.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "boom": "2.10.1" - } - }, - "dashdash": { - "version": "1.14.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "debug": { - "version": "2.6.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.4.2", - "bundled": true, - "dev": true, - "optional": true - }, - "delayed-stream": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "ecc-jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "extend": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "extsprintf": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "bundled": true, - "dev": true, - "optional": true - }, - "form-data": { - "version": "2.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.15" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "fstream": { - "version": "1.0.11", - "bundled": true, - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.1" - } - }, - "fstream-ignore": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fstream": "1.0.11", - "inherits": "2.0.3", - "minimatch": "3.0.4" - } - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "1.1.1", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" - } - }, - "getpass": { - "version": "0.1.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "graceful-fs": { - "version": "4.1.11", - "bundled": true, - "dev": true - }, - "har-schema": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "har-validator": { - "version": "4.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "hawk": { - "version": "3.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, - "hoek": { - "version": "2.16.3", - "bundled": true, - "dev": true - }, - "http-signature": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.0", - "sshpk": "1.13.0" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true - }, - "ini": { - "version": "1.3.4", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "isstream": { - "version": "0.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "jodid25519": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsonify": "0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "jsonify": { - "version": "0.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "jsprim": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.0.2", - "json-schema": "0.2.3", - "verror": "1.3.6" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "mime-db": { - "version": "1.27.0", - "bundled": true, - "dev": true - }, - "mime-types": { - "version": "2.1.15", - "bundled": true, - "dev": true, - "requires": { - "mime-db": "1.27.0" - } - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "node-pre-gyp": { - "version": "0.6.36", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "mkdirp": "0.5.1", - "nopt": "4.0.1", - "npmlog": "4.1.0", - "rc": "1.2.1", - "request": "2.81.0", - "rimraf": "2.6.1", - "semver": "5.3.0", - "tar": "2.2.1", - "tar-pack": "3.4.0" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1.1.0", - "osenv": "0.1.4" - } - }, - "npmlog": { - "version": "4.1.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "oauth-sign": { - "version": "0.8.2", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "performance-now": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "1.0.7", - "bundled": true, - "dev": true - }, - "punycode": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true - }, - "qs": { - "version": "6.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.4", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.2.9", - "bundled": true, - "dev": true, - "requires": { - "buffer-shims": "1.0.0", - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "1.0.1", - "util-deprecate": "1.0.2" - } - }, - "request": { - "version": "2.81.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.15", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.0.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.2", - "tunnel-agent": "0.6.0", - "uuid": "3.0.1" - } - }, - "rimraf": { - "version": "2.6.1", - "bundled": true, - "dev": true, - "requires": { - "glob": "7.1.2" - } - }, - "safe-buffer": { - "version": "5.0.1", - "bundled": true, - "dev": true - }, - "semver": { - "version": "5.3.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sntp": { - "version": "1.0.9", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "hoek": "2.16.3" - } - }, - "sshpk": { - "version": "1.13.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jodid25519": "1.0.2", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, - "stringstream": { - "version": "0.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "2.2.1", - "bundled": true, - "dev": true, - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" - } - }, - "tar-pack": { - "version": "3.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "2.6.8", - "fstream": "1.0.11", - "fstream-ignore": "1.0.5", - "once": "1.4.0", - "readable-stream": "2.2.9", - "rimraf": "2.6.1", - "tar": "2.2.1", - "uid-number": "0.0.6" - } - }, - "tough-cookie": { - "version": "2.3.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "punycode": "1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "bundled": true, - "dev": true, - "optional": true - }, - "uid-number": { - "version": "0.0.6", - "bundled": true, - "dev": true, - "optional": true - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "uuid": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "verror": { - "version": "1.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "extsprintf": "1.0.2" - } - }, - "wide-align": { - "version": "1.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true - } - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "generate-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", - "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", - "dev": true - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "dev": true, - "requires": { - "is-property": "1.0.2" - } - }, - "get-set-props": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-set-props/-/get-set-props-0.1.0.tgz", - "integrity": "sha1-mYR1wXhEVobQsyJG2l3428++jqM=", - "dev": true - }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "1.0.0" - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "2.0.0", - "is-glob": "2.0.1" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "2.0.1" - } - }, - "global-dirs": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.0.tgz", - "integrity": "sha1-ENNAOeDfBCcuJizyQiT3IJQ0308=", - "dev": true, - "requires": { - "ini": "1.3.4" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "1.0.2", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - } - }, - "got": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", - "dev": true, - "requires": { - "create-error-class": "3.0.2", - "duplexer3": "0.1.4", - "get-stream": "3.0.0", - "is-redirect": "1.0.0", - "is-retry-allowed": "1.1.0", - "is-stream": "1.1.0", - "lowercase-keys": "1.0.0", - "safe-buffer": "5.1.1", - "timed-out": "4.0.1", - "unzip-response": "2.0.1", - "url-parse-lax": "1.0.0" - } - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", - "requires": { - "ajv": "5.2.3", - "har-schema": "2.0.0" - }, - "dependencies": { - "ajv": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.3.tgz", - "integrity": "sha1-wG9Zh3jETGsWGrr+NGa4GtGBTtI=", - "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.0.0", - "json-schema-traverse": "0.3.1", - "json-stable-stringify": "1.0.1" - } - } - } - }, - "has": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", - "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", - "dev": true, - "requires": { - "function-bind": "1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "hawk": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", - "requires": { - "boom": "4.3.1", - "cryptiles": "3.1.2", - "hoek": "4.2.0", - "sntp": "2.0.2" - } - }, - "hoek": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", - "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" - }, - "hosted-git-info": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", - "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==" - }, - "html-encoding-sniffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.1.tgz", - "integrity": "sha1-eb96eF6klf5mFl5zQVPzY/9UN9o=", - "requires": { - "whatwg-encoding": "1.0.1" - } - }, - "http": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/http/-/http-0.0.0.tgz", - "integrity": "sha1-huYybSnF0Dnen6xYSkVon5KfT3I=" - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.13.1" - } - }, - "iconv-lite": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.4.tgz", - "integrity": "sha1-6V8uQdsHNfwhZS94J6XuMuY8g6g=" - }, - "ignore": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.5.tgz", - "integrity": "sha512-JLH93mL8amZQhh/p6mfQgVBH3M6epNq3DfsXsTSuSrInVjwyYlFE1nv2AgfRCC8PoOhM0jwQ5v8s9LgbK7yGDw==", - "dev": true - }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true - }, - "import-modules": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/import-modules/-/import-modules-1.1.0.tgz", - "integrity": "sha1-dI23nFzEK7lwHvq0JPiU5yYA6dw=", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "requires": { - "repeating": "2.0.1" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ini": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", - "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", - "dev": true - }, - "inquirer": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", - "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", - "dev": true, - "requires": { - "ansi-escapes": "1.4.0", - "ansi-regex": "2.1.1", - "chalk": "1.1.3", - "cli-cursor": "1.0.2", - "cli-width": "2.2.0", - "figures": "1.7.0", - "lodash": "4.17.4", - "readline2": "1.0.1", - "run-async": "0.1.0", - "rx-lite": "3.1.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "through": "2.3.8" - } - }, - "interpret": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.4.tgz", - "integrity": "sha1-ggzdWIuGj/sZGoCVBtbJyPISsbA=", - "dev": true - }, - "ipaddr.js": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.0.5.tgz", - "integrity": "sha1-X6eM8wG4JceKvDBC2BJyMEnqI8c=" - }, - "irregular-plurals": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-1.4.0.tgz", - "integrity": "sha1-LKmwM2UREYVUEvFr5dd8YqRYp2Y=", - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "requires": { - "binary-extensions": "1.10.0" - } - }, - "is-buffer": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", - "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", - "dev": true - }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "requires": { - "builtin-modules": "1.1.1" - } - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "requires": { - "is-primitive": "2.0.0" - } - }, - "is-error": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-error/-/is-error-2.2.1.tgz", - "integrity": "sha1-aEqW2EB2V3yY9M20DG0mpRI78Zw=", - "dev": true - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-get-set-prop": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-get-set-prop/-/is-get-set-prop-1.0.0.tgz", - "integrity": "sha1-JzGHfk14pqae3M5rudaLB3nnYxI=", - "dev": true, - "requires": { - "get-set-props": "0.1.0", - "lowercase-keys": "1.0.0" - } - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - }, - "is-installed-globally": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", - "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", - "dev": true, - "requires": { - "global-dirs": "0.1.0", - "is-path-inside": "1.0.0" - } - }, - "is-js-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-js-type/-/is-js-type-2.0.0.tgz", - "integrity": "sha1-c2FwBtZZtOtHKbunR9KHgt8PfiI=", - "dev": true, - "requires": { - "js-types": "1.0.0" - } - }, - "is-my-json-valid": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz", - "integrity": "sha512-ochPsqWS1WXj8ZnMIV0vnNXooaMhp7cyL4FMSIPKTtnV0Ha/T19G2b9kkhcNsabV9bxYkze7/aLZJb/bYuFduQ==", - "dev": true, - "requires": { - "generate-function": "2.0.0", - "generate-object-property": "1.2.0", - "jsonpointer": "4.0.1", - "xtend": "4.0.1" - } - }, - "is-npm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", - "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", - "dev": true - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - }, - "is-obj-prop": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-obj-prop/-/is-obj-prop-1.0.0.tgz", - "integrity": "sha1-s03nnEULjXxzqyzfZ9yHWtuF+A4=", - "dev": true, - "requires": { - "lowercase-keys": "1.0.0", - "obj-props": "1.1.0" - } - }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", - "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", - "dev": true, - "requires": { - "is-path-inside": "1.0.0" - } - }, - "is-path-inside": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", - "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", - "dev": true, - "requires": { - "path-is-inside": "1.0.2" - } - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "dev": true - }, - "is-proto-prop": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-proto-prop/-/is-proto-prop-1.0.0.tgz", - "integrity": "sha1-s5UflcCJkk+11PzaZUKrPoPisiA=", - "dev": true, - "requires": { - "lowercase-keys": "1.0.0", - "proto-props": "0.2.1" - } - }, - "is-redirect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", - "dev": true - }, - "is-resolvable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", - "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", - "dev": true, - "requires": { - "tryit": "1.0.3" - } - }, - "is-retry-allowed": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", - "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", - "dev": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "jpeg-js": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.2.0.tgz", - "integrity": "sha1-U+RI7J0mPmgyZkZ+lELSxaLvVII=" - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-types": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/js-types/-/js-types-1.0.0.tgz", - "integrity": "sha1-0kLmSU7Vcq08koCfyL7X92h8vwM=", - "dev": true - }, - "js-yaml": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", - "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", - "dev": true, - "requires": { - "argparse": "1.0.9", - "esprima": "4.0.0" - }, - "dependencies": { - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", - "dev": true - } - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true - }, - "jsdom": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-9.12.0.tgz", - "integrity": "sha1-6MVG//ywbADUgzyoRBD+1/igl9Q=", - "requires": { - "abab": "1.0.3", - "acorn": "4.0.13", - "acorn-globals": "3.1.0", - "array-equal": "1.0.0", - "content-type-parser": "1.0.1", - "cssom": "0.3.2", - "cssstyle": "0.2.37", - "escodegen": "1.8.1", - "html-encoding-sniffer": "1.0.1", - "nwmatcher": "1.4.1", - "parse5": "1.5.1", - "request": "2.83.0", - "sax": "1.2.4", - "symbol-tree": "3.2.2", - "tough-cookie": "2.3.2", - "webidl-conversions": "4.0.2", - "whatwg-encoding": "1.0.1", - "whatwg-url": "4.8.0", - "xml-name-validator": "2.0.1" - } - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "requires": { - "jsonify": "0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" - }, - "jsonpointer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", - "dev": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "keypress": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/keypress/-/keypress-0.2.1.tgz", - "integrity": "sha1-HoBFQlABjbrUw/6USX1uZ7YmnHc=" - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.5" - } - }, - "latest-version": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", - "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", - "dev": true, - "requires": { - "package-json": "4.0.1" - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "requires": { - "prelude-ls": "1.1.2", - "type-check": "0.3.2" - } - }, - "livereload": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/livereload/-/livereload-0.4.1.tgz", - "integrity": "sha1-xU4WTDXRW62822uDBB6xC4/ey5c=", - "dev": true, - "requires": { - "chokidar": "1.7.0", - "opts": "1.2.6", - "ws": "0.8.1" - }, - "dependencies": { - "ws": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-0.8.1.tgz", - "integrity": "sha1-a2UnO5kZPF8Gekz1gJWY93fjt1k=", - "dev": true, - "requires": { - "bufferutil": "1.2.1", - "options": "0.0.6", - "ultron": "1.0.2", - "utf-8-validate": "1.2.2" - }, - "dependencies": { - "bufferutil": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-1.2.1.tgz", - "integrity": "sha1-N75dNuHgZJIiHmjUdLGsWOUQy9c=", - "dev": true, - "optional": true, - "requires": { - "bindings": "1.2.1", - "nan": "2.7.0" - } - }, - "utf-8-validate": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-1.2.2.tgz", - "integrity": "sha1-i7hxpHQeCFxwSHynrNvX1tNgKes=", - "dev": true, - "optional": true, - "requires": { - "bindings": "1.2.1", - "nan": "2.4.0" - }, - "dependencies": { - "nan": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.4.0.tgz", - "integrity": "sha1-+zxZ1F/k7/4hXwuJD4rfbrMtIjI=", - "dev": true, - "optional": true - } - } - } - } - } - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" - }, - "dependencies": { - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } - } - }, - "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" - }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", - "dev": true - }, - "lodash.cond": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz", - "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=", - "dev": true - }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", - "dev": true - }, - "lodash.kebabcase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", - "integrity": "sha1-hImxyw0p/4gZXM7KRI/21swpXDY=", - "dev": true - }, - "lodash.snakecase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", - "integrity": "sha1-OdcUo1NXFHg3rv1ktdy7Fr7Nj40=", - "dev": true - }, - "lodash.upperfirst": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", - "integrity": "sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984=", - "dev": true - }, - "log-symbols": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.1.0.tgz", - "integrity": "sha512-zLeLrzMA1A2vRF1e/0Mo+LNINzi6jzBylHj5WqvQ/WK/5WCZt8si9SyN4p9llr/HRYvVR1AoXHRHl4WTHyQAzQ==", - "dev": true, - "requires": { - "chalk": "2.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.0" - } - }, - "chalk": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", - "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.4.0" - } - }, - "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - } - } - }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "requires": { - "currently-unhandled": "0.4.1", - "signal-exit": "3.0.2" - } - }, - "lowercase-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", - "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", - "dev": true - }, - "lru-cache": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.2.tgz", - "integrity": "sha1-H92tk4quEmPOE4aAvhs/WRwKtBw=" - }, - "make-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.0.0.tgz", - "integrity": "sha1-l6ARdR6R3YfPre9Ygy67BJNt6Xg=", - "dev": true, - "requires": { - "pify": "2.3.0" - } - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "requires": { - "camelcase-keys": "2.1.0", - "decamelize": "1.2.0", - "loud-rejection": "1.6.0", - "map-obj": "1.0.1", - "minimist": "1.2.0", - "normalize-package-data": "2.4.0", - "object-assign": "4.1.1", - "read-pkg-up": "1.0.1", - "redent": "1.0.0", - "trim-newlines": "1.0.0" - } - }, - "merge": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz", - "integrity": "sha1-dTHjnUlJwoGma4xabgJl6LBYlNo=" - }, - "merge-descriptors": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-0.0.2.tgz", - "integrity": "sha1-w2pSp4FDdRPFcnXzndnTF1FKyMc=" - }, - "methods": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.0.tgz", - "integrity": "sha1-XcpO4S31L/OwVhRZhqjwHLyGQ28=" - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "2.0.0", - "array-unique": "0.2.1", - "braces": "1.8.5", - "expand-brackets": "0.1.5", - "extglob": "0.3.2", - "filename-regex": "2.0.1", - "is-extglob": "1.0.0", - "is-glob": "2.0.1", - "kind-of": "3.2.2", - "normalize-path": "2.1.1", - "object.omit": "2.0.1", - "parse-glob": "3.0.4", - "regex-cache": "0.4.4" - } - }, - "mime": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", - "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=" - }, - "mime-db": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.12.0.tgz", - "integrity": "sha1-PQxjGA9FjrENMlqqN9fFiuMS6dc=" - }, - "mime-types": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.14.tgz", - "integrity": "sha1-MQ4VnbI+B3+Lsit0jav6SVcUCqY=", - "requires": { - "mime-db": "1.12.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "1.1.8" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - } - } - }, - "mongodb": { - "version": "2.2.33", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.33.tgz", - "integrity": "sha1-tTfEcdNKZlG0jzb9vyl1A0Dgi1A=", - "requires": { - "es6-promise": "3.2.1", - "mongodb-core": "2.1.17", - "readable-stream": "2.2.7" - } - }, - "mongodb-core": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.17.tgz", - "integrity": "sha1-pBizN6FKFJkPtRC5I97mqBMXPfg=", - "requires": { - "bson": "1.0.4", - "require_optional": "1.0.1" - } - }, - "monk": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/monk/-/monk-3.1.4.tgz", - "integrity": "sha1-bpi3U/YA9WISVIm1fkJM1Iy9LBA=", - "requires": { - "debug": "2.0.0", - "mongodb": "2.2.33" - } - }, - "morgan": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.3.2.tgz", - "integrity": "sha1-rEGqFSIe5OXyrIQ4lraRgTmhjv0=", - "requires": { - "basic-auth": "1.0.0", - "depd": "0.4.5", - "on-finished": "2.1.0" - } - }, - "ms": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", - "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=" - }, - "multer": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.3.0.tgz", - "integrity": "sha1-CSsmcPaEb6SRSWXvyM+Uwg/sbNI=", - "requires": { - "append-field": "0.1.0", - "busboy": "0.2.14", - "concat-stream": "1.6.0", - "mkdirp": "0.5.1", - "object-assign": "3.0.0", - "on-finished": "2.3.0", - "type-is": "1.6.15", - "xtend": "4.0.1" - }, - "dependencies": { - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" - }, - "mime-types": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", - "requires": { - "mime-db": "1.30.0" - } - }, - "object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "type-is": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", - "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", - "requires": { - "media-typer": "0.3.0", - "mime-types": "2.1.17" - } - } - } - }, - "multimatch": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", - "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", - "dev": true, - "requires": { - "array-differ": "1.0.0", - "array-union": "1.0.2", - "arrify": "1.0.1", - "minimatch": "3.0.4" - } - }, - "mustache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.0.tgz", - "integrity": "sha1-QCj3d4sXcIpImTCm5SrDvKDaQdA=" - }, - "mustache-express": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/mustache-express/-/mustache-express-1.2.5.tgz", - "integrity": "sha1-FypIRFpd5crcnwV/apKkr8oHrhQ=", - "requires": { - "async": "0.2.10", - "lru-cache": "2.5.2", - "mustache": "2.3.0" - }, - "dependencies": { - "async": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", - "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=" - } - } - }, - "mute-stream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", - "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", - "dev": true - }, - "nan": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", - "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=", - "dev": true, - "optional": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "negotiator": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.4.9.tgz", - "integrity": "sha1-kuRrbbU8fkIe1koryU8IvnYw3z8=" - }, - "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "requires": { - "hosted-git-info": "2.5.0", - "is-builtin-module": "1.0.0", - "semver": "5.4.1", - "validate-npm-package-license": "3.0.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "1.1.0" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "2.0.1" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "nwmatcher": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.1.tgz", - "integrity": "sha1-eumwew6oBNt+JfBctf5Al9TklJ8=" - }, - "oauth": { - "version": "0.9.15", - "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", - "integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE=" - }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" - }, - "obj-props": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/obj-props/-/obj-props-1.1.0.tgz", - "integrity": "sha1-YmMT+qRCvv1KROmgLDy2vek3tRE=", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "requires": { - "for-own": "0.1.5", - "is-extendable": "0.1.1" - } - }, - "on-finished": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.1.0.tgz", - "integrity": "sha1-DFOfCSkej/rd4MiiWFD7LO3HAi0=", - "requires": { - "ee-first": "1.0.5" - } - }, - "on-headers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "1.0.0" - } - }, - "options": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", - "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" - }, - "opts": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/opts/-/opts-1.2.6.tgz", - "integrity": "sha1-0YXAQlz9652h0YKQi2W1wCOP67M=", - "dev": true - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-limit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz", - "integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=", - "dev": true - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "1.1.0" - } - }, - "package-json": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", - "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", - "dev": true, - "requires": { - "got": "6.7.1", - "registry-auth-token": "3.3.1", - "registry-url": "3.1.0", - "semver": "5.4.1" - } - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, - "requires": { - "glob-base": "0.3.0", - "is-dotfile": "1.0.3", - "is-extglob": "1.0.0", - "is-glob": "2.0.1" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "1.3.1" - } - }, - "parse5": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz", - "integrity": "sha1-m387DeMr543CQBsXVzzK8Pb1nZQ=" - }, - "parseurl": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", - "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=" - }, - "passport": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/passport/-/passport-0.3.2.tgz", - "integrity": "sha1-ndAJ+RXo/glbASSgG4+C2gdRAQI=", - "requires": { - "passport-strategy": "1.0.0", - "pause": "0.0.1" - } - }, - "passport-github": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/passport-github/-/passport-github-1.1.0.tgz", - "integrity": "sha1-jOHj/NYa11eOsd9ZWDnkrqEjVdQ=", - "requires": { - "passport-oauth2": "1.4.0" - } - }, - "passport-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", - "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", - "requires": { - "passport-strategy": "1.0.0" - } - }, - "passport-oauth2": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.4.0.tgz", - "integrity": "sha1-9i+BWDy+EmCb585vFguTlaJ7hq0=", - "requires": { - "oauth": "0.9.15", - "passport-strategy": "1.0.0", - "uid2": "0.0.3", - "utils-merge": "1.0.0" - } - }, - "passport-strategy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", - "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "requires": { - "pinkie-promise": "2.0.1" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.3.tgz", - "integrity": "sha1-IbmrgidCed4lsVbqCP0SylG4rss=" - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - } - }, - "pause": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", - "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "2.0.4" - } - }, - "pkg-conf": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.0.0.tgz", - "integrity": "sha1-BxyHZQQDvM+5xif1h1G/5HwGcnk=", - "dev": true, - "requires": { - "find-up": "2.1.0", - "load-json-file": "2.0.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "2.0.0" - } - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "strip-bom": "3.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } - } - }, - "pkg-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", - "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", - "dev": true, - "requires": { - "find-up": "1.1.2" - } - }, - "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "dev": true, - "requires": { - "find-up": "2.1.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "2.0.0" - } - } - } - }, - "plur": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz", - "integrity": "sha1-dIJFLBoPUI4+NE6uwxLJHCncZVo=", - "dev": true, - "requires": { - "irregular-plurals": "1.4.0" - } - }, - "pluralize": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", - "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" - }, - "progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", - "dev": true - }, - "proto-props": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/proto-props/-/proto-props-0.2.1.tgz", - "integrity": "sha1-XgHcJnWg3pq/p255nfozTW9IP0s=", - "dev": true - }, - "proxy-addr": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.10.tgz", - "integrity": "sha1-DUCoL4Afw1VWfS7LZe/j8HfxIcU=", - "requires": { - "forwarded": "0.1.0", - "ipaddr.js": "1.0.5" - } - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "qs": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-2.2.4.tgz", - "integrity": "sha1-Lp+800tUDjQhySTs0B6QqpdTGcg=" - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" - }, - "random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" - }, - "randomatic": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", - "dev": true, - "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "1.1.5" - } - } - } - }, - "range-parser": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.3.tgz", - "integrity": "sha1-aHKCNTXGkuLCoBA4Jq/YLC4P8XU=" - }, - "raw-body": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.3.0.tgz", - "integrity": "sha1-l4IwoValVI9C7vFN4i0PT2EAg9E=", - "requires": { - "bytes": "1.0.0", - "iconv-lite": "0.4.4" - } - }, - "rc": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.2.tgz", - "integrity": "sha1-2M6ctX6NZNnHut2YdsfDTL48cHc=", - "dev": true, - "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.4", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" - } - }, - "readable-stream": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz", - "integrity": "sha1-BwV6y+JGeyIELTb5jFrVBwVOlbE=", - "requires": { - "buffer-shims": "1.0.0", - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "readdirp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", - "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "readable-stream": "2.2.7", - "set-immediate-shim": "1.0.1" - } - }, - "readline2": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", - "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "mute-stream": "0.0.5" - } - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, - "requires": { - "resolve": "1.4.0" - } - }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "requires": { - "indent-string": "2.1.0", - "strip-indent": "1.0.1" - } - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "dev": true, - "requires": { - "is-equal-shallow": "0.1.3" - } - }, - "registry-auth-token": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.1.tgz", - "integrity": "sha1-+w0yie4Nmtosu1KvXf5mywcNMAY=", - "dev": true, - "requires": { - "rc": "1.2.2", - "safe-buffer": "5.1.1" - } - }, - "registry-url": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", - "dev": true, - "requires": { - "rc": "1.2.2" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "requires": { - "is-finite": "1.0.2" - } - }, - "request": { - "version": "2.83.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", - "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", - "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.3.1", - "har-validator": "5.0.3", - "hawk": "6.0.2", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.1", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.3", - "tunnel-agent": "0.6.0", - "uuid": "3.1.0" - }, - "dependencies": { - "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" - }, - "mime-types": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", - "requires": { - "mime-db": "1.30.0" - } - }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" - }, - "tough-cookie": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", - "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", - "requires": { - "punycode": "1.4.1" - } - } - } - }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "0.1.0", - "resolve-from": "1.0.1" - }, - "dependencies": { - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true - } - } - }, - "require_optional": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", - "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", - "requires": { - "resolve-from": "2.0.0", - "semver": "5.4.1" - } - }, - "resolve": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz", - "integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==", - "dev": true, - "requires": { - "path-parse": "1.0.5" - } - }, - "resolve-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-1.0.0.tgz", - "integrity": "sha1-Tq7qQe0EDRcCRX32SkKysH0kb58=", - "dev": true, - "requires": { - "resolve-from": "2.0.0" - } - }, - "resolve-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "1.1.1", - "onetime": "1.1.0" - } - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "requires": { - "glob": "7.1.2" - } - }, - "run-async": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", - "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", - "dev": true, - "requires": { - "once": "1.4.0" - } - }, - "rx-lite": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", - "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", - "dev": true - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "semver": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" - }, - "semver-diff": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", - "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", - "dev": true, - "requires": { - "semver": "5.4.1" - } - }, - "send": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/send/-/send-0.9.3.tgz", - "integrity": "sha1-tDp0FM0Im3++ybdVJG98N7e4XMA=", - "requires": { - "debug": "2.0.0", - "depd": "0.4.5", - "destroy": "1.0.3", - "escape-html": "1.0.1", - "etag": "1.4.0", - "fresh": "0.2.4", - "mime": "1.2.11", - "ms": "0.6.2", - "on-finished": "2.1.0", - "range-parser": "1.0.3" - } - }, - "serve-favicon": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.1.7.tgz", - "integrity": "sha1-e5EcDqTA+aKtaG2qUiJ2b3vH23k=", - "requires": { - "etag": "1.5.1", - "fresh": "0.2.4", - "ms": "0.6.2" - }, - "dependencies": { - "crc": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.2.1.tgz", - "integrity": "sha1-XZyPt3okXNXsopHl0tAFM0urAII=" - }, - "etag": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.5.1.tgz", - "integrity": "sha1-VMUN4E7kJpVWKSWsVmWIKRvn6eo=", - "requires": { - "crc": "3.2.1" - } - } - } - }, - "serve-static": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.6.5.tgz", - "integrity": "sha1-rKF+DerEqHcp9geHgbfSf2OqPZw=", - "requires": { - "escape-html": "1.0.1", - "parseurl": "1.3.1", - "send": "0.9.3", - "utils-merge": "1.0.0" - } - }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "shelljs": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", - "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", - "dev": true, - "requires": { - "glob": "7.1.2", - "interpret": "1.0.4", - "rechoir": "0.6.2" - } - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true - }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "dev": true - }, - "sntp": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.0.2.tgz", - "integrity": "sha1-UGQRDwr4X3z9t9a2ekACjOUrSys=", - "requires": { - "hoek": "4.2.0" - } - }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "dev": true, - "requires": { - "is-plain-obj": "1.1.0" - } - }, - "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", - "optional": true, - "requires": { - "amdefine": "1.0.1" - } - }, - "spdx-correct": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", - "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", - "requires": { - "spdx-license-ids": "1.2.2" - } - }, - "spdx-expression-parse": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", - "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=" - }, - "spdx-license-ids": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", - "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=" - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sshpk": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", - "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - } - }, - "streamsearch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "5.1.1" - } - }, - "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "requires": { - "is-utf8": "0.2.1" - } - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "requires": { - "get-stdin": "4.0.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "struct": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/struct/-/struct-0.0.11.tgz", - "integrity": "sha1-rtX9bvmnT7ePBC9SCRCA0ZPqyjo=" - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "symbol-tree": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", - "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=" - }, - "table": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", - "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", - "dev": true, - "requires": { - "ajv": "4.11.8", - "ajv-keywords": "1.5.1", - "chalk": "1.1.3", - "lodash": "4.17.4", - "slice-ansi": "0.0.4", - "string-width": "2.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - } - } - }, - "term-size": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", - "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", - "dev": true, - "requires": { - "execa": "0.7.0" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "the-argv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/the-argv/-/the-argv-1.0.0.tgz", - "integrity": "sha1-AIRwUAVzDdhNt1UlPJMa45jblSI=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", - "dev": true - }, - "tough-cookie": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", - "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", - "requires": { - "punycode": "1.4.1" - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" - }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" - }, - "tryit": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", - "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "5.1.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "requires": { - "prelude-ls": "1.1.2" - } - }, - "type-is": { - "version": "1.5.7", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.5.7.tgz", - "integrity": "sha1-uTaKWTzG730GReeLL0xky+zQXpA=", - "requires": { - "media-typer": "0.3.0", - "mime-types": "2.0.14" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "requires": { - "random-bytes": "1.0.0" - } - }, - "uid2": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz", - "integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=" - }, - "ultron": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", - "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=" - }, - "unique-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", - "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", - "dev": true, - "requires": { - "crypto-random-string": "1.0.0" - } - }, - "unzip-response": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", - "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", - "dev": true - }, - "update-notifier": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.3.0.tgz", - "integrity": "sha1-TognpruRUUCrCTVZ1wFOPruDdFE=", - "dev": true, - "requires": { - "boxen": "1.2.2", - "chalk": "2.1.0", - "configstore": "3.1.1", - "import-lazy": "2.1.0", - "is-installed-globally": "0.1.0", - "is-npm": "1.0.0", - "latest-version": "3.1.0", - "semver-diff": "2.1.0", - "xdg-basedir": "3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.0" - } - }, - "chalk": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", - "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.4.0" - } - }, - "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - } - } - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" - } - } - }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "dev": true, - "requires": { - "prepend-http": "1.0.4" - } - }, - "user-home": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", - "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", - "dev": true, - "requires": { - "os-homedir": "1.0.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "utils-merge": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", - "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" - }, - "uuid": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" - }, - "validate-npm-package-license": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", - "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", - "requires": { - "spdx-correct": "1.0.2", - "spdx-expression-parse": "1.0.4" - } - }, - "validator": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-6.3.0.tgz", - "integrity": "sha1-R84j7Y1Ord+p1LjvAHG2zxB418g=" - }, - "vary": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.0.1.tgz", - "integrity": "sha1-meSYFWaihhGN+yuBc1ffeZM3bRA=" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "1.3.0" - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" - }, - "webpage": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/webpage/-/webpage-0.3.0.tgz", - "integrity": "sha1-FcjJnoIrSZ6Zga5odlObQjRIuOc=" - }, - "whatwg-encoding": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.1.tgz", - "integrity": "sha1-PGxFGhmO567FWx7GHQkgxngBpfQ=", - "requires": { - "iconv-lite": "0.4.13" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", - "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=" - } - } - }, - "whatwg-url": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-4.8.0.tgz", - "integrity": "sha1-0pgaqRSMHgCkHFphMRZqtGg7vMA=", - "requires": { - "tr46": "0.0.3", - "webidl-conversions": "3.0.1" - }, - "dependencies": { - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" - } - } - }, - "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", - "dev": true, - "requires": { - "isexe": "2.0.0" - } - }, - "widest-line": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-1.0.0.tgz", - "integrity": "sha1-DAnIXCqUaD0Nfq+O4JfVZL8OEFw=", - "dev": true, - "requires": { - "string-width": "1.0.2" - } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "dev": true, - "requires": { - "mkdirp": "0.5.1" - } - }, - "write-file-atomic": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", - "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "imurmurhash": "0.1.4", - "signal-exit": "3.0.2" - } - }, - "write-json-file": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-2.3.0.tgz", - "integrity": "sha1-K2TIozAE1UuGmMdtWFp3zrYdoy8=", - "dev": true, - "requires": { - "detect-indent": "5.0.0", - "graceful-fs": "4.1.11", - "make-dir": "1.0.0", - "pify": "3.0.0", - "sort-keys": "2.0.0", - "write-file-atomic": "2.3.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "sort-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", - "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", - "dev": true, - "requires": { - "is-plain-obj": "1.1.0" - } - } - } - }, - "write-pkg": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/write-pkg/-/write-pkg-2.1.0.tgz", - "integrity": "sha1-NTqkTDnEjCFED1wIzmq9RhQcnAg=", - "dev": true, - "requires": { - "sort-keys": "1.1.2", - "write-json-file": "2.3.0" - } - }, - "ws": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.4.tgz", - "integrity": "sha1-V/QNA2gy5fUFVmKjl8Tedu1mv2E=", - "requires": { - "options": "0.0.6", - "ultron": "1.0.2" - } - }, - "xdg-basedir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", - "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", - "dev": true - }, - "xml-name-validator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", - "integrity": "sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=" - }, - "xo": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/xo/-/xo-0.18.2.tgz", - "integrity": "sha1-kqQusCpPsUnf6lUYAhkU9arIT/A=", - "dev": true, - "requires": { - "arrify": "1.0.1", - "debug": "2.6.9", - "deep-assign": "1.0.0", - "eslint": "3.19.0", - "eslint-config-xo": "0.18.2", - "eslint-formatter-pretty": "1.3.0", - "eslint-plugin-ava": "4.2.2", - "eslint-plugin-import": "2.7.0", - "eslint-plugin-no-use-extend-native": "0.3.12", - "eslint-plugin-promise": "3.6.0", - "eslint-plugin-unicorn": "2.1.2", - "get-stdin": "5.0.1", - "globby": "6.1.0", - "has-flag": "2.0.0", - "ignore": "3.3.5", - "lodash.isequal": "4.5.0", - "meow": "3.7.0", - "multimatch": "2.1.0", - "path-exists": "3.0.0", - "pkg-conf": "2.0.0", - "resolve-cwd": "1.0.0", - "resolve-from": "2.0.0", - "slash": "1.0.0", - "update-notifier": "2.3.0", - "xo-init": "0.5.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "get-stdin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", - "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } - } - }, - "xo-init": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/xo-init/-/xo-init-0.5.0.tgz", - "integrity": "sha1-jijex5Z2zF4EL95f2PcQ4mRrDjY=", - "dev": true, - "requires": { - "arrify": "1.0.1", - "execa": "0.5.1", - "minimist": "1.2.0", - "path-exists": "3.0.0", - "read-pkg-up": "2.0.0", - "the-argv": "1.0.0", - "write-pkg": "2.1.0" - }, - "dependencies": { - "cross-spawn": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", - "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", - "dev": true, - "requires": { - "lru-cache": "4.1.1", - "which": "1.3.0" - } - }, - "execa": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.5.1.tgz", - "integrity": "sha1-3j+4XLjW6RyFvLzrFkWBeFy1ezY=", - "dev": true, - "requires": { - "cross-spawn": "4.0.2", - "get-stream": "2.3.1", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "2.0.0" - } - }, - "get-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", - "integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=", - "dev": true, - "requires": { - "object-assign": "4.1.1", - "pinkie-promise": "2.0.1" - } - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "strip-bom": "3.0.0" - } - }, - "lru-cache": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", - "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", - "dev": true, - "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "2.3.0" - } - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "2.0.0", - "normalize-package-data": "2.4.0", - "path-type": "2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "2.1.0", - "read-pkg": "2.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } - } - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - } - } -} diff --git a/package.json b/package.json index 4ce803b4..0aa458ef 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "start": "node ./bin/www", - "test": "xo" + "start": "node ./bin/www" }, "dependencies": { "async": "^2.0.1", @@ -15,7 +14,7 @@ "debug": "~2.0.0", "dompurify": "^0.8.3", "express": "~4.9.0", - "express-session": "^1.15.6", + "express-session": "^1.14.0", "express-validator": "^2.20.8", "fast-json-patch": "^1.1.1", "file-type": "^3.8.0", @@ -25,7 +24,7 @@ "jsdom": "^9.5.0", "keypress": "^0.2.1", "merge": "^1.2.0", - "mongodb": "^2.2.33", + "mongodb": "^2.2.5", "monk": "^3.1.1", "morgan": "~1.3.0", "multer": "^1.2.0", @@ -33,23 +32,15 @@ "passport": "^0.3.2", "passport-github": "^1.1.0", "passport-local": "^1.0.0", - "request": "^2.83.0", + "request": "^2.74.0", "serve-favicon": "~2.1.3", "struct": "0.0.11", "url": "^0.11.0", "validator": "^6.0.0", - "webpage": "^0.3.0", "ws": "^1.1.1" }, - "devDependencies": { + "devDependencies" : { "connect-livereload": "^0.5.3", - "livereload": "^0.4.0", - "xo": "^0.18.2" - }, - "xo": { - "space": 4, - "rules": { - "no-buffer-constructor": "off" - } + "livereload": "^0.4.0" } } diff --git a/public/js/atlasMaker-draw.js b/public/js/atlasMaker-draw.js index 02741797..39c404e1 100644 --- a/public/js/atlasMaker-draw.js +++ b/public/js/atlasMaker-draw.js @@ -6,205 +6,205 @@ var AtlasMakerDraw = { /** * @function resizeWindow */ - resizeWindow: function resizeWindow() { - var me=AtlasMakerWidget; - var l=me.traceLog(resizeWindow,1);if(l)console.log.apply(undefined,l); + resizeWindow: function resizeWindow() { + var me=AtlasMakerWidget; + var l=me.traceLog(resizeWindow,1);if(l)console.log.apply(undefined,l); - var wH=me.container.height(); - var wW=me.container.width(); - var wAspect=wW/wH; - var bAspect=me.brain_W*me.brain_Wdim/(me.brain_H*me.brain_Hdim); - - /* - if(me.editMode==1) { - */ - // In edit mode width or height can be fixed to 100% - // depending on the slice and container aspect ratio - if(wAspect>bAspect) - $('#resizable').css({width:(100*bAspect/wAspect)+'%',height:'100%'}); - else - $('#resizable').css({width:'100%',height:(100*wAspect/bAspect)+'%'}); - /* - } else { - // In display mode slice width is always fixed to 100% - $('#resizable').css({width:'100%',height:(100*wAspect/bAspect)+'%'}); - - // Slice height cannot be larger than window's inner height: - var sliceH=me.container.height(); - var windowH=window.innerHeight; - if(sliceH>windowH) { - var f=windowH/sliceH; - $('#resizable').css({width:(f*100)+'%',height:f*(100*wAspect/bAspect)+'%'}); - } - } - */ - }, + var wH=me.container.height(); + var wW=me.container.width(); + var wAspect=wW/wH; + var bAspect=me.brain_W*me.brain_Wdim/(me.brain_H*me.brain_Hdim); + + /* + if(me.editMode==1) { + */ + // In edit mode width or height can be fixed to 100% + // depending on the slice and container aspect ratio + if(wAspect>bAspect) + $('#resizable').css({width:(100*bAspect/wAspect)+'%',height:'100%'}); + else + $('#resizable').css({width:'100%',height:(100*wAspect/bAspect)+'%'}); + /* + } else { + // In display mode slice width is always fixed to 100% + $('#resizable').css({width:'100%',height:(100*wAspect/bAspect)+'%'}); + + // Slice height cannot be larger than window's inner height: + var sliceH=me.container.height(); + var windowH=window.innerHeight; + if(sliceH>windowH) { + var f=windowH/sliceH; + $('#resizable').css({width:(f*100)+'%',height:f*(100*wAspect/bAspect)+'%'}); + } + } + */ + }, /** * @function configureBrainImage */ - configureBrainImage: function configureBrainImage() { - var me=AtlasMakerWidget; - var l=me.traceLog(configureBrainImage);if(l)console.log.apply(undefined,l); - - if(me.User.view==null) - me.User.view="sag"; - - var s2v=me.User.s2v; - switch(me.User.view) { - case 'sag': me.brain_W=s2v.sdim[1]; me.brain_H=s2v.sdim[2]; me.brain_D=s2v.sdim[0]; me.brain_Wdim=s2v.wpixdim[1]; me.brain_Hdim=s2v.wpixdim[2]; break; // sagital - case 'cor': me.brain_W=s2v.sdim[0]; me.brain_H=s2v.sdim[2]; me.brain_D=s2v.sdim[1]; me.brain_Wdim=s2v.wpixdim[0]; me.brain_Hdim=s2v.wpixdim[2]; break; // coronal - case 'axi': me.brain_W=s2v.sdim[0]; me.brain_H=s2v.sdim[1]; me.brain_D=s2v.sdim[2]; me.brain_Wdim=s2v.wpixdim[0]; me.brain_Hdim=s2v.wpixdim[1]; break; // axial - } + configureBrainImage: function configureBrainImage() { + var me=AtlasMakerWidget; + var l=me.traceLog(configureBrainImage);if(l)console.log.apply(undefined,l); + + if(me.User.view==null) + me.User.view="sag"; + + var s2v=me.User.s2v; + switch(me.User.view) { + case 'sag': me.brain_W=s2v.sdim[1]; me.brain_H=s2v.sdim[2]; me.brain_D=s2v.sdim[0]; me.brain_Wdim=s2v.wpixdim[1]; me.brain_Hdim=s2v.wpixdim[2]; break; // sagital + case 'cor': me.brain_W=s2v.sdim[0]; me.brain_H=s2v.sdim[2]; me.brain_D=s2v.sdim[1]; me.brain_Wdim=s2v.wpixdim[0]; me.brain_Hdim=s2v.wpixdim[2]; break; // coronal + case 'axi': me.brain_W=s2v.sdim[0]; me.brain_H=s2v.sdim[1]; me.brain_D=s2v.sdim[2]; me.brain_Wdim=s2v.wpixdim[0]; me.brain_Hdim=s2v.wpixdim[1]; break; // axial + } - me.canvas.width=me.brain_W; - me.canvas.height=me.brain_H*me.brain_Hdim/me.brain_Wdim; - me.brain_offcn.width=me.brain_W; - me.brain_offcn.height=me.brain_H; - me.brain_px=me.brain_offtx.getImageData(0,0,me.brain_offcn.width,me.brain_offcn.height); - - if(me.User.slice==null || me.User.slice>=me.brain_D-1) - me.User.slice=parseInt(me.brain_D/2); + me.canvas.width=me.brain_W; + me.canvas.height=me.brain_H*me.brain_Hdim/me.brain_Wdim; + me.brain_offcn.width=me.brain_W; + me.brain_offcn.height=me.brain_H; + me.brain_px=me.brain_offtx.getImageData(0,0,me.brain_offcn.width,me.brain_offcn.height); + + if(me.User.slice==null || me.User.slice>=me.brain_D-1) + me.User.slice=parseInt(me.brain_D/2); - me.sendUserDataMessage(JSON.stringify({'view':me.User.view,'slice':me.User.slice})); - - // configure toolbar slider - $(".slider#slice").data({max:me.brain_D-1,val:me.User.slice}); - if($("#slice .thumb")[0]) $("#slice .thumb")[0].style.left=(me.User.slice/(me.brain_D-1)*100)+"%"; - me.drawImages(); - me.initCursor(); - }, + me.sendUserDataMessage(JSON.stringify({'view':me.User.view,'slice':me.User.slice})); + + // configure toolbar slider + $(".slider#slice").data({max:me.brain_D-1,val:me.User.slice}); + if($("#slice .thumb")[0]) $("#slice .thumb")[0].style.left=(me.User.slice/(me.brain_D-1)*100)+"%"; + me.drawImages(); + me.initCursor(); + }, /** * @function configureAtlasImage */ - configureAtlasImage: function configureAtlasImage() { - var me=AtlasMakerWidget; - var l=me.traceLog(configureAtlasImage);if(l)console.log.apply(undefined,l); - - // has to be run *after* configureBrainImage - me.atlas_offcn.width=me.brain_W; - me.atlas_offcn.height=me.brain_H; - me.atlas_px=me.atlas_offtx.getImageData(0,0,me.atlas_offcn.width,me.atlas_offcn.height); - }, + configureAtlasImage: function configureAtlasImage() { + var me=AtlasMakerWidget; + var l=me.traceLog(configureAtlasImage);if(l)console.log.apply(undefined,l); + + // has to be run *after* configureBrainImage + me.atlas_offcn.width=me.brain_W; + me.atlas_offcn.height=me.brain_H; + me.atlas_px=me.atlas_offtx.getImageData(0,0,me.atlas_offcn.width,me.atlas_offcn.height); + }, /** * @function nearestNeighbour */ - nearestNeighbour: function nearestNeighbour(ctx) { - var me=AtlasMakerWidget; - var l=me.traceLog(nearestNeighbour,1);if(l)console.log.apply(undefined,l); - - ctx.imageSmoothingEnabled = false; - ctx.mozImageSmoothingEnabled = false; - }, + nearestNeighbour: function nearestNeighbour(ctx) { + var me=AtlasMakerWidget; + var l=me.traceLog(nearestNeighbour,1);if(l)console.log.apply(undefined,l); + + ctx.imageSmoothingEnabled = false; + ctx.mozImageSmoothingEnabled = false; + }, /** * @function computeSegmentedVolume */ - computeSegmentedVolume: function computeSegmentedVolume() { - var me=AtlasMakerWidget; - var l=me.traceLog(computeSegmentedVolume,1);if(l)console.log.apply(undefined,l); + computeSegmentedVolume: function computeSegmentedVolume() { + var me=AtlasMakerWidget; + var l=me.traceLog(computeSegmentedVolume,1);if(l)console.log.apply(undefined,l); - var i,sum=0; - var data=me.atlas.data; - var dim=me.atlas.dim; + var i,sum=0; + var data=me.atlas.data; + var dim=me.atlas.dim; - for(i=0;i0) - sum++; - } - return sum*me.User.pixdim[0]*me.User.pixdim[1]*me.User.pixdim[2]; - }, + for(i=0;i0) + sum++; + } + return sum*me.User.pixdim[0]*me.User.pixdim[1]*me.User.pixdim[2]; + }, /** * @function displayInformation * @desc Overlays text and vectorial information on top of the annotation volume slice. Text information is added from the AtlasMakerWidget.info object. Vectorial information is displayed using svg format */ - displayInformation: function displayInformation() { - var me=AtlasMakerWidget; - var l=me.traceLog(displayInformation,1);if(l)console.log.apply(undefined,l); - - me.info.slice=me.User.slice; - var i=0,str; - var text=me.container.find("#text-layer"); - var vector=me.container.find("#vector-layer"); - - str=""; - for(var k in me.info) { - str+=""+k+": "+me.info[k]+"
"; - } - text.html(str); - - str=""; - if(me.User.measureLength) { - var W=parseFloat($('#atlasMaker canvas').css('width')); - var w=parseFloat($('#atlasMaker canvas').attr('width')); - var zx=W/w,zy=zx*me.brain_Hdim/me.brain_Wdim,p=me.User.measureLength,str1; - var W=parseFloat($('#atlasMaker canvas').css('width')); - var w=parseFloat($('#atlasMaker canvas').attr('width')); - str1="M"+zx*p[0].x+","+zy*p[0].y; - for(i=1;i", - "", - (i>0)?"":""].join("\n"); - } - vector.html(str); - }, + displayInformation: function displayInformation() { + var me=AtlasMakerWidget; + var l=me.traceLog(displayInformation,1);if(l)console.log.apply(undefined,l); + + me.info.slice=me.User.slice; + var i=0,str; + var text=me.container.find("#text-layer"); + var vector=me.container.find("#vector-layer"); + + str=""; + for(var k in me.info) { + str+=""+k+": "+me.info[k]+"
"; + } + text.html(str); + + str=""; + if(me.User.measureLength) { + var W=parseFloat($('#atlasMaker canvas').css('width')); + var w=parseFloat($('#atlasMaker canvas').attr('width')); + var zx=W/w,zy=zx*me.brain_Hdim/me.brain_Wdim,p=me.User.measureLength,str1; + var W=parseFloat($('#atlasMaker canvas').css('width')); + var w=parseFloat($('#atlasMaker canvas').attr('width')); + str1="M"+zx*p[0].x+","+zy*p[0].y; + for(i=1;i", + "", + (i>0)?"":""].join("\n"); + } + vector.html(str); + }, /** * @function drawImages */ - drawImages: function drawImages() { - var me=AtlasMakerWidget; - var l=me.traceLog(drawImages,1);if(l)console.log.apply(undefined,l); - - if(me.brain_img.img - && me.flagLoadingImg.view - && me.flagLoadingImg.slice) { - me.context.clearRect(0,0,me.context.canvas.width,me.canvas.height); - me.displayInformation(); + drawImages: function drawImages() { + var me=AtlasMakerWidget; + var l=me.traceLog(drawImages,1);if(l)console.log.apply(undefined,l); + + if(me.brain_img.img + && me.flagLoadingImg.view + && me.flagLoadingImg.slice) { + me.context.clearRect(0,0,me.context.canvas.width,me.canvas.height); + me.displayInformation(); - me.nearestNeighbour(me.context); - - me.context.drawImage(me.brain_img.img,0,0,me.brain_W,me.brain_H*me.brain_Hdim/me.brain_Wdim); - me.drawAtlasImage(me.flagLoadingImg.view,me.flagLoadingImg.slice); - } + me.nearestNeighbour(me.context); + + me.context.drawImage(me.brain_img.img,0,0,me.brain_W,me.brain_H*me.brain_Hdim/me.brain_Wdim); + me.drawAtlasImage(me.flagLoadingImg.view,me.flagLoadingImg.slice); + } - if(!me.brain_img.img || me.brain_img.view!=me.User.view || me.brain_img.slice!=me.User.slice) { - me.sendRequestSliceMessage(); - } - }, + if(!me.brain_img.img || me.brain_img.view!=me.User.view || me.brain_img.slice!=me.User.slice) { + me.sendRequestSliceMessage(); + } + }, /** * @function drawAtlasImage */ - drawAtlasImage: function drawAtlasImage(view,slice) { - var me=AtlasMakerWidget; - var l=me.traceLog(drawAtlasImage,1);if(l)console.log.apply(undefined,l); - - if(!me.atlas) - return; + drawAtlasImage: function drawAtlasImage(view,slice) { + var me=AtlasMakerWidget; + var l=me.traceLog(drawAtlasImage,1);if(l)console.log.apply(undefined,l); + + if(!me.atlas) + return; - var data=me.atlas.data; - var dim=me.atlas.dim; - var s,val; + var data=me.atlas.data; + var dim=me.atlas.dim; + var s,val; - ys=yc=ya=slice; - for(y=0;y0)?255:0; - i=(y*me.atlas_offcn.width+x)*4; - me.atlas_px.data[ i ] =c[0]; - me.atlas_px.data[ i+1 ]=c[1]; - me.atlas_px.data[ i+2 ]=c[2]; - me.atlas_px.data[ i+3 ]=alpha*me.alphaLevel; - } - me.atlas_offtx.putImageData(me.atlas_px, 0, 0); + var c=me.ontologyValueToColor(data[i]); + var alpha=(data[i]>0)?255:0; + i=(y*me.atlas_offcn.width+x)*4; + me.atlas_px.data[ i ] =c[0]; + me.atlas_px.data[ i+1 ]=c[1]; + me.atlas_px.data[ i+2 ]=c[2]; + me.atlas_px.data[ i+3 ]=alpha*me.alphaLevel; + } + me.atlas_offtx.putImageData(me.atlas_px, 0, 0); - me.nearestNeighbour(me.context); - me.context.drawImage(me.atlas_offcn,0,0,me.brain_W,me.brain_H*me.brain_Hdim/me.brain_Wdim); - } + me.nearestNeighbour(me.context); + me.context.drawImage(me.atlas_offcn,0,0,me.brain_W,me.brain_H*me.brain_Hdim/me.brain_Wdim); + } }; diff --git a/public/js/atlasMaker-interaction.js b/public/js/atlasMaker-interaction.js index a5d39240..261bac44 100644 --- a/public/js/atlasMaker-interaction.js +++ b/public/js/atlasMaker-interaction.js @@ -2,758 +2,757 @@ * @page AtlasMaker: Interaction */ var AtlasMakerInteraction = { - //======================================================================================== - // Local user interaction - //======================================================================================== + //======================================================================================== + // Local user interaction + //======================================================================================== /** * @function changeToolbarDisplay */ - changeToolbarDisplay: function changeToolbarDisplay(display) { - var me=AtlasMakerWidget; - var l=me.traceLog(changeToolbarDisplay,0,"#f00");if(l)console.log.apply(undefined,l); - - switch(display) { - case "minimize": - $("#tools-maximized").hide(); - $("#tools-minimized").show(); - break; - case "maximize": - $("#tools-maximized").show(); - $("#tools-minimized").hide(); - break; - case "left": - $("body").attr("data-toolbarDisplay","left"); - break; - case "right": - $("body").attr("data-toolbarDisplay","right"); - break; + changeToolbarDisplay: function changeToolbarDisplay(display) { + var me=AtlasMakerWidget; + var l=me.traceLog(changeToolbarDisplay,0,"#f00");if(l)console.log.apply(undefined,l); + + switch(display) { + case "minimize": + $("#tools-maximized").hide(); + $("#tools-minimized").show(); + break; + case "maximize": + $("#tools-maximized").show(); + $("#tools-minimized").hide(); + break; + case "left": + $("body").attr("data-toolbarDisplay","left"); + break; + case "right": + $("body").attr("data-toolbarDisplay","right"); + break; } - }, + }, /** * @function changeView */ - changeView: function changeView(theView) { - var me=AtlasMakerWidget; - var l=me.traceLog(changeView,0,"#f00");if(l)console.log.apply(undefined,l); - - switch(theView) { - case 'sag': - me.User.view='sag'; - break; - case 'cor': - me.User.view='cor'; - break; - case 'axi': - me.User.view='axi'; - break; - } - me.sendUserDataMessage(JSON.stringify({'view':me.User.view})); - - me.configureBrainImage(); - me.configureAtlasImage(); - me.resizeWindow(); + changeView: function changeView(theView) { + var me=AtlasMakerWidget; + var l=me.traceLog(changeView,0,"#f00");if(l)console.log.apply(undefined,l); + + switch(theView) { + case 'sag': + me.User.view='sag'; + break; + case 'cor': + me.User.view='cor'; + break; + case 'axi': + me.User.view='axi'; + break; + } + me.sendUserDataMessage(JSON.stringify({'view':me.User.view})); + + me.configureBrainImage(); + me.configureAtlasImage(); + me.resizeWindow(); - me.drawImages(); - - me.initCursor(); + me.drawImages(); + + me.initCursor(); - }, + }, /** * @function changeTool * @desc Change the tool that defines the effect of mouse clicks * @param {String} theToole Name of the tool: Paint, Erase, Measure, Adjust */ - changeTool: function changeTool(theTool) { - var me=AtlasMakerWidget; - var l=me.traceLog(changeTool,0,"#f00");if(l)console.log.apply(undefined,l); - - if(theTool.toLowerCase()==me.User.tool) - return; - - switch(theTool) { - case 'Show': - me.User.tool='show'; - break; - case 'Paint': - me.User.tool='paint'; - break; - case 'Erase': - me.User.tool='erase'; - break; - case 'Measure': - me.User.tool='measure'; - break; - case 'Adjust': - me.User.tool='adjust'; - if($("#adjust").length==0) { - $.get("/templates/adjust.html",function(html) { - me.container.find("#resizable").append(html); - }); - } - break; - case 'Eyedrop': - me.User.tool='eyedrop'; - break; - } - me.sendUserDataMessage(JSON.stringify({'tool':me.User.tool})); - me.User.measureLength=null; - }, + changeTool: function changeTool(theTool) { + var me=AtlasMakerWidget; + var l=me.traceLog(changeTool,0,"#f00");if(l)console.log.apply(undefined,l); + + if(theTool.toLowerCase()==me.User.tool) + return; + + switch(theTool) { + case 'Show': + me.User.tool='show'; + break; + case 'Paint': + me.User.tool='paint'; + break; + case 'Erase': + me.User.tool='erase'; + break; + case 'Measure': + me.User.tool='measure'; + break; + case 'Adjust': + me.User.tool='adjust'; + if($("#adjust").length==0) { + $.get("/templates/adjust.html",function(html) { + me.container.find("#resizable").append(html); + }); + } + break; + case 'Eyedrop': + me.User.tool='eyedrop'; + break; + } + me.sendUserDataMessage(JSON.stringify({'tool':me.User.tool})); + me.User.measureLength=null; + }, /** * @function changePenSize */ - changePenSize: function changePenSize(theSize) { - var me=AtlasMakerWidget; - var l=me.traceLog(changePenSize,0,"#f00");if(l)console.log.apply(undefined,l); - - me.User.penSize=parseInt(theSize); - me.sendUserDataMessage(JSON.stringify({'penSize':me.User.penSize})); - }, + changePenSize: function changePenSize(theSize) { + var me=AtlasMakerWidget; + var l=me.traceLog(changePenSize,0,"#f00");if(l)console.log.apply(undefined,l); + + me.User.penSize=parseInt(theSize); + me.sendUserDataMessage(JSON.stringify({'penSize':me.User.penSize})); + }, /** * @function changePenColor */ - changePenColor: function changePenColor(index) { - var me=AtlasMakerWidget; - var l=me.traceLog(changePenColor,0,"#f00");if(l)console.log.apply(undefined,l); - - var c=me.ontology.labels[index].color; - $("#color").css({backgroundColor:'rgb('+c[0]+','+c[1]+','+c[2]+')'}); - me.User.penValue=me.ontology.labels[index].value; - me.sendUserDataMessage(JSON.stringify({'penValue':me.User.penValue})); - }, + changePenColor: function changePenColor(index) { + var me=AtlasMakerWidget; + var l=me.traceLog(changePenColor,0,"#f00");if(l)console.log.apply(undefined,l); + + var c=me.ontology.labels[index].color; + $("#color").css({backgroundColor:'rgb('+c[0]+','+c[1]+','+c[2]+')'}); + me.User.penValue=me.ontology.labels[index].value; + me.sendUserDataMessage(JSON.stringify({'penValue':me.User.penValue})); + }, /** * @function changeSlice */ - changeSlice: function changeSlice(x) { - var me=AtlasMakerWidget; - var l=me.traceLog(changeSlice,1,"#f00");if(l)console.log.apply(undefined,l); + changeSlice: function changeSlice(x) { + var me=AtlasMakerWidget; + var l=me.traceLog(changeSlice,1,"#f00");if(l)console.log.apply(undefined,l); - var max=$("#slice").data("max"); - $("#slice").data("val",x); - $("#slice .thumb")[0].style.left=(x*100/max)+"%"; - - me.User.slice=x; - me.sendUserDataMessage(JSON.stringify({'slice':me.User.slice})); + var max=$("#slice").data("max"); + $("#slice").data("val",x); + $("#slice .thumb")[0].style.left=(x*100/max)+"%"; + + me.User.slice=x; + me.sendUserDataMessage(JSON.stringify({'slice':me.User.slice})); - me.drawImages(); - }, + me.drawImages(); + }, /** * @function prevSlice */ - prevSlice: function prevSlice() { - var me=AtlasMakerWidget; - var l=me.traceLog(prevSlice,1,"#f00");if(l)console.log.apply(undefined,l); + prevSlice: function prevSlice() { + var me=AtlasMakerWidget; + var l=me.traceLog(prevSlice,1,"#f00");if(l)console.log.apply(undefined,l); - var x=$("#slice").data("val")-1; - if(x<0) x=0; - x=Math.round(x); - if(x!=$("#slice").data("val")) { - $("#slice").data("val",x); - me.changeSlice(x); - } - }, + var x=$("#slice").data("val")-1; + if(x<0) x=0; + x=Math.round(x); + if(x!=$("#slice").data("val")) { + $("#slice").data("val",x); + me.changeSlice(x); + } + }, /** * @function nextSlice */ - nextSlice: function nextSlice() { - var me=AtlasMakerWidget; - var l=me.traceLog(nextSlice,1,"#f00");if(l)console.log.apply(undefined,l); + nextSlice: function nextSlice() { + var me=AtlasMakerWidget; + var l=me.traceLog(nextSlice,1,"#f00");if(l)console.log.apply(undefined,l); - var max=$("#slice").data("max"); - var x=$("#slice").data("val")+1; - if(x>max) x=max; - x=Math.round(x); - if(x!=$("#slice").data("val")) { - $("#slice").data("val",x); - me.changeSlice(x); - } - }, + var max=$("#slice").data("max"); + var x=$("#slice").data("val")+1; + if(x>max) x=max; + x=Math.round(x); + if(x!=$("#slice").data("val")) { + $("#slice").data("val",x); + me.changeSlice(x); + } + }, /** * @function toggleFill */ - toggleFill: function toggleFill(x) { - var me=AtlasMakerWidget; - var l=me.traceLog(toggleFill,0,"#f00");if(l)console.log.apply(undefined,l); - - me.User.doFill=x; - me.sendUserDataMessage(JSON.stringify({'doFill':me.User.doFill})); - }, + toggleFill: function toggleFill(x) { + var me=AtlasMakerWidget; + var l=me.traceLog(toggleFill,0,"#f00");if(l)console.log.apply(undefined,l); + + me.User.doFill=x; + me.sendUserDataMessage(JSON.stringify({'doFill':me.User.doFill})); + }, /** * @function toggleChat */ - toggleChat: function toggleChat() { - var me=AtlasMakerWidget; - var l=me.traceLog(toggleChat,0,"#f00");if(l)console.log.apply(undefined,l); - - $("#chatBlock").toggle(); - }, + toggleChat: function toggleChat() { + var me=AtlasMakerWidget; + var l=me.traceLog(toggleChat,0,"#f00");if(l)console.log.apply(undefined,l); + + $("#chatBlock").toggle(); + }, /** * @function toggleFullscreen */ - toggleFullscreen: function toggleFullscreen() { - var me=AtlasMakerWidget; - var l=me.traceLog(toggleFullscreen,0,"#f00");if(l)console.log.apply(undefined,l); + toggleFullscreen: function toggleFullscreen() { + var me=AtlasMakerWidget; + var l=me.traceLog(toggleFullscreen,0,"#f00");if(l)console.log.apply(undefined,l); - if(me.fullscreen==false) { - // Enter fullscreen - //----------------- - - // add black overlay - var black=$("
"); - black.css({position:'fixed',top:0,left:0,width:'100%',height:'100%','z-index':5,'background-color':'#222'}); - $('body').append(black); - - // configure display mode - // $("#atlasMaker").removeClass('display-mode'); - $("body").addClass('atlasMaker-fullscreen'); - $("#atlasMaker").detach().appendTo('body'); - - // me.editMode=1; - me.resizeWindow(); - - // configure toolbar for edit mode - //$("#log").outerHeight($("#tools-side").outerHeight()-$("#log").offset().top-$("#msg").closest("tr").outerHeight()); - me.fullscreen=true; - } else { + if(me.fullscreen==false) { + // Enter fullscreen + //----------------- + + // add black overlay + var black=$("
"); + black.css({position:'fixed',top:0,left:0,width:'100%',height:'100%','z-index':5,'background-color':'#222'}); + $('body').append(black); + + // configure display mode + // $("#atlasMaker").removeClass('display-mode'); + $("body").addClass('atlasMaker-fullscreen'); + $("#atlasMaker").detach().appendTo('body'); + + // me.editMode=1; + me.resizeWindow(); + + // configure toolbar for edit mode + //$("#log").outerHeight($("#tools-side").outerHeight()-$("#log").offset().top-$("#msg").closest("tr").outerHeight()); + me.fullscreen=true; + } else { - // Exit fullscreen - //---------------- - - // remove black overlay - $("#blackOverlay").remove(); - - // go back to display mode - $("body").removeClass('atlasMaker-fullscreen'); - // $("#atlasMaker").addClass('display-mode'); - $("#atlasMaker").detach().appendTo('#stereotaxic'); - // me.editMode=0; - me.resizeWindow(); + // Exit fullscreen + //---------------- + + // remove black overlay + $("#blackOverlay").remove(); + + // go back to display mode + $("body").removeClass('atlasMaker-fullscreen'); + // $("#atlasMaker").addClass('display-mode'); + $("#atlasMaker").detach().appendTo('#stereotaxic'); + // me.editMode=0; + me.resizeWindow(); - me.fullscreen=false; - } - }, + me.fullscreen=false; + } + }, /** * @function render3D */ - render3D: function render3D() { - var me=AtlasMakerWidget; - var l=me.traceLog(render3D,0,"#f00");if(l)console.log.apply(undefined,l); - - // puts a fresh version of the segmentation in localStorage - localStorage.brainbox=URL.createObjectURL(new Blob([me.encodeNifti()])); - - // opens 3d render window - window.open("/templates/surface.html?path="+me.User.dirname+me.User.atlasFilename,"_blank"); - }, + render3D: function render3D() { + var me=AtlasMakerWidget; + var l=me.traceLog(render3D,0,"#f00");if(l)console.log.apply(undefined,l); + + // puts a fresh version of the segmentation in localStorage + localStorage.brainbox=URL.createObjectURL(new Blob([me.encodeNifti()])); + + // opens 3d render window + window.open("/templates/surface.html?path="+me.User.dirname+me.User.atlasFilename,"_blank"); + }, /** * @function link */ - link: function link() { - var me=AtlasMakerWidget; - var l=me.traceLog(link,0,"#f00");if(l)console.log.apply(undefined,l); - window.prompt("Copy to clipboard:", location.href+"&view="+AtlasMakerWidget.User.view+"&slice="+AtlasMakerWidget.User.slice); - }, + link: function link() { + var me=AtlasMakerWidget; + var l=me.traceLog(link,0,"#f00");if(l)console.log.apply(undefined,l); + window.prompt("Copy to clipboard:", location.href+"&view="+AtlasMakerWidget.User.view+"&slice="+AtlasMakerWidget.User.slice); + }, /** * @function upload */ - upload: function upload() { - var me=AtlasMakerWidget; - var l=me.traceLog(upload,0,"#f00");if(l)console.log.apply(undefined,l); - - var inp=$(""); - inp.hide(); - $("body").append(inp); - var input=inp.get(0); - input.type="file"; - input.onchange=function from_upload(e){ - var name=this.files[0]; - var reader = new FileReader(); - reader.onload = function from_upload(e) { - var result=e.target.result; - var nii; - if(name.name.split('.').pop()=="gz") { - var inflate=new pako.Inflate(); - inflate.push(new Uint8Array(result),true); - nii=inflate.result.buffer; - } - else - nii=result; - var mri=me.loadNifti(nii); + upload: function upload() { + var me=AtlasMakerWidget; + var l=me.traceLog(upload,0,"#f00");if(l)console.log.apply(undefined,l); + + var inp=$(""); + inp.hide(); + $("body").append(inp); + var input=inp.get(0); + input.type="file"; + input.onchange=function from_upload(e){ + var name=this.files[0]; + var reader = new FileReader(); + reader.onload = function from_upload(e) { + var result=e.target.result; + var nii; + if(name.name.split('.').pop()=="gz") { + var inflate=new pako.Inflate(); + inflate.push(new Uint8Array(result),true); + nii=inflate.result.buffer; + } + else + nii=result; + var mri=me.loadNifti(nii); - if( mri.dim[0]!=me.User.dim[0] || - mri.dim[1]!=me.User.dim[1] || - mri.dim[2]!=me.User.dim[2]) { - console.log("ERROR: Volume dimensions do not match"); - return; - } - - // copy uploaded data to atlas data - var i; - for(i=0;i
"); - $("#finger").addClass("touchDevice"); + var W=parseFloat($('#atlasMaker canvas').css('width')); + var H=parseFloat($('#atlasMaker canvas').css('height')); + var w=parseFloat($('#atlasMaker canvas').attr('width')); + var h=parseFloat($('#atlasMaker canvas').attr('height')); + + me.Crsr.x=parseInt(w/2); + me.Crsr.y=parseInt(h/2); + + me.Crsr.fx=parseInt(w/2)*(W/w); + me.Crsr.fy=parseInt(h/2)*(H/h); + $("#cursor").css({left:(me.Crsr.x*(W/w))+"px",top:(me.Crsr.y*(H/h))+"px",width:me.User.penSize*(W/w),height:me.User.penSize*(H/h)}); + + if(me.flagUsePreciseCursor) { + if($("#finger").length==0) { + me.container.append("
"); + $("#finger").addClass("touchDevice"); - // configure touch events for tablets - $("#finger").on("touchstart",function(e){me.touchstart(e)}); - $("#finger").on("touchend",function(e){me.touchend(e)}); - $("#finger").on("touchmove",function(e){me.touchmove(e)}); - - // turn off eventual touch events handled by canvas - me.canvas.ontouchstart=null; - me.canvas.ontouchmove=null; - me.canvas.ontouchend=null; - } - me.updateCursor(); + // configure touch events for tablets + $("#finger").on("touchstart",function(e){me.touchstart(e)}); + $("#finger").on("touchend",function(e){me.touchend(e)}); + $("#finger").on("touchmove",function(e){me.touchmove(e)}); + + // turn off eventual touch events handled by canvas + me.canvas.ontouchstart=null; + me.canvas.ontouchmove=null; + me.canvas.ontouchend=null; + } + me.updateCursor(); - $("#finger").css({left:me.Crsr.fx+"px",top:me.Crsr.fy+"px"}); - } else { - // remove precise cursor - $("#finger").remove(); + $("#finger").css({left:me.Crsr.fx+"px",top:me.Crsr.fy+"px"}); + } else { + // remove precise cursor + $("#finger").remove(); - // configure touch events for tablets - me.canvas.ontouchstart=me.touchstart; - me.canvas.ontouchmove=me.touchmove; - me.canvas.ontouchend=me.touchend; - } - }, + // configure touch events for tablets + me.canvas.ontouchstart=me.touchstart; + me.canvas.ontouchmove=me.touchmove; + me.canvas.ontouchend=me.touchend; + } + }, /** * @function updateCursor */ - updateCursor: function updateCursor() { - var me=AtlasMakerWidget; - var l=me.traceLog(updateCursor,1,"#f00");if(l)console.log.apply(undefined,l); + updateCursor: function updateCursor() { + var me=AtlasMakerWidget; + var l=me.traceLog(updateCursor,1,"#f00");if(l)console.log.apply(undefined,l); - $("#finger").removeClass("move draw configure"); - switch(me.Crsr.state) { - case "move": $("#finger").addClass("move"); break; - case "draw": $("#finger").addClass("draw"); break; - case "configure": $("#finger").addClass("configure"); break; - } - }, + $("#finger").removeClass("move draw configure"); + switch(me.Crsr.state) { + case "move": $("#finger").addClass("move"); break; + case "draw": $("#finger").addClass("draw"); break; + case "configure": $("#finger").addClass("configure"); break; + } + }, /** * @function mousedown */ - mousedown: function mousedown(e) { - var me=AtlasMakerWidget; - var l=me.traceLog(mousedown,0,"#f00");if(l)console.log.apply(undefined,l); - - e.preventDefault(); + mousedown: function mousedown(e) { + var me=AtlasMakerWidget; + var l=me.traceLog(mousedown,0,"#f00");if(l)console.log.apply(undefined,l); + + e.preventDefault(); - var W=parseFloat($('#atlasMaker canvas').css('width')); - var H=parseFloat($('#atlasMaker canvas').css('height')); - var w=parseFloat($('#atlasMaker canvas').attr('width')); - var h=parseFloat($('#atlasMaker canvas').attr('height')); - var o=$('#atlasMaker canvas').offset(); - var x=parseInt((e.pageX-o.left)*(w/W)); - // i have to add here the compensation for rectangular pixels: f(brain_Wdim, brain_Hdim) - var y=parseInt((e.pageY-o.top)*(h/H)); - me.down(x,Math.round(y*me.brain_Wdim/me.brain_Hdim)); - }, + var W=parseFloat($('#atlasMaker canvas').css('width')); + var H=parseFloat($('#atlasMaker canvas').css('height')); + var w=parseFloat($('#atlasMaker canvas').attr('width')); + var h=parseFloat($('#atlasMaker canvas').attr('height')); + var o=$('#atlasMaker canvas').offset(); + var x=parseInt((e.pageX-o.left)*(w/W)); + // i have to add here the compensation for rectangular pixels: f(brain_Wdim, brain_Hdim) + var y=parseInt((e.pageY-o.top)*(h/H)); + me.down(x,Math.round(y*me.brain_Wdim/me.brain_Hdim)); + }, /** * @function mousemove * @desc Handles a mouse move event. The x and y slice screens are computed from the pageX and pageY screen coordinates and dispatched to the generic move handler. The position and size of the cursor are adjusted. * @param {Event} e Event object */ - mousemove: function mousemove(e) { - var me=AtlasMakerWidget; - var l=me.traceLog(mousemove,2,"#f00");if(l)console.log.apply(undefined,l); - - e.preventDefault(); - var W=parseFloat($('#atlasMaker canvas').css('width')); - var H=parseFloat($('#atlasMaker canvas').css('height')); - var w=parseFloat($('#atlasMaker canvas').attr('width')); - var h=parseFloat($('#atlasMaker canvas').attr('height')); - var o=$('#atlasMaker canvas').offset(); - var x=parseInt((e.pageX-o.left)*(w/W)); - var y=parseInt((e.pageY-o.top)*(h/H)); - - $("#cursor").css({ - left:(x*(W/w))+'px', - top:(y*(H/h))+'px', - width:me.User.penSize*(W/w), - height:me.User.penSize*(H/h) - }); - me.move(x,Math.round(y*me.brain_Wdim/me.brain_Hdim)); - }, + mousemove: function mousemove(e) { + var me=AtlasMakerWidget; + var l=me.traceLog(mousemove,2,"#f00");if(l)console.log.apply(undefined,l); + + e.preventDefault(); + var W=parseFloat($('#atlasMaker canvas').css('width')); + var H=parseFloat($('#atlasMaker canvas').css('height')); + var w=parseFloat($('#atlasMaker canvas').attr('width')); + var h=parseFloat($('#atlasMaker canvas').attr('height')); + var o=$('#atlasMaker canvas').offset(); + var x=parseInt((e.pageX-o.left)*(w/W)); + var y=parseInt((e.pageY-o.top)*(h/H)); + + $("#cursor").css({ + left:(x*(W/w))+'px', + top:(y*(H/h))+'px', + width:me.User.penSize*(W/w), + height:me.User.penSize*(H/h) + }); + me.move(x,Math.round(y*me.brain_Wdim/me.brain_Hdim)); + }, /** * @function mouseup */ - mouseup: function mouseup(e) { - var me=AtlasMakerWidget; - var l=me.traceLog(mouseup,0,"#f00");if(l)console.log.apply(undefined,l); - - me.up(e); - }, + mouseup: function mouseup(e) { + var me=AtlasMakerWidget; + var l=me.traceLog(mouseup,0,"#f00");if(l)console.log.apply(undefined,l); + + me.up(e); + }, /** * @function touchstart */ - touchstart: function touchstart(e) { - var me=AtlasMakerWidget; - var l=me.traceLog(touchstart,0,"#f00");if(l)console.log.apply(undefined,l); - - e.preventDefault(); + touchstart: function touchstart(e) { + var me=AtlasMakerWidget; + var l=me.traceLog(touchstart,0,"#f00");if(l)console.log.apply(undefined,l); + + e.preventDefault(); - var W=parseFloat($('#atlasMaker canvas').css('width')); - var H=parseFloat($('#atlasMaker canvas').css('height')); - var w=parseFloat($('#atlasMaker canvas').attr('width')); - var h=parseFloat($('#atlasMaker canvas').attr('height')); - var o=$('#atlasMaker canvas').offset(); - var touchEvent; - if(e.originalEvent) - touchEvent=e.originalEvent.changedTouches[0]; - else - touchEvent=e.changedTouches[0]; - var x=parseInt((touchEvent.pageX-o.left)*(w/W)); - var y=parseInt((touchEvent.pageY-o.top)*(h/H)); - - if(me.flagUsePreciseCursor) { - // Precision cursor - me.Crsr.x0=x; - me.Crsr.cachedX=x; - me.Crsr.y0=y; - me.Crsr.cachedY=y; - me.Crsr.fx=$("#finger").offset().left; - me.Crsr.fy=$("#finger").offset().top; - me.Crsr.touchStarted=true; - setTimeout(function() { - if( me.Crsr.cachedX == me.Crsr.x0 && me.Crsr.cachedY==me.Crsr.y0 && !me.Crsr.touchStarted) { - // short tap: change mode - me.Crsr.state=(me.Crsr.state=="move")?"draw":"move"; - me.updateCursor(); - } - },200); - setTimeout(function() { - if (me.Crsr.cachedX==me.Crsr.x0 && me.Crsr.cachedY==me.Crsr.y0 && me.Crsr.touchStarted) { - // long tap: change to configure mode - me.Crsr.prevState=me.Crsr.state; - me.Crsr.state="configure"; - me.updateCursor(); - } - },1000); - me.down(me.Crsr.x,Math.round(me.Crsr.y*me.brain_Wdim/me.brain_Hdim)); - } else - me.down(x,Math.round(y*me.brain_Wdim/me.brain_Hdim)); - }, + var W=parseFloat($('#atlasMaker canvas').css('width')); + var H=parseFloat($('#atlasMaker canvas').css('height')); + var w=parseFloat($('#atlasMaker canvas').attr('width')); + var h=parseFloat($('#atlasMaker canvas').attr('height')); + var o=$('#atlasMaker canvas').offset(); + var touchEvent; + if(e.originalEvent) + touchEvent=e.originalEvent.changedTouches[0]; + else + touchEvent=e.changedTouches[0]; + var x=parseInt((touchEvent.pageX-o.left)*(w/W)); + var y=parseInt((touchEvent.pageY-o.top)*(h/H)); + + if(me.flagUsePreciseCursor) { + // Precision cursor + me.Crsr.x0=x; + me.Crsr.cachedX=x; + me.Crsr.y0=y; + me.Crsr.cachedY=y; + me.Crsr.fx=$("#finger").offset().left; + me.Crsr.fy=$("#finger").offset().top; + me.Crsr.touchStarted=true; + setTimeout(function() { + if( me.Crsr.cachedX == me.Crsr.x0 && me.Crsr.cachedY==me.Crsr.y0 && !me.Crsr.touchStarted) { + // short tap: change mode + me.Crsr.state=(me.Crsr.state=="move")?"draw":"move"; + me.updateCursor(); + } + },200); + setTimeout(function() { + if (me.Crsr.cachedX==me.Crsr.x0 && me.Crsr.cachedY==me.Crsr.y0 && me.Crsr.touchStarted) { + // long tap: change to configure mode + me.Crsr.prevState=me.Crsr.state; + me.Crsr.state="configure"; + me.updateCursor(); + } + },1000); + me.down(me.Crsr.x,Math.round(me.Crsr.y*me.brain_Wdim/me.brain_Hdim)); + } else + me.down(x,Math.round(y*me.brain_Wdim/me.brain_Hdim)); + }, /** * @function touchmove */ - touchmove: function touchmove(e) { - var me=AtlasMakerWidget; - var l=me.traceLog(touchmove,2,"#f00");if(l)console.log.apply(undefined,l); - - if(me.Crsr.touchStarted==false && me.debug) { - console.log("WARNING: touch can move without having started"); - } - - e.preventDefault(); + touchmove: function touchmove(e) { + var me=AtlasMakerWidget; + var l=me.traceLog(touchmove,2,"#f00");if(l)console.log.apply(undefined,l); + + if(me.Crsr.touchStarted==false && me.debug) { + console.log("WARNING: touch can move without having started"); + } + + e.preventDefault(); - var W=parseFloat($('#atlasMaker canvas').css('width')); - var H=parseFloat($('#atlasMaker canvas').css('height')); - var w=parseFloat($('#atlasMaker canvas').attr('width')); - var h=parseFloat($('#atlasMaker canvas').attr('height')); - var o=$('#atlasMaker canvas').offset(); - var touchEvent; - if(e.originalEvent) - touchEvent=e.originalEvent.changedTouches[0]; - else - touchEvent=e.changedTouches[0]; - var x=parseInt((touchEvent.pageX-o.left)*(w/W)); - var y=parseInt((touchEvent.pageY-o.top)*(h/H)); - - if(me.flagUsePreciseCursor) { - // Precision cursor - var dx=x-me.Crsr.x0; - var dy=y-me.Crsr.y0; - if(me.Crsr.state=="move"||me.Crsr.state=="draw") { - me.Crsr.x+=dx; - me.Crsr.y+=dy; - $("#cursor").css({left:me.Crsr.x*(W/w),top:me.Crsr.y*(H/h),width:me.User.penSize*(W/w),height:me.User.penSize*(H/h)}); - if(me.Crsr.state=="draw") - me.move(me.Crsr.x,Math.round(me.Crsr.y*me.brain_Wdim/me.brain_Hdim)); - } - me.Crsr.fx+=dx*(W/w); - me.Crsr.fy+=dy*(H/h); - $("#finger").offset({left:me.Crsr.fx,top:me.Crsr.fy}); - - me.Crsr.x0=x; - me.Crsr.y0=y; - } else { - $("#cursor").css({ - left:(x*(W/w))+'px', - top:(y*(H/h))+'px', - width:me.User.penSize*(W/w), - height:me.User.penSize*(H/h) - }); - me.move(x,Math.round(y*me.brain_Wdim/me.brain_Hdim)); - } - }, + var W=parseFloat($('#atlasMaker canvas').css('width')); + var H=parseFloat($('#atlasMaker canvas').css('height')); + var w=parseFloat($('#atlasMaker canvas').attr('width')); + var h=parseFloat($('#atlasMaker canvas').attr('height')); + var o=$('#atlasMaker canvas').offset(); + var touchEvent; + if(e.originalEvent) + touchEvent=e.originalEvent.changedTouches[0]; + else + touchEvent=e.changedTouches[0]; + var x=parseInt((touchEvent.pageX-o.left)*(w/W)); + var y=parseInt((touchEvent.pageY-o.top)*(h/H)); + + if(me.flagUsePreciseCursor) { + // Precision cursor + var dx=x-me.Crsr.x0; + var dy=y-me.Crsr.y0; + if(me.Crsr.state=="move"||me.Crsr.state=="draw") { + me.Crsr.x+=dx; + me.Crsr.y+=dy; + $("#cursor").css({left:me.Crsr.x*(W/w),top:me.Crsr.y*(H/h),width:me.User.penSize*(W/w),height:me.User.penSize*(H/h)}); + if(me.Crsr.state=="draw") + me.move(me.Crsr.x,Math.round(me.Crsr.y*me.brain_Wdim/me.brain_Hdim)); + } + me.Crsr.fx+=dx*(W/w); + me.Crsr.fy+=dy*(H/h); + $("#finger").offset({left:me.Crsr.fx,top:me.Crsr.fy}); + + me.Crsr.x0=x; + me.Crsr.y0=y; + } else { + $("#cursor").css({ + left:(x*(W/w))+'px', + top:(y*(H/h))+'px', + width:me.User.penSize*(W/w), + height:me.User.penSize*(H/h) + }); + me.move(x,Math.round(y*me.brain_Wdim/me.brain_Hdim)); + } + }, /** * @function touchend */ - touchend: function touchend(e) { - var me=AtlasMakerWidget; - var l=me.traceLog(touchend,0,"#f00");if(l)console.log.apply(undefined,l); - - e.preventDefault(); - - if(me.flagUsePreciseCursor) { - // Precision cursor - me.Crsr.touchStarted=false; - if(me.Crsr.state=="configure") { - me.Crsr.state=me.Crsr.prevState; - me.updateCursor(); - } - } - me.up(e); - }, + touchend: function touchend(e) { + var me=AtlasMakerWidget; + var l=me.traceLog(touchend,0,"#f00");if(l)console.log.apply(undefined,l); + + e.preventDefault(); + + if(me.flagUsePreciseCursor) { + // Precision cursor + me.Crsr.touchStarted=false; + if(me.Crsr.state=="configure") { + me.Crsr.state=me.Crsr.prevState; + me.updateCursor(); + } + } + me.up(e); + }, /** * @function down * @desc Generic pointer down event: Deals with down events generated by mouse clicks or touch events. The effect of the down event is determined by the current User.tool * @param {integer} x X coordinate in slice space * @param {integer} y Y coordinate in slice space */ - down: function down(x,y) { - var me=AtlasMakerWidget; - var l=me.traceLog(down,0,"#f00");if(l)console.log.apply(undefined,l); - - var z=me.User.slice; - - switch(me.User.tool) { - case 'show': + down: function down(x,y) { + var me=AtlasMakerWidget; + var l=me.traceLog(down,0,"#f00");if(l)console.log.apply(undefined,l); + + var z=me.User.slice; + + switch(me.User.tool) { + case 'show': me.User.mouseIsDown = true; me.sendUserDataMessage(JSON.stringify({'mouseIsDown':true})); me.showxy(-1,'m',x,y,me.User); - break; - case 'paint': - // check for 'edit' access - if(me.editMode == 0) - return; - // fill - if(me.User.doFill) - me.paintxy(-1,'f',x,y,me.User); - //paint - else { - me.User.mouseIsDown = true; - me.sendUserDataMessage(JSON.stringify({'mouseIsDown':true})); - me.paintxy(-1,'mf',x,y,me.User); - } - break; - case 'erase': - // check for 'edit' access - if(me.editMode == 0) - return; - // fill - if(me.User.doFill) - me.paintxy(-1,'e',x,y,me.User); + break; + case 'paint': + // check for 'edit' access + if(me.editMode == 0) + return; + // fill + if(me.User.doFill) + me.paintxy(-1,'f',x,y,me.User); + //paint + else { + me.User.mouseIsDown = true; + me.sendUserDataMessage(JSON.stringify({'mouseIsDown':true})); + me.paintxy(-1,'mf',x,y,me.User); + } + break; + case 'erase': + // check for 'edit' access + if(me.editMode == 0) + return; + // fill + if(me.User.doFill) + me.paintxy(-1,'e',x,y,me.User); // erase - else { - me.User.mouseIsDown = true; - me.sendUserDataMessage(JSON.stringify({'mouseIsDown':true})); - me.paintxy(-1,'me',x,y,me.User); - } - break; - case 'measure': - if(me.User.measureLength==null) - me.User.measureLength=[{x:x,y:y}]; - else - me.User.measureLength.push({x:x,y:y}); - break; - case 'adjust': - me.User.mouseIsDown = true; - me.info.x=x/me.brain_W; - me.info.y=1-y/me.brain_H; - break; - case 'eyedrop': - var value = me.eyedrop( x,y,me.User ); - var index = me.ontology.valueToIndex[ value ]; - var selRegionName = me.ontology.labels[ index ].name; - me.info.region = selRegionName; - me.changePenColor( index ); - var selRegionColor = me.ontology.labels[ index ].color; - break; - } - - // init annotation length counter - me.annotationLength=0; - }, + else { + me.User.mouseIsDown = true; + me.sendUserDataMessage(JSON.stringify({'mouseIsDown':true})); + me.paintxy(-1,'me',x,y,me.User); + } + break; + case 'measure': + if(me.User.measureLength==null) + me.User.measureLength=[{x:x,y:y}]; + else + me.User.measureLength.push({x:x,y:y}); + break; + case 'adjust': + me.User.mouseIsDown = true; + me.info.x=x/me.brain_W; + me.info.y=1-y/me.brain_H; + break; + case 'eyedrop': + var value = me.eyedrop( x,y,me.User ); + var index = me.ontology.valueToIndex[ value ]; + var selRegionName = me.ontology.labels[ index ].name; + me.info.region = selRegionName; + me.changePenColor( index ); + var selRegionColor = me.ontology.labels[ index ].color; + break; + } + + // init annotation length counter + me.annotationLength=0; + }, /** * @function move * @desc Generic pointer move event: Deals with move events generated by mouse clicks or touch events. The effect of the move event is determined by the current User.tool * @param {integer} x X coordinate in slice space * @param {integer} y Y coordinate in slice space */ - move: function move(x,y) { - var me=AtlasMakerWidget; - var l=me.traceLog(move,2,"#f00");if(l)console.log.apply(undefined,l); - - /* - if(MyLoginWidget.loggedin==0 || me.editMode==0) - return; - */ + move: function move(x,y) { + var me=AtlasMakerWidget; + var l=me.traceLog(move,2,"#f00");if(l)console.log.apply(undefined,l); + + /* + if(MyLoginWidget.loggedin==0 || me.editMode==0) + return; + */ - var z=me.User.slice; + var z=me.User.slice; - if(!me.User.mouseIsDown) - return; - - switch(me.User.tool) { - case 'show': - me.showxy(-1,'m',x,y,me.User); - break; - case 'paint': - me.paintxy(-1,'lf',x,y,me.User); - break; - case 'erase': - me.paintxy(-1,'le',x,y,me.User); - break; - case 'adjust': - me.info.x=x/me.brain_W; - me.info.y=1-y/me.brain_H; - me.drawImages(); - break; - } - }, + if(!me.User.mouseIsDown) + return; + + switch(me.User.tool) { + case 'show': + me.showxy(-1,'m',x,y,me.User); + break; + case 'paint': + me.paintxy(-1,'lf',x,y,me.User); + break; + case 'erase': + me.paintxy(-1,'le',x,y,me.User); + break; + case 'adjust': + me.info.x=x/me.brain_W; + me.info.y=1-y/me.brain_H; + me.drawImages(); + break; + } + }, /** * @function up * @desc Generic pointer up event: Deals with up events generated by mouse clicks or touch events. * @param {integer} x X coordinate in slice space * @param {integer} y Y coordinate in slice space */ - up: function up(e) { - var me=AtlasMakerWidget; - var l=me.traceLog(up,0,"#f00");if(l)console.log.apply(undefined,l); + up: function up(e) { + var me=AtlasMakerWidget; + var l=me.traceLog(up,0,"#f00");if(l)console.log.apply(undefined,l); - /* - if(MyLoginWidget.loggedin==0 || me.editMode==0) - return; - */ + /* + if(MyLoginWidget.loggedin==0 || me.editMode==0) + return; + */ - // Send mouse up (touch ended) message - me.User.mouseIsDown = false; - me.User.x0=-1; + // Send mouse up (touch ended) message + me.User.mouseIsDown = false; + me.User.x0=-1; me.sendUserDataMessage(JSON.stringify({'mouseIsDown':false})); - - var msg; - - switch(me.User.tool) { - case 'show': + + var msg; + switch(me.User.tool) { + case 'show': var msg={"c":"u"}; me.sendShowMessage(msg); break; @@ -761,7 +760,7 @@ var AtlasMakerInteraction = { case 'erase': var msg={c:"mu"}; me.sendPaintMessage(msg); - + // add annotated length to User.annotation length and post to DB me.logToDatabase("annotationLength",{ source:me.User.source, @@ -773,78 +772,72 @@ var AtlasMakerInteraction = { me.info.length = length+" mm"; me.displayInformation(); }); - + me.annotationLength=0; - + // compute total segmented volume var vol=me.computeSegmentedVolume(); me.info.volume=parseInt(vol)+" mm3"; break; - case 'eyedrop': - me.displayInformation(); - - var msg={"c":"mu"}; - me.sendPaintMessage(msg); - break; default: var msg={"c":"mu"}; me.sendPaintMessage(msg); } - - /* - TEST - */ - //me.sendRequestSliceMessage(); - }, + + /* + TEST + */ + //me.sendRequestSliceMessage(); + }, /** * @function keyDown */ - keyDown: function keyDown(e) { - var me=AtlasMakerWidget; - var l=me.traceLog(keyDown,2,"#f00");if(l)console.log.apply(undefined,l); - - // console.log("key:",e.which); - - if(e.which==13 && $(e.target).attr('contenteditable')) { + keyDown: function keyDown(e) { + var me=AtlasMakerWidget; + var l=me.traceLog(keyDown,2,"#f00");if(l)console.log.apply(undefined,l); + + // console.log("key:",e.which); + + if(e.which==13 && $(e.target).attr('contenteditable')) { e.preventDefault(); return; } - - if(e.target.tagName!="BODY") - return; - - switch(e.which) { - case 13: // return - if(me.User.measureLength) { - var length=0; - var p=me.User.measureLength; - var wdim=me.brain_Wdim,hdim=me.brain_Hdim; - var i; - for(i=1;i"); - me.User.measureLength=null; - } - break; - case 37: // left arrow - me.prevSlice(); - e.preventDefault(); - break; - case 39: // right arrow - me.nextSlice(this); - e.preventDefault(); - break; - } - }, + + if(e.target.tagName!="BODY") + return; + + switch(e.which) { + case 13: // return + if(me.User.measureLength) { + var length=0; + var p=me.User.measureLength; + var wdim=me.brain_Wdim,hdim=me.brain_Hdim; + var i; + for(i=1;i"); + me.User.measureLength=null; + } + break; + case 37: // left arrow + me.prevSlice(); + e.preventDefault(); + break; + case 39: // right arrow + me.nextSlice(this); + e.preventDefault(); + break; + } + }, /** * @function onkey */ - onkey: function onkey(e) { - var me=AtlasMakerWidget; - var l=me.traceLog(onkey,2,"#f00");if(l)console.log.apply(undefined,l); - - if (e.keyCode == 13) { - me.sendChatMessage(); - } - } -} + onkey: function onkey(e) { + var me=AtlasMakerWidget; + var l=me.traceLog(onkey,2,"#f00");if(l)console.log.apply(undefined,l); + + if (e.keyCode == 13) { + me.sendChatMessage(); + } + } +} \ No newline at end of file diff --git a/public/js/atlasMaker-io.js b/public/js/atlasMaker-io.js index 78c36124..b93c08fa 100644 --- a/public/js/atlasMaker-io.js +++ b/public/js/atlasMaker-io.js @@ -15,37 +15,37 @@ var AtlasMakerIO = { .floatle('intent_p2') // 2nd intent parameter. .floatle('intent_p3') // 3rd intent parameter. .word16Sle('intent_code') // nifti intent. - .word16Sle('datatype') // Data type. - .word16Sle('bitpix') // Number of bits per voxel. - .word16Sle('slice_start') // First slice index. + .word16Sle('datatype') // Data type. + .word16Sle('bitpix') // Number of bits per voxel. + .word16Sle('slice_start') // First slice index. .array('pixdim',8,'floatle') // Grid spacings (unit per dimension). - .floatle('vox_offset') // Offset into a .nii file. - .floatle('scl_slope') // Data scaling, slope. - .floatle('scl_inter') // Data scaling, offset. - .word16Sle('slice_end') // Last slice index. - .word8('slice_code') // Slice timing order. - .word8('xyzt_units') // Units of pixdim[1..4]. - .floatle('cal_max') // Maximum display intensity. - .floatle('cal_min') // Minimum display intensity. - .floatle('slice_duration') // Time for one slice. - .floatle('toffset') // Time axis shift. - .word32Sle('glmax') // Not used; compatibility with analyze. - .word32Sle('glmin') // Not used; compatibility with analyze. - .chars('descrip',80) // Any text. - .chars('aux_file',24) // Auxiliary filename. - .word16Sle('qform_code') // Use the quaternion fields. - .word16Sle('sform_code') // Use of the affine fields. - .floatle('quatern_b') // Quaternion b parameter. - .floatle('quatern_c') // Quaternion c parameter. - .floatle('quatern_d') // Quaternion d parameter. - .floatle('qoffset_x') // Quaternion x shift. - .floatle('qoffset_y') // Quaternion y shift. - .floatle('qoffset_z') // Quaternion z shift. + .floatle('vox_offset') // Offset into a .nii file. + .floatle('scl_slope') // Data scaling, slope. + .floatle('scl_inter') // Data scaling, offset. + .word16Sle('slice_end') // Last slice index. + .word8('slice_code') // Slice timing order. + .word8('xyzt_units') // Units of pixdim[1..4]. + .floatle('cal_max') // Maximum display intensity. + .floatle('cal_min') // Minimum display intensity. + .floatle('slice_duration') // Time for one slice. + .floatle('toffset') // Time axis shift. + .word32Sle('glmax') // Not used; compatibility with analyze. + .word32Sle('glmin') // Not used; compatibility with analyze. + .chars('descrip',80) // Any text. + .chars('aux_file',24) // Auxiliary filename. + .word16Sle('qform_code') // Use the quaternion fields. + .word16Sle('sform_code') // Use of the affine fields. + .floatle('quatern_b') // Quaternion b parameter. + .floatle('quatern_c') // Quaternion c parameter. + .floatle('quatern_d') // Quaternion d parameter. + .floatle('qoffset_x') // Quaternion x shift. + .floatle('qoffset_y') // Quaternion y shift. + .floatle('qoffset_z') // Quaternion z shift. .array('srow_x',4,'floatle') // 1st row affine transform .array('srow_y',4,'floatle') // 2nd row affine transform. .array('srow_z',4,'floatle') // 3rd row affine transform. - .chars('intent_name',16) // Name or meaning of the data. - .chars('magic',4), // Magic string. + .chars('intent_name',16) // Name or meaning of the data. + .chars('magic',4), // Magic string. NiiHdrBE: Struct() .word32Sbe('sizeof_hdr') // Size of the header. Must be 348 (bytes) .chars('data_type',10) // Not used; compatibility with analyze. @@ -59,37 +59,37 @@ var AtlasMakerIO = { .floatbe('intent_p2') // 2nd intent parameter. .floatbe('intent_p3') // 3rd intent parameter. .word16Sbe('intent_code') // nifti intent. - .word16Sbe('datatype') // Data type. - .word16Sbe('bitpix') // Number of bits per voxel. - .word16Sbe('slice_start') // First slice index. + .word16Sbe('datatype') // Data type. + .word16Sbe('bitpix') // Number of bits per voxel. + .word16Sbe('slice_start') // First slice index. .array('pixdim',8,'floatbe') // Grid spacings (unit per dimension). - .floatbe('vox_offset') // Offset into a .nii file. - .floatbe('scl_slope') // Data scaling, slope. - .floatbe('scl_inter') // Data scaling, offset. - .word16Sbe('slice_end') // Last slice index. - .word8('slice_code') // Slice timing order. - .word8('xyzt_units') // Units of pixdim[1..4]. - .floatbe('cal_max') // Maximum display intensity. - .floatbe('cal_min') // Minimum display intensity. - .floatbe('slice_duration') // Time for one slice. - .floatbe('toffset') // Time axis shift. - .word32Sbe('glmax') // Not used; compatibility with analyze. - .word32Sbe('glmin') // Not used; compatibility with analyze. - .chars('descrip',80) // Any text. - .chars('aux_file',24) // Auxiliary filename. - .word16Sbe('qform_code') // Use the quaternion fields. - .word16Sbe('sform_code') // Use of the affine fields. - .floatbe('quatern_b') // Quaternion b parameter. - .floatbe('quatern_c') // Quaternion c parameter. - .floatbe('quatern_d') // Quaternion d parameter. - .floatbe('qoffset_x') // Quaternion x shift. - .floatbe('qoffset_y') // Quaternion y shift. - .floatbe('qoffset_z') // Quaternion z shift. + .floatbe('vox_offset') // Offset into a .nii file. + .floatbe('scl_slope') // Data scaling, slope. + .floatbe('scl_inter') // Data scaling, offset. + .word16Sbe('slice_end') // Last slice index. + .word8('slice_code') // Slice timing order. + .word8('xyzt_units') // Units of pixdim[1..4]. + .floatbe('cal_max') // Maximum display intensity. + .floatbe('cal_min') // Minimum display intensity. + .floatbe('slice_duration') // Time for one slice. + .floatbe('toffset') // Time axis shift. + .word32Sbe('glmax') // Not used; compatibility with analyze. + .word32Sbe('glmin') // Not used; compatibility with analyze. + .chars('descrip',80) // Any text. + .chars('aux_file',24) // Auxiliary filename. + .word16Sbe('qform_code') // Use the quaternion fields. + .word16Sbe('sform_code') // Use of the affine fields. + .floatbe('quatern_b') // Quaternion b parameter. + .floatbe('quatern_c') // Quaternion c parameter. + .floatbe('quatern_d') // Quaternion d parameter. + .floatbe('qoffset_x') // Quaternion x shift. + .floatbe('qoffset_y') // Quaternion y shift. + .floatbe('qoffset_z') // Quaternion z shift. .array('srow_x',4,'floatbe') // 1st row affine transform .array('srow_y',4,'floatbe') // 2nd row affine transform. .array('srow_z',4,'floatbe') // 3rd row affine transform. - .chars('intent_name',16) // Name or meaning of the data. - .chars('magic',4), // Magic string. + .chars('intent_name',16) // Name or meaning of the data. + .chars('magic',4), // Magic string. MghHdr: Struct() .word32Sbe('v') .word32Sbe('ndim1') @@ -107,17 +107,17 @@ var AtlasMakerIO = { /** * @function encodeNifti */ - encodeNifti: function encodeNifti() { - var me=AtlasMakerWidget; - var l=me.traceLog(encodeNifti);if(l)console.log.apply(undefined,l); + encodeNifti: function encodeNifti() { + var me=AtlasMakerWidget; + var l=me.traceLog(encodeNifti);if(l)console.log.apply(undefined,l); - var sizeof_hdr=348; - var dimensions=4; // number of dimension values provided - var spacetimeunits=2+8; // 2=nifti code for millimetres | 8=nifti code for seconds - var datatype=2; // datatype for 8 bits (DT_UCHAR8 in nifti or UCHAR in analyze) - var vox_offset=352; - var bitsPerVoxel=8; - + var sizeof_hdr=348; + var dimensions=4; // number of dimension values provided + var spacetimeunits=2+8; // 2=nifti code for millimetres | 8=nifti code for seconds + var datatype=2; // datatype for 8 bits (DT_UCHAR8 in nifti or UCHAR in analyze) + var vox_offset=352; + var bitsPerVoxel=8; + var newHdr = { sizeof_hdr: sizeof_hdr, data_type: '', db_name: '', extents: 0, session_error: 0, regular: 0, dim_info: 0, @@ -128,7 +128,7 @@ var AtlasMakerIO = { slice_start: 0, pixdim: [-1, me.User.pixdim[0], me.User.pixdim[1], me.User.pixdim[2], 0, 1, 1, 1], vox_offset: vox_offset, - scl_slope: 1, scl_inter: 0, slice_end: 0, slice_code: 0, + scl_slope: 0, scl_inter: 0, slice_end: 0, slice_code: 0, xyzt_units: 10, cal_max: 0, cal_min: 0, slice_duration: 0, toffset: 0, glmax: 0, glmin: 0, @@ -149,66 +149,66 @@ var AtlasMakerIO = { for(i in newHdr) me.NiiHdrLE.fields[i] = newHdr[i]; hdr = toArrayBuffer(niihdr); - var data=me.atlas.data; + var data=me.atlas.data; var nii = new Uint8Array(vox_offset+data.length); for(i=0;iMath.abs(mi.v)) mi={i:n,v:o}}); var mj={i:0,v:0};j.map(function(o,n){if(Math.abs(o)>Math.abs(mj.v)) mj={i:n,v:o}}); var mk={i:0,v:0};k.map(function(o,n){if(Math.abs(o)>Math.abs(mk.v)) mk={i:n,v:o}}); - mri.s2v = { - // old s2v fields - s2w: me.invMat(w2s), - sdim: [], - sori: [-wmin[0]/Math.abs(wpixdim[0]),-wmin[1]/Math.abs(wpixdim[1]),-wmin[2]/Math.abs(wpixdim[2])], - wpixdim: [], - w2v: me.invMat(v2w), + mri.s2v = { + // old s2v fields + s2w: me.invMat(w2s), + sdim: [], + sori: [-wmin[0]/Math.abs(wpixdim[0]),-wmin[1]/Math.abs(wpixdim[1]),-wmin[2]/Math.abs(wpixdim[2])], + wpixdim: [], + w2v: me.invMat(v2w), wori: wori, // new s2v transformation @@ -317,7 +317,7 @@ var AtlasMakerIO = { X: (mi.v>0)?0:(mri.dim[0]-1), // starting value for space coordinate x when voxel coordinate i starts Y: (mj.v>0)?0:(mri.dim[1]-1), // same for y Z: (mk.v>0)?0:(mri.dim[2]-1) // same for z - }; + }; mri.v2w=v2w; mri.wori=wori; mri.s2v.sdim[mi.i] = mri.dim[0]; @@ -326,119 +326,119 @@ var AtlasMakerIO = { mri.s2v.wpixdim[mi.i] = mri.pixdim[0]; mri.s2v.wpixdim[mj.i] = mri.pixdim[1]; mri.s2v.wpixdim[mk.i] = mri.pixdim[2]; - }, + }, /** * @function testS2VTransformation */ - testS2VTransformation: function testS2VTransformation() { - var me=AtlasMakerWidget; - var l=me.traceLog(testS2VTransformation);if(l)console.log.apply(undefined,l); - - /* - check the S2V transformation to see if it looks correct. - If it does not, reset it - */ - var mri=me.User; // this line is different from server - var doReset=false; - - // console.log("Transformation TEST:"); - // console.log(" 1. transformation volume"); - - var vv=mri.dim[0]*mri.dim[1]*mri.dim[2]; - var vs=mri.s2v.sdim[0]*mri.s2v.sdim[1]*mri.s2v.sdim[2]; - var diff=(vs-vv)/vv; - if(Math.abs(diff)>0.001) { - console.log(" ERROR: Difference is too large"); - console.log(" original volume:",vv); - console.log(" rotated volume:",vs); - console.log(" % difference:",diff*100); - doReset=true; - } else { - // console.log(" ok."); - } - - // console.log(" 2. transformation origin"); - if( mri.s2v.sori[0]<0||mri.s2v.sori[0]>mri.s2v.sdim[0] || - mri.s2v.sori[1]<0||mri.s2v.sori[1]>mri.s2v.sdim[1] || - mri.s2v.sori[2]<0||mri.s2v.sori[2]>mri.s2v.sdim[2]) { - // console.log(" Origin point is outside the dimensions of the data"); - doReset=true; - } else { - // console.log(" ok."); - } + testS2VTransformation: function testS2VTransformation() { + var me=AtlasMakerWidget; + var l=me.traceLog(testS2VTransformation);if(l)console.log.apply(undefined,l); + + /* + check the S2V transformation to see if it looks correct. + If it does not, reset it + */ + var mri=me.User; // this line is different from server + var doReset=false; + + // console.log("Transformation TEST:"); + // console.log(" 1. transformation volume"); + + var vv=mri.dim[0]*mri.dim[1]*mri.dim[2]; + var vs=mri.s2v.sdim[0]*mri.s2v.sdim[1]*mri.s2v.sdim[2]; + var diff=(vs-vv)/vv; + if(Math.abs(diff)>0.001) { + console.log(" ERROR: Difference is too large"); + console.log(" original volume:",vv); + console.log(" rotated volume:",vs); + console.log(" % difference:",diff*100); + doReset=true; + } else { + // console.log(" ok."); + } + + // console.log(" 2. transformation origin"); + if( mri.s2v.sori[0]<0||mri.s2v.sori[0]>mri.s2v.sdim[0] || + mri.s2v.sori[1]<0||mri.s2v.sori[1]>mri.s2v.sdim[1] || + mri.s2v.sori[2]<0||mri.s2v.sori[2]>mri.s2v.sdim[2]) { + // console.log(" Origin point is outside the dimensions of the data"); + doReset=true; + } else { + // console.log(" ok."); + } - if(doReset) { - // console.log("THE TRANSFORMATION WILL BE RESET"); - mri.v2w=[[mri.pixdim[0],0,0],[0,-mri.pixdim[1],0],[0,0,-mri.pixdim[2]]]; - mri.wori=[0,mri.dim[1]-1,mri.dim[2]-1]; + if(doReset) { + // console.log("THE TRANSFORMATION WILL BE RESET"); + mri.v2w=[[mri.pixdim[0],0,0],[0,-mri.pixdim[1],0],[0,0,-mri.pixdim[2]]]; + mri.wori=[0,mri.dim[1]-1,mri.dim[2]-1]; - // re-compute the transformation from voxel space to screen space - me.computeS2VTransformation(); // this line is different from server - /* - console.log(mri.dir); - console.log(mri.ori); - console.log(mri.s2v); - */ - } - }, + // re-compute the transformation from voxel space to screen space + me.computeS2VTransformation(); // this line is different from server + /* + console.log(mri.dir); + console.log(mri.ori); + console.log(mri.s2v); + */ + } + }, /** * @function S2I */ - S2I: function S2I(s,mri) { - var me=AtlasMakerWidget; - var l=me.traceLog(S2I,3);if(l)console.log.apply(undefined,l); - + S2I: function S2I(s,mri) { + var me=AtlasMakerWidget; + var l=me.traceLog(S2I,3);if(l)console.log.apply(undefined,l); + var s2v = mri.s2v; var v = [s2v.X+s2v.dx*s[s2v.x],s2v.Y+s2v.dy*s[s2v.y],s2v.Z+s2v.dz*s[s2v.z]]; index = v[0] + v[1]*mri.dim[0] + v[2]*mri.dim[0]*mri.dim[1]; return index; - }, + }, /** * @function mulMatVec */ - mulMatVec: function mulMatVec(m,v) { - return [ - m[0][0]*v[0]+m[0][1]*v[1]+m[0][2]*v[2], - m[1][0]*v[0]+m[1][1]*v[1]+m[1][2]*v[2], - m[2][0]*v[0]+m[2][1]*v[1]+m[2][2]*v[2] - ]; - }, + mulMatVec: function mulMatVec(m,v) { + return [ + m[0][0]*v[0]+m[0][1]*v[1]+m[0][2]*v[2], + m[1][0]*v[0]+m[1][1]*v[1]+m[1][2]*v[2], + m[2][0]*v[0]+m[2][1]*v[1]+m[2][2]*v[2] + ]; + }, /** * @function invMat */ - invMat: function invMat(m) { - var det; - var w=[[],[],[]]; + invMat: function invMat(m) { + var det; + var w=[[],[],[]]; - det=m[0][1]*m[1][2]*m[2][0] + m[0][2]*m[1][0]*m[2][1] + m[0][0]*m[1][1]*m[2][2] - m[0][2]*m[1][1]*m[2][0] - m[0][0]*m[1][2]*m[2][1] - m[0][1]*m[1][0]*m[2][2]; - - w[0][0]=(m[1][1]*m[2][2] - m[1][2]*m[2][1])/det; - w[0][1]=(m[0][2]*m[2][1] - m[0][1]*m[2][2])/det; - w[0][2]=(m[0][1]*m[1][2] - m[0][2]*m[1][1])/det; - - w[1][0]=(m[1][2]*m[2][0] - m[1][0]*m[2][2])/det; - w[1][1]=(m[0][0]*m[2][2] - m[0][2]*m[2][0])/det; - w[1][2]=(m[0][2]*m[1][0] - m[0][0]*m[1][2])/det; - - w[2][0]=(m[1][0]*m[2][1] - m[1][1]*m[2][0])/det; - w[2][1]=(m[0][1]*m[2][0] - m[0][0]*m[2][1])/det; - w[2][2]=(m[0][0]*m[1][1] - m[0][1]*m[1][0])/det; - - return w; - }, + det=m[0][1]*m[1][2]*m[2][0] + m[0][2]*m[1][0]*m[2][1] + m[0][0]*m[1][1]*m[2][2] - m[0][2]*m[1][1]*m[2][0] - m[0][0]*m[1][2]*m[2][1] - m[0][1]*m[1][0]*m[2][2]; + + w[0][0]=(m[1][1]*m[2][2] - m[1][2]*m[2][1])/det; + w[0][1]=(m[0][2]*m[2][1] - m[0][1]*m[2][2])/det; + w[0][2]=(m[0][1]*m[1][2] - m[0][2]*m[1][1])/det; + + w[1][0]=(m[1][2]*m[2][0] - m[1][0]*m[2][2])/det; + w[1][1]=(m[0][0]*m[2][2] - m[0][2]*m[2][0])/det; + w[1][2]=(m[0][2]*m[1][0] - m[0][0]*m[1][2])/det; + + w[2][0]=(m[1][0]*m[2][1] - m[1][1]*m[2][0])/det; + w[2][1]=(m[0][1]*m[2][0] - m[0][0]*m[2][1])/det; + w[2][2]=(m[0][0]*m[1][1] - m[0][1]*m[1][0])/det; + + return w; + }, /** * @function subVecVec */ - subVecVec: function subVecVec(a,b) { - return [a[0]-b[0],a[1]-b[1],a[2]-b[2]]; - }, + subVecVec: function subVecVec(a,b) { + return [a[0]-b[0],a[1]-b[1],a[2]-b[2]]; + }, /** * @function addVecVec */ - addVecVec: function addVecVec(a,b) { - return [a[0]+b[0],a[1]+b[1],a[2]+b[2]]; - }, - /* - Linear Algebra} - */ + addVecVec: function addVecVec(a,b) { + return [a[0]+b[0],a[1]+b[1],a[2]+b[2]]; + }, + /* + Linear Algebra} + */ }; \ No newline at end of file diff --git a/public/js/atlasMaker-paint.js b/public/js/atlasMaker-paint.js index a8eb34b6..b19ae2af 100644 --- a/public/js/atlasMaker-paint.js +++ b/public/js/atlasMaker-paint.js @@ -2,41 +2,41 @@ * @page AtlasMaker: Painting commands */ var AtlasMakerPaint = { - //==================================================================================== - // Paint functions - //==================================================================================== - showxy: function showxy(u,c,x,y,usr) { - var me=AtlasMakerWidget; - var l=me.traceLog(showxy,1,"#0c0");if(l)console.log.apply(undefined,l); - - // u: user number - // c: command - // x, y: coordinates - msg={c: c, x: x, y: y}; - if(u==-1 && JSON.stringify(msg)!=JSON.stringify(me.msg0)) { - me.sendShowMessage(msg); - me.msg0=msg; - } - if(u!=-1) { - switch(c) { - case 'u': - if(usr.pointer) { + //==================================================================================== + // Paint functions + //==================================================================================== + showxy: function showxy(u,c,x,y,usr) { + var me=AtlasMakerWidget; + var l=me.traceLog(showxy,1,"#0c0");if(l)console.log.apply(undefined,l); + + // u: user number + // c: command + // x, y: coordinates + msg={c: c, x: x, y: y}; + if(u==-1 && JSON.stringify(msg)!=JSON.stringify(me.msg0)) { + me.sendShowMessage(msg); + me.msg0=msg; + } + if(u!=-1) { + switch(c) { + case 'u': + if(usr.pointer) { usr.pointer.remove(); delete usr.pointer; } - break; - case 'm': + break; + case 'm': if(!usr.pointer) { usr.pointer = $('
'+((usr.username == 'Anonymous')?u:usr.username)+'
'); $("#resizable").append(usr.pointer); } usr.pointer.css({left:x*$("#resizable").width()/me.brain_W,top:y*$("#resizable").height()/me.brain_H}); - break; - } - } - usr.x0=x; - usr.y0=y; - }, + break; + } + } + usr.x0=x; + usr.y0=y; + }, /** * @function paintxy * @desc Dispatches paint/erase commands to the annotation volume and to the server for broadcast @@ -46,27 +46,27 @@ var AtlasMakerPaint = { * @param {integer} y Y coordinate in slice space * @param {Object} usr User object for the current user. Contains the painting value, view and slice */ - paintxy: function paintxy(u,c,x,y,usr) { - var me=AtlasMakerWidget; - var l=me.traceLog(paintxy,1,"#0c0");if(l)console.log.apply(undefined,l); - - // u: user number - // c: command - // x, y: coordinates - msg={c:c,x:x,y:y}; - - if(u==-1 && JSON.stringify(msg)!=JSON.stringify(me.msg0)) { - me.sendPaintMessage(msg); - me.msg0=msg; - } - - var dim=me.atlas.dim; - - var coord={x:x,y:y,z:usr.slice}; - if(usr.x0<0) { - usr.x0=coord.x; - usr.y0=coord.y; - } + paintxy: function paintxy(u,c,x,y,usr) { + var me=AtlasMakerWidget; + var l=me.traceLog(paintxy,1,"#0c0");if(l)console.log.apply(undefined,l); + + // u: user number + // c: command + // x, y: coordinates + msg={c:c,x:x,y:y}; + + if(u==-1 && JSON.stringify(msg)!=JSON.stringify(me.msg0)) { + me.sendPaintMessage(msg); + me.msg0=msg; + } + + var dim=me.atlas.dim; + + var coord={x:x,y:y,z:usr.slice}; + if(usr.x0<0) { + usr.x0=coord.x; + usr.y0=coord.y; + } if(u!=-1) { if(c === 'mu') { @@ -98,30 +98,30 @@ var AtlasMakerPaint = { break; } - usr.x0=coord.x; - usr.y0=coord.y; - }, + usr.x0=coord.x; + usr.y0=coord.y; + }, /** * @function paintvol * @desc Paints a series of voxels as indicated in an array. This function is exclusively used for undoing * @param {Array} voxels Array where each object contains a voxel index and a voxel value. The voxel index goes from 0 to dim[0]*dim[1]*dim[2]-1 */ - paintvol: function paintvol(voxels) { - var me=AtlasMakerWidget; - var l=me.traceLog(paintvol,0,"#0c0");if(l)console.log.apply(undefined,l); - - var i, - ind, // voxel index - val; // voxel delta-value, such that -=val undoes - for(i=0;i0) { - if(Q.length>max) - max=Q.length; - n=Q.shift(); - if(atlas.data[me.slice2index(n.x,n.y,z,myView)]!=bval) - continue; - left=n.x; - right=n.x; - y=n.y; - while (left-1>=0 && atlas.data[me.slice2index(left-1,y,z,myView)]==bval) { + var Q=[], + left, + right, + n; + var i,max=0; + var bval=atlas.data[me.slice2index(x,y,z,myView)]; // background-value: value of the voxel where the click occurred + + if(bval==val) // nothing to do + return; + + Q.push({x:x,y:y}); + while(Q.length>0) { + if(Q.length>max) + max=Q.length; + n=Q.shift(); + if(atlas.data[me.slice2index(n.x,n.y,z,myView)]!=bval) + continue; + left=n.x; + right=n.x; + y=n.y; + while (left-1>=0 && atlas.data[me.slice2index(left-1,y,z,myView)]==bval) { left--; } - while (right+1=0 && atlas.data[me.slice2index(x,y-1,z,myView)]==bval) - Q.push({x:x,y:y-1}); - if(y+1=0 && atlas.data[me.slice2index(x,y-1,z,myView)]==bval) + Q.push({x:x,y:y-1}); + if(y+1 -dy) { - err -= dy; - x1 += sx; - } - if (e2 < dx) { - err += dx; - y1 += sy; - } + xyzi1=me.slice2xyzi(x1,y1,z,usr.view); + xyzi2=me.slice2xyzi(x2,y2,z,usr.view); + me.annotationLength+=Math.sqrt( Math.pow(me.brain_pixdim[0]*(xyzi1[0]-xyzi2[0]),2)+ + Math.pow(me.brain_pixdim[1]*(xyzi1[1]-xyzi2[1]),2)+ + Math.pow(me.brain_pixdim[2]*(xyzi1[2]-xyzi2[2]),2)); + + for(j=0;j -dy) { + err -= dy; + x1 += sx; + } + if (e2 < dx) { + err += dx; + y1 += sy; + } for(j=0;jMRI"); - $("#chat").text("Chat (1 connected)"); - me.flagConnected = 1; - me.reconnectionTimeout = 5; - resolve(); - }; - - me.receiveFunctions["saveMetadata"] = me.receiveMetadata; - me.receiveFunctions["userData"] = me.receiveUserDataMessage; - me.receiveFunctions["volInfo"] = function (data) { console.log("volInfo", data) }; - me.receiveFunctions["chat"] = me.receiveChatMessage; - me.receiveFunctions["show"] = me.receiveShowMessage; - me.receiveFunctions["paint"] = me.receivePaintMessage; - me.receiveFunctions["paintvol"] = me.receivePaintVolumeMessage; - me.receiveFunctions["disconnect"] = me.receiveDisconnectMessage; - me.receiveFunctions["serverMessage"] = me.receiveServerMessage; - - me.receiveFunctions["requestSlice"] = function (data) { console.log("requestSlice", data) }; - me.receiveFunctions["requestSlice2"] = function (data) { console.log("requestSlice2", data) }; - - me.socket.onmessage = me.receiveSocketMessage; - - me.socket.onclose = function (msg) { - me.flagConnected = 0; - - // Try to reconnect - // wait a random initial time, to prevent an avalanche - // of reconnections in case of server crash - var rand = 1000 + 5000 * Math.random(); - console.log("Initial random time:", rand); - setTimeout(function () { - var timeout = me.reconnectionTimeout; - $("#chat").text("Disconnected. Try to reconnect in " + (timeout--) + " s..."); - if (me.timer) { + createSocket: function createSocket(host) { + var me=AtlasMakerWidget; + var l=me.traceLog(createSocket,0,"#aca");if(l)console.log.apply(undefined,l); + + var ws; + + if (window.WebSocket) { + ws=new WebSocket(host); + } else if (window.MozWebSocket) { + ws=new MozWebSocket(host); + } else { + console.log("ERROR: browser does not support WebSockets"); + } + + return ws; + }, + /** + * @function initSocketConnection + */ + initSocketConnection: function initSocketConnection() { + var me=AtlasMakerWidget; + var l=me.traceLog(initSocketConnection,0,"#aca");if(l)console.log.apply(undefined,l); + + var def=$.Deferred(); + + // WS connection + var host = "ws://" + window.location.hostname + ":8080/"; + + if(me.debug) + console.log("[initSocketConnection] host:",host); + if (me.progress) + me.progress.html("Connecting..."); + + try { + me.socket = me.createSocket(host); + + me.socket.onopen = function(msg) { + if(me.debug) + console.log("[initSocketConnection] connection open",msg); + me.progress.html("MRI"); + $("#chat").text("Chat (1 connected)"); + me.flagConnected=1; + me.reconnectionTimeout = 5; + def.resolve(); + }; + + me.receiveFunctions["saveMetadata"]=me.receiveMetadata; + me.receiveFunctions["userData"]=me.receiveUserDataMessage; + me.receiveFunctions["volInfo"]=function(data){console.log("volInfo",data)}; + me.receiveFunctions["chat"]=me.receiveChatMessage; + me.receiveFunctions["show"]=me.receiveShowMessage; + me.receiveFunctions["paint"]=me.receivePaintMessage; + me.receiveFunctions["paintvol"]=me.receivePaintVolumeMessage; + me.receiveFunctions["disconnect"]=me.receiveDisconnectMessage; + me.receiveFunctions["serverMessage"]=me.receiveServerMessage; + + me.receiveFunctions["requestSlice"]=function(data){console.log("requestSlice",data)}; + me.receiveFunctions["requestSlice2"]=function(data){console.log("requestSlice2",data)}; + + me.socket.onmessage = me.receiveSocketMessage; + + me.socket.onclose = function(msg) { + me.flagConnected=0; + + // Try to reconnect + // wait a random initial time, to prevent an avalanche + // of reconnections in case of server crash + var rand = 1000+5000*Math.random(); + console.log("Initial random time:",rand); + setTimeout(function() { + var timeout = me.reconnectionTimeout; + $("#chat").text("Disconnected. Try to reconnect in "+(timeout--)+" s..."); + if(me.timer) { + clearInterval(me.timer); + } + me.timer = setInterval(function() { + if(timeout < 0) { + $("#chat").text("Reconnecting..."); + me.socket = null; clearInterval(me.timer); + setTimeout(function() { + me.reconnectionTimeout *= 2; + me.initSocketConnection() + .then(function() { + me.sendUserDataMessage("allUserData"); + me.sendUserDataMessage("sendAtlas"); + clearInterval(me.timer); + }) + .catch(function() { + timeout=me.reconnectionTimeout; + $("#chat").text("Disconnected. Try to reconnect in "+(timeout--)+" s..."); + }); + }, 1000); + } else { + $("#chat").text("Disconnected. Try to reconnect in "+(timeout--)+" s..."); } - me.timer = setInterval(function () { - if (timeout < 0) { - $("#chat").text("Reconnecting..."); - me.socket = null; - clearInterval(me.timer); - setTimeout(function () { - me.reconnectionTimeout *= 2; - me.initSocketConnection() - .then(function () { - me.sendUserDataMessage("allUserData"); - me.sendUserDataMessage("sendAtlas"); - clearInterval(me.timer); - }) - .catch(function () { - timeout = me.reconnectionTimeout; - $("#chat").text("Disconnected. Try to reconnect in " + (timeout--) + " s..."); - }); - }, 1000); - } else { - $("#chat").text("Disconnected. Try to reconnect in " + (timeout--) + " s..."); - } - }, 1000); - }, rand); - }; - - window.onbeforeunload = function () { - me.socket.onclose = function () { }; // disable onclose handler first - me.socket.close() - }; - } - catch (ex) { - $("#chat").text("Chat (not connected - connection error)"); - } - }) - }, - /** + }, 1000); + }, rand); + }; + + window.onbeforeunload = function() { + me.socket.onclose = function () {}; // disable onclose handler first + me.socket.close() + }; + } + catch (ex) { + $("#chat").text("Chat (not connected - connection error)"); + } + + return def.promise(); + }, + /** * @function receiveSocketMessage */ - receiveSocketMessage: function receiveSocketMessage(msg) { - var me = AtlasMakerWidget; - var l = me.traceLog(receiveSocketMessage, 1, "#aca"); if (l) console.log.apply(undefined, l); - - // Message: atlas data initialisation - if (msg.data instanceof Blob) { - me.receiveBinaryMessage(msg.data); - return; - } - - // Message: interaction message - var data = JSON.parse(msg.data); - me.receiveFunctions[data.type](data); - }, - /** + receiveSocketMessage: function receiveSocketMessage(msg) { + var me=AtlasMakerWidget; + var l=me.traceLog(receiveSocketMessage,1,"#aca");if(l)console.log.apply(undefined,l); + + // Message: atlas data initialisation + if(msg.data instanceof Blob) { + me.receiveBinaryMessage(msg.data); + return; + } + + // Message: interaction message + var data=JSON.parse(msg.data); + me.receiveFunctions[data.type](data); + }, + /** * @function sendUserDataMessage */ - sendUserDataMessage: function sendUserDataMessage(description) { - var me = AtlasMakerWidget; - var l = me.traceLog(sendUserDataMessage, 1, "#aca"); if (l) console.log.apply(undefined, l); - - if (me.flagConnected == 0) - return; - - if (me.debug > 1) console.log("message: " + description); - - if (description === "allUserData") - var msg = { "type": "userData", "user": me.User, "description": description }; - else - var msg = { "type": "userData", "description": description }; - try { - me.socket.send(JSON.stringify(msg)); - } catch (ex) { - console.log("ERROR: Unable to sendUserDataMessage", ex); - } - }, - /** + sendUserDataMessage: function sendUserDataMessage(description) { + var me=AtlasMakerWidget; + var l=me.traceLog(sendUserDataMessage,1,"#aca");if(l)console.log.apply(undefined,l); + + if(me.flagConnected==0) + return; + + if(me.debug>1) console.log("message: "+description); + + if(description === "allUserData") + var msg={"type":"userData","user":me.User,"description":description}; + else + var msg={"type":"userData","description":description}; + try { + me.socket.send(JSON.stringify(msg)); + } catch (ex) { + console.log("ERROR: Unable to sendUserDataMessage",ex); + } + }, + /** * @function receiveBinaryMessage */ - receiveBinaryMessage: function receiveBinaryMessage(msgData) { - var me = AtlasMakerWidget; - var l = me.traceLog(receiveBinaryMessage, 1, "#aca"); if (l) console.log.apply(undefined, l); - + receiveBinaryMessage: function receiveBinaryMessage(msgData) { + var me=AtlasMakerWidget; + var l=me.traceLog(receiveBinaryMessage,1,"#aca");if(l)console.log.apply(undefined,l); + var fileReader = new FileReader(); fileReader.onload = function from_receiveSocketMessage() { - var data = new Uint8Array(this.result); - var sz = data.length; - var ext = String.fromCharCode(data[sz - 8], data[sz - 7], data[sz - 6]); - - if (me.debug > 1) console.log("type: " + ext); + var data=new Uint8Array(this.result); + var sz=data.length; + var ext=String.fromCharCode(data[sz-8],data[sz-7],data[sz-6]); - switch (ext) { + if(me.debug>1) console.log("type: "+ext); + + switch(ext) { case 'nii': { - var inflate = new pako.Inflate(); - inflate.push(data, true); - var atlas = new Object(); - atlas.data = inflate.result; - atlas.name = me.atlasFilename; - atlas.dim = me.brain_dim; - - me.atlas = atlas; + var inflate=new pako.Inflate(); + inflate.push(data,true); + var atlas=new Object(); + atlas.data=inflate.result; + atlas.name=me.atlasFilename; + atlas.dim=me.brain_dim; + + me.atlas=atlas; me.configureBrainImage(); me.configureAtlasImage(); me.resizeWindow(); - me.brain_img.img = null; + me.brain_img.img=null; me.drawImages(); - + // compute total segmented volume - var vol = me.computeSegmentedVolume(); - me.info.volume = parseInt(vol) + " mm3"; + var vol=me.computeSegmentedVolume(); + me.info.volume=parseInt(vol)+" mm3"; // setup download link - var link = me.container.find("span#download_atlas"); - link.html("" + atlas.name); + var link=me.container.find("span#download_atlas"); + link.html(""+atlas.name); break; } @@ -205,283 +207,283 @@ var AtlasMakerWS = { var urlCreator = window.URL || window.webkitURL; var imageUrl = urlCreator.createObjectURL(msgData); var img = new Image(); - - me.isMRILoaded = true; // receiving a jpg is proof of a loaded MRI - - img.onload = function from_initSocketConnection() { - var flagFirstImage = (me.brain_img.img == null); - me.brain_img.img = img; - me.brain_img.view = me.flagLoadingImg.view; - me.brain_img.slice = me.flagLoadingImg.slice; + + me.isMRILoaded=true; // receiving a jpg is proof of a loaded MRI + + img.onload=function from_initSocketConnection(){ + var flagFirstImage=(me.brain_img.img==null); + me.brain_img.img=img; + me.brain_img.view=me.flagLoadingImg.view; + me.brain_img.slice=me.flagLoadingImg.slice; me.drawImages(); + + me.flagLoadingImg.loading=false; - me.flagLoadingImg.loading = false; - - if (flagFirstImage || me.flagLoadingImg.view != me.User.view || me.flagLoadingImg.slice != me.User.slice) { + if(flagFirstImage || me.flagLoadingImg.view!=me.User.view ||me.flagLoadingImg.slice!=me.User.slice) { me.sendRequestSliceMessage(); } - + // remove loading indicator $("#loadingIndicator").hide(); } - img.src = imageUrl; + img.src=imageUrl; break; } } }; fileReader.readAsArrayBuffer(msgData); - }, - /** + }, + /** * @function receiveUserDataMessage */ - receiveUserDataMessage: function receiveUserDataMessage(data) { - var me = AtlasMakerWidget; - var l = me.traceLog(receiveUserDataMessage, 0, "#aca"); if (l) console.log.apply(undefined, l); - - if (me.debug > 1) console.log("description: " + data.description, data); - - var u = data.uid; - - // First time the user is observed - if (me.Collab[u] === undefined) { - try { - //var msg=""+data.user.username+" entered atlas "+data.user.specimenName+"/"+data.user.atlasFilename+"
" - var msg; - if (data.user === undefined || data.user.username === "Anonymous") { - msg = "" + data.uid + " entered
"; - } else { - msg = "" + data.user.username + " entered
"; - } - $("#log").append(msg); - $("#log").scrollTop($("#log")[0].scrollHeight); - } catch (e) { - console.log("data:", data); - console.log(e); - } - } - - if (data.description === "allUserData") - me.Collab[u] = data.user; - else { - try { + receiveUserDataMessage: function receiveUserDataMessage(data) { + var me=AtlasMakerWidget; + var l=me.traceLog(receiveUserDataMessage,0,"#aca");if(l)console.log.apply(undefined,l); + + if(me.debug>1) console.log("description: "+data.description,data); + + var u=data.uid; + + // First time the user is observed + if(me.Collab[u]===undefined) { + try { + //var msg=""+data.user.username+" entered atlas "+data.user.specimenName+"/"+data.user.atlasFilename+"
" + var msg; + if(data.user === undefined || data.user.username === "Anonymous") { + msg=""+data.uid+" entered
"; + } else { + msg=""+data.user.username+" entered
"; + } + $("#log").append(msg); + $("#log").scrollTop($("#log")[0].scrollHeight); + } catch (e) { + console.log("data:",data); + console.log(e); + } + } + + if(data.description === "allUserData") + me.Collab[u]=data.user; + else { + try { var changes = JSON.parse(data.description); var i; - for (i in changes) + for(i in changes) me.Collab[u][i] = changes[i]; } catch (e) { console.log(e); } - } - - var v, nusers = 1; - for (v in me.Collab) - nusers++; - $("#chat").text("Chat (" + nusers + " connected)"); - }, - /** + } + + var v,nusers=1; + for(v in me.Collab) + nusers++; + $("#chat").text("Chat ("+nusers+" connected)"); + }, + /** * @function sendChatMessage */ - sendChatMessage: function sendChatMessage() { - var me = AtlasMakerWidget; - var l = me.traceLog(sendChatMessage, 0, "#aca"); if (l) console.log.apply(undefined, l); - - if (me.flagConnected == 0) - return; - var msg = DOMPurify.sanitize($('input#msg')[0].value); - try { - me.socket.send(JSON.stringify({ "type": "chat", "msg": msg, "username": me.User.username })); - var msg = "me: " + msg + "
"; - $("#log").append(msg); - $("#log").scrollTop($("#log")[0].scrollHeight); - $('input#msg').val(""); - } catch (ex) { - console.log("ERROR: Unable to sendChatMessage", ex); - } - }, - /** + sendChatMessage: function sendChatMessage() { + var me=AtlasMakerWidget; + var l=me.traceLog(sendChatMessage,0,"#aca");if(l)console.log.apply(undefined,l); + + if(me.flagConnected==0) + return; + var msg = DOMPurify.sanitize($('input#msg')[0].value); + try { + me.socket.send(JSON.stringify({"type":"chat","msg":msg,"username":me.User.username})); + var msg="me: "+msg+"
"; + $("#log").append(msg); + $("#log").scrollTop($("#log")[0].scrollHeight); + $('input#msg').val(""); + } catch (ex) { + console.log("ERROR: Unable to sendChatMessage",ex); + } + }, + /** * @function receiveChatMessage */ - receiveChatMessage: function receiveChatMessage(data) { - var me = AtlasMakerWidget; - var l = me.traceLog(receiveChatMessage, 0, "#aca"); if (l) console.log.apply(undefined, l); - console.log(data); - - var theSource = me.Collab[data.uid].source; - var theView = me.Collab[data.uid].view; - var theSlice = me.Collab[data.uid].slice; - var link = "/mri?url=" + theSource + "&view=" + theView + "&slice=" + theSlice; - var theUsername = data.username; - var msg = ""+theUsername+": "+data.msg+"
" - $("#log").append(msg); - $("#log").scrollTop($("#log")[0].scrollHeight); - }, - /** + receiveChatMessage: function receiveChatMessage(data) { + var me=AtlasMakerWidget; + var l=me.traceLog(receiveChatMessage,0,"#aca");if(l)console.log.apply(undefined,l); + console.log(data); + + var theSource=me.Collab[data.uid].source; + var theView=me.Collab[data.uid].view; + var theSlice=me.Collab[data.uid].slice; + var link = "/mri?url="+theSource+"&view="+theView+"&slice="+theSlice; + var theUsername=data.username; + var msg=""+theUsername+": "+data.msg+"
" + $("#log").append(msg); + $("#log").scrollTop($("#log")[0].scrollHeight); + }, + /** * @function sendPaintMessage * @desc On user painting, this function broadcasts the painting event to all other connected users * @param {Object} msg Painting event object: {"c":c,"x":x,"y":y}, where "c" is the command (l,e,lf,ef) and x and y are the coordinates in slice space */ - sendPaintMessage: function sendPaintMessage(msg) { - var me=AtlasMakerWidget; - var l=me.traceLog(sendPaintMessage,1,"#aca");if(l)console.log.apply(undefined,l); - - if(me.flagConnected==0) - return; - try { - me.socket.send(JSON.stringify({type:"paint",data:msg})); - } catch (ex) { - console.log("ERROR: Unable to sendPaintMessage",ex); - } - }, - /** + sendPaintMessage: function sendPaintMessage(msg) { + var me=AtlasMakerWidget; + var l=me.traceLog(sendPaintMessage,1,"#aca");if(l)console.log.apply(undefined,l); + + if(me.flagConnected==0) + return; + try { + me.socket.send(JSON.stringify({type:"paint",data:msg})); + } catch (ex) { + console.log("ERROR: Unable to sendPaintMessage",ex); + } + }, + /** * @function receivePaintMessage * @desc Receive paint events from other connected users */ - receivePaintMessage: function receivePaintMessage(data) { - var me=AtlasMakerWidget; - var l=me.traceLog(receivePaintMessage,3,"#aca");if(l)console.log.apply(undefined,l); - - var msg=data.data; - var u=data.uid; // user - var c=msg.c; // command - var x=parseInt(msg.x); // x coordinate - var y=parseInt(msg.y); // y coordinate - - if(me.Collab[u]) - me.paintxy(u,c,x,y,me.Collab[u]); - }, - /** + receivePaintMessage: function receivePaintMessage(data) { + var me=AtlasMakerWidget; + var l=me.traceLog(receivePaintMessage,3,"#aca");if(l)console.log.apply(undefined,l); + + var msg=data.data; + var u=data.uid; // user + var c=msg.c; // command + var x=parseInt(msg.x); // x coordinate + var y=parseInt(msg.y); // y coordinate + + if(me.Collab[u]) + me.paintxy(u,c,x,y,me.Collab[u]); + }, + /** * @function sendShowMessage * @desc On user showing, this function broadcasts the showing event to all other connected users * @param {Object} msg Showing event object: {"x":x,"y":y}, where x and y are the coordinates in slice space */ - sendShowMessage: function sendShowMessage(msg) { - var me=AtlasMakerWidget; - var l=me.traceLog(sendShowMessage,1,"#aca");if(l)console.log.apply(undefined,l); - - if(me.flagConnected==0) - return; - try { - me.socket.send(JSON.stringify({type:"show",data:msg})); - } catch (ex) { - console.log("ERROR: Unable to sendShowMessage",ex); - } - }, - /** + sendShowMessage: function sendShowMessage(msg) { + var me=AtlasMakerWidget; + var l=me.traceLog(sendShowMessage,1,"#aca");if(l)console.log.apply(undefined,l); + + if(me.flagConnected==0) + return; + try { + me.socket.send(JSON.stringify({type:"show",data:msg})); + } catch (ex) { + console.log("ERROR: Unable to sendShowMessage",ex); + } + }, + /** * @function receiveShowMessage * @desc Receive show events from other connected users */ - receiveShowMessage: function receiveShowMessage(data) { - var me=AtlasMakerWidget; - var l=me.traceLog(receiveShowMessage,3,"#aca");if(l)console.log.apply(undefined,l); - - var msg=data.data; - var u=data.uid; // user - var c=msg.c; // command - var x=parseInt(msg.x); // x coordinate - var y=parseInt(msg.y); // y coordinate - - if(me.Collab[u]) - me.showxy(u,c,x,y,me.Collab[u]); - }, - /** + receiveShowMessage: function receiveShowMessage(data) { + var me=AtlasMakerWidget; + var l=me.traceLog(receiveShowMessage,3,"#aca");if(l)console.log.apply(undefined,l); + + var msg=data.data; + var u=data.uid; // user + var c=msg.c; // command + var x=parseInt(msg.x); // x coordinate + var y=parseInt(msg.y); // y coordinate + + if(me.Collab[u]) + me.showxy(u,c,x,y,me.Collab[u]); + }, + /** * @function receivePaintVolumeMessage */ - receivePaintVolumeMessage: function receivePaintVolumeMessage(data) { - var me=AtlasMakerWidget; - var l=me.traceLog(receivePaintVolumeMessage,0,"#aca");if(l)console.log.apply(undefined,l); - - var i,ind,val,voxels; - - voxels=data.data; - me.paintvol(voxels.data); - - /* - TEST - */ - me.sendRequestSliceMessage(); - }, - /** + receivePaintVolumeMessage: function receivePaintVolumeMessage(data) { + var me=AtlasMakerWidget; + var l=me.traceLog(receivePaintVolumeMessage,0,"#aca");if(l)console.log.apply(undefined,l); + + var i,ind,val,voxels; + + voxels=data.data; + me.paintvol(voxels.data); + + /* + TEST + */ + me.sendRequestSliceMessage(); + }, + /** * @function sendUndoMessage */ - sendUndoMessage: function sendUndoMessage() { - var me=AtlasMakerWidget; - var l=me.traceLog(sendUndoMessage,0,"#aca");if(l)console.log.apply(undefined,l); - - if(me.flagConnected==0) - return; - try { - me.socket.send(JSON.stringify({type:"paint",data:{c:"u"}})); - } catch (ex) { - console.log("ERROR: Unable to sendUndoMessage",ex); - } - }, - /** + sendUndoMessage: function sendUndoMessage() { + var me=AtlasMakerWidget; + var l=me.traceLog(sendUndoMessage,0,"#aca");if(l)console.log.apply(undefined,l); + + if(me.flagConnected==0) + return; + try { + me.socket.send(JSON.stringify({type:"paint",data:{c:"u"}})); + } catch (ex) { + console.log("ERROR: Unable to sendUndoMessage",ex); + } + }, + /** * @function sendSaveMessage */ - sendSaveMessage: function sendSaveMessage() { - var me=AtlasMakerWidget; - var l=me.traceLog(sendSaveMessage,0,"#aca");if(l)console.log.apply(undefined,l); - - if(me.flagConnected==0) - return; - try { - me.socket.send(JSON.stringify({type:"save"})); - } catch (ex) { - console.log("ERROR: Unable to sendSaveMessage",ex); - } - }, - /** + sendSaveMessage: function sendSaveMessage() { + var me=AtlasMakerWidget; + var l=me.traceLog(sendSaveMessage,0,"#aca");if(l)console.log.apply(undefined,l); + + if(me.flagConnected==0) + return; + try { + me.socket.send(JSON.stringify({type:"save"})); + } catch (ex) { + console.log("ERROR: Unable to sendSaveMessage",ex); + } + }, + /** * @function sendRequestMRIMessage */ - sendRequestMRIMessage: function sendRequestMRIMessage(source) { - var me=AtlasMakerWidget; - var l=me.traceLog(sendRequestMRIMessage,1,"#aca");if(l)console.log.apply(undefined,l); - - if(me.flagConnected==0) - return; - - try { - me.socket.send(JSON.stringify({ - type:"requestMRI", - source:"sendRequestMRIMessage" - })); - } catch (ex) { - console.log("ERROR: Unable to sendRequestMRIMessage",ex); - } - }, - /** + sendRequestMRIMessage: function sendRequestMRIMessage(source) { + var me=AtlasMakerWidget; + var l=me.traceLog(sendRequestMRIMessage,1,"#aca");if(l)console.log.apply(undefined,l); + + if(me.flagConnected==0) + return; + + try { + me.socket.send(JSON.stringify({ + type:"requestMRI", + source:"sendRequestMRIMessage" + })); + } catch (ex) { + console.log("ERROR: Unable to sendRequestMRIMessage",ex); + } + }, + /** * @function sendRequestSliceMessage */ - sendRequestSliceMessage: function sendRequestSliceMessage() { - var me=AtlasMakerWidget; - var l=me.traceLog(sendRequestSliceMessage,1,"#aca");if(l)console.log.apply(undefined,l); - - if(me.flagConnected==0) - return; - if(me.flagLoadingImg.loading==true) - return; - try { - me.socket.send(JSON.stringify({ - - type:"requestSlice", - /* - TEST - */ - //type:"requestSlice2", - - view:me.User.view, - slice:me.User.slice - })); - me.flagLoadingImg.loading=true; - me.flagLoadingImg.view=me.User.view; - me.flagLoadingImg.slice=me.User.slice; - - } catch (ex) { - console.log("ERROR: Unable to sendRequestSliceMessage",ex); - } - }, + sendRequestSliceMessage: function sendRequestSliceMessage() { + var me=AtlasMakerWidget; + var l=me.traceLog(sendRequestSliceMessage,1,"#aca");if(l)console.log.apply(undefined,l); + + if(me.flagConnected==0) + return; + if(me.flagLoadingImg.loading==true) + return; + try { + me.socket.send(JSON.stringify({ + + type:"requestSlice", + /* + TEST + */ + //type:"requestSlice2", + + view:me.User.view, + slice:me.User.slice + })); + me.flagLoadingImg.loading=true; + me.flagLoadingImg.view=me.User.view; + me.flagLoadingImg.slice=me.User.slice; + + } catch (ex) { + console.log("ERROR: Unable to sendRequestSliceMessage",ex); + } + }, /** * @todo This is really not the place for some of this code. The receiveMetadata * function is ok, but the direct references to projectInfo -- a structure @@ -489,136 +491,136 @@ var AtlasMakerWS = { * mechanism for uncoupling the 2 pieces of code is not clear. It could be * a subscription, for example. */ - receiveMetadata: function receiveMetadata(data) { - var me=AtlasMakerWidget; - var l=me.traceLog(receiveMetadata,1,"#aca");if(l)console.log.apply(undefined,l); + receiveMetadata: function receiveMetadata(data) { + var me=AtlasMakerWidget; + var l=me.traceLog(receiveMetadata,1,"#aca");if(l)console.log.apply(undefined,l); var projShortname = projectInfo.shortname; - for (var i in projectInfo.files.list) { - if (projectInfo.files.list[i].source == data.metadata.source) { - for (var key in projectInfo.files.list[i].mri.annotations[projShortname]) { - info_proxy["files.list." + i + ".mri.annotations." + projShortname + "." + key] = data.metadata.mri.annotations[projShortname][key]; - } - info_proxy["files.list." + i + ".name"] = data.metadata.name; - break; - } - } - }, - /** + for (var i in projectInfo.files.list) { + if (projectInfo.files.list[i].source == data.metadata.source) { + for (var key in projectInfo.files.list[i].mri.annotations[projShortname]) { + info_proxy["files.list." + i + ".mri.annotations." + projShortname + "." + key] = data.metadata.mri.annotations[projShortname][key]; + } + info_proxy["files.list." + i + ".name"] = data.metadata.name; + break; + } + } + }, + /** * @function sendSaveMetadataMessage */ - sendSaveMetadataMessage: function sendSaveMetadataMessage(info, method, patch) { - var me=AtlasMakerWidget; - var l=me.traceLog(sendSaveMetadataMessage,1,"#aca");if(l)console.log.apply(undefined,l); - - return new Promise(function(resolve, reject) { - if(me.flagConnected==0) { - console.log("WARNING: Not connected: will not save metadata"); - return reject(); + sendSaveMetadataMessage: function sendSaveMetadataMessage(info, method, patch) { + var me=AtlasMakerWidget; + var l=me.traceLog(sendSaveMetadataMessage,1,"#aca");if(l)console.log.apply(undefined,l); + + var def = $.Deferred(); + if(me.flagConnected==0) { + console.log("WARNING: Not connected: will not save metadata"); + return def.reject().promise(); + } + + try { + var rnd = Math.random().toString(36).slice(20); + var met = method || "append"; + if(method == "patch") { + me.socket.send(JSON.stringify({ + type:"saveMetadata", + metadata: info, + method: met, + patch: patch, + rnd: rnd + })); + } else { + me.socket.send(JSON.stringify({ + type:"saveMetadata", + metadata: info, + method: met, + rnd: rnd + })); } - - try { - var rnd = Math.random().toString(36).slice(20); - var met = method || "append"; - if(method == "patch") { - me.socket.send(JSON.stringify({ - type:"saveMetadata", - metadata: info, - method: met, - patch: patch, - rnd: rnd - })); - } else { - me.socket.send(JSON.stringify({ - type:"saveMetadata", - metadata: info, - method: met, - rnd: rnd - })); - } - if(me.debug>1) { - console.log(rnd); - console.log(info); - } - resolve(); - - } catch (ex) { - console.log("ERROR: Unable to sendSaveMetadataMessage",ex); - reject(); + if(me.debug>1) { + console.log(rnd); + console.log(info); } - }); - }, - /** + def.resolve(); + + } catch (ex) { + console.log("ERROR: Unable to sendSaveMetadataMessage",ex); + def.reject(); + } + return def.promise(); + }, + /** * @function receiveDisconnectMessage */ - receiveDisconnectMessage: function receiveDisconnectMessage(data) { - var me=AtlasMakerWidget; - var l=me.traceLog(receiveDisconnectMessage,0,"#aca");if(l)console.log.apply(undefined,l); - - var u=data.uid; // user - if(me.Collab[u]) { - var msg; - if(me.Collab[u].username === undefined || me.Collab[u].username === "Anonymous") - msg = ""+me.Collab[u].uid+" left
"; - else + receiveDisconnectMessage: function receiveDisconnectMessage(data) { + var me=AtlasMakerWidget; + var l=me.traceLog(receiveDisconnectMessage,0,"#aca");if(l)console.log.apply(undefined,l); + + var u=data.uid; // user + if(me.Collab[u]) { + var msg; + if(me.Collab[u].username === undefined || me.Collab[u].username === "Anonymous") + msg = ""+me.Collab[u].uid+" left
"; + else msg = ""+me.Collab[u].username+" left
"; - } - else - var msg=""+u+" left
"; - delete me.Collab[u]; - var v,nusers=1; for(v in me.Collab) nusers++; - $("#chat").text("Chat ("+nusers+" connected)"); - $("#log").append(msg); - $("#log").scrollTop($("#log")[0].scrollHeight); - }, - /** + } + else + var msg=""+u+" left
"; + delete me.Collab[u]; + var v,nusers=1; for(v in me.Collab) nusers++; + $("#chat").text("Chat ("+nusers+" connected)"); + $("#log").append(msg); + $("#log").scrollTop($("#log")[0].scrollHeight); + }, + /** * @function receiveServerMessage */ - receiveServerMessage: function receiveServerMessage(data) { - var me=AtlasMakerWidget; - var l=me.traceLog(receiveServerMessage,0,"#aca");if(l)console.log.apply(undefined,l); - - var msg=data.msg; - var prevMsg=$("#chat").text(); - $("#chat").text(msg); - setTimeout(function(){$("#chat").text(prevMsg)},5000); - }, - /** - * @function replayWSTraffic - * @desc Replays websocket traffic recorded at the served. Used for debugging - * @param Array recorded An array of websocket messages recorded in the server - */ - replayWSTraffic: function replayWSTraffic(recorded) { - var me=AtlasMakerWidget; - var l=me.traceLog(replayWSTraffic,0,"#aca");if(l)console.log.apply(undefined,l); - var i; - for(i=0;il)) { var str,arg=[]; // str="am> "+(f.name)+" "+(f.caller?(f.caller.name||"annonymous"):"root"); @@ -103,141 +103,142 @@ var AtlasMakerWidget = { return arg; } */ - }, + }, /** * @function quit */ - quit: function quit() { - var me=AtlasMakerWidget; - var l=me.traceLog(quit,0,"#bbd");if(l)console.log.apply(undefined,l); - - me.log("","Goodbye!"); - me.socket.close(); - me.socket = null; - }, - - //==================================================================================== - // Configuration - //==================================================================================== + quit: function quit() { + var me=AtlasMakerWidget; + var l=me.traceLog(quit,0,"#bbd");if(l)console.log.apply(undefined,l); + + me.log("","Goodbye!"); + me.socket.close(); + me.socket = null; + }, + + //==================================================================================== + // Configuration + //==================================================================================== /** * @function initAtlasMaker */ - initAtlasMaker: function initAtlasMaker(elem) { - var me=AtlasMakerWidget; - var l=me.traceLog(initAtlasMaker,0,"#bbd");if(l)console.log.apply(undefined,l); - - // check if user is loged in - $.get("/loggedIn",function(res) { - console.log(res); - if(res.loggedIn) - me.User.username=res.username - else - me.User.username='Anonymous'; - }); - - // Create offscreen canvas for mri and atlas - me.brain_offcn=document.createElement('canvas'); - me.brain_offtx=me.brain_offcn.getContext('2d'); - me.atlas_offcn=document.createElement('canvas'); - me.atlas_offtx=me.atlas_offcn.getContext('2d'); - - // Set widget div (create one if none) - if(elem==undefined) { - me.container=$("
'); - me.canvas = me.container.find('canvas')[0]; - me.context = me.canvas.getContext('2d'); - - // Add div to display slice number - me.container.find("#resizable").append("
"); - - // Add div to display slice number - me.container.find("#resizable").append(""); - - // Add cursor (a small div) - me.container.find("#resizable").append("
"); - - $('body').attr('data-toolbarDisplay','right'); - - // Add precise cursor - var isTouchArr=[];//["iPad","iPod"]; - var curDevice=navigator.userAgent.split(/[(;]/)[1]; - if($.inArray(curDevice,isTouchArr)>=0) { - me.flagUsePreciseCursor=true; - me.initCursor(); - } - - // Configure mouse events for desktop computers - me.canvas.onmousedown = me.mousedown; - me.canvas.onmousemove = me.mousemove; - me.canvas.onmouseup = me.mouseup; - - // Connect event to respond to window resizing - $(window).resize(function() { - me.resizeWindow(); - }); - - // get pointer to progress div - me.progress=$("a.download_MRI"); - - // Init the toolbar: load template, wire actions - return new Promise(function(resolve, reject) { - $.get("/templates/tools.html",function from_initAtlasMaker(html) { - me.container.append(html); - - // intercept keyboard events - $(document).keydown(function(e){me.keyDown(e)}); - - // configure annotation tools - $("#tools-minimized").click(function(){me.changeToolbarDisplay("maximize")}); - me.push($(".push#display-minimize"),function(){me.changeToolbarDisplay("minimize")}); - me.push($(".push#display-left"),function(){me.changeToolbarDisplay("left")}); - me.push($(".push#display-right"),function(){me.changeToolbarDisplay("right")}); - me.slider($(".slider#slice"),function(x){me.changeSlice(Math.round(x))}); - me.chose($(".chose#plane"),me.changeView); - me.chose($(".chose#paintTool"),me.changeTool); - me.chose($(".chose#penSize"),me.changePenSize); - me.toggle($(".toggle#precise"),me.togglePreciseCursor); - me.toggle($(".toggle#fill"),me.toggleFill); - me.toggle($(".toggle#fullscreen"),me.toggleFullscreen); - me.toggle($(".toggle#bubble"),me.toggleChat); - me.push($(".push#3drender"),me.render3D); - me.push($(".push#link"),me.link); - me.push($(".push#upload"),me.upload); - me.push($(".push#download"),me.download); - me.push($(".push#color"),me.color); - me.push($(".push#undo"),me.sendUndoMessage); - me.push($(".push#save"),me.sendSaveMessage); - me.push($(".push#prev"),me.prevSlice); - me.push($(".push#next"),me.nextSlice); - - // connect chat message input - $("#msg").keypress(function keypress_fromInitAtlasMaker(e) {me.onkey(e)}); - - $("#tools-minimized").hide(); - }) - .then(function from_initAtlasMaker() { - // Init web socket connection - return me.initSocketConnection(); - }).then(function() { - resolve() - }); - }); - }, + initAtlasMaker: function initAtlasMaker(elem) { + var me=AtlasMakerWidget; + var l=me.traceLog(initAtlasMaker,0,"#bbd");if(l)console.log.apply(undefined,l); + + // check if user is loged in + $.get("/loggedIn",function(res) { + console.log(res); + if(res.loggedIn) + me.User.username=res.username + else + me.User.username='Anonymous'; + }); + + // Create offscreen canvas for mri and atlas + me.brain_offcn=document.createElement('canvas'); + me.brain_offtx=me.brain_offcn.getContext('2d'); + me.atlas_offcn=document.createElement('canvas'); + me.atlas_offtx=me.atlas_offcn.getContext('2d'); + + // Set widget div (create one if none) + if(elem==undefined) { + me.container=$("
'); + me.canvas = me.container.find('canvas')[0]; + me.context = me.canvas.getContext('2d'); + + // Add div to display slice number + me.container.find("#resizable").append("
"); + + // Add div to display slice number + me.container.find("#resizable").append(""); + + // Add cursor (a small div) + me.container.find("#resizable").append("
"); + + $('body').attr('data-toolbarDisplay','right'); + + // Add precise cursor + var isTouchArr=[];//["iPad","iPod"]; + var curDevice=navigator.userAgent.split(/[(;]/)[1]; + if($.inArray(curDevice,isTouchArr)>=0) { + me.flagUsePreciseCursor=true; + me.initCursor(); + } + + // Configure mouse events for desktop computers + me.canvas.onmousedown = me.mousedown; + me.canvas.onmousemove = me.mousemove; + me.canvas.onmouseup = me.mouseup; + + // Connect event to respond to window resizing + $(window).resize(function() { + me.resizeWindow(); + }); + + // get pointer to progress div + me.progress=$("a.download_MRI"); + + // Init the toolbar: load template, wire actions + var def=$.Deferred(); + $.get("/templates/tools.html",function from_initAtlasMaker(html) { + me.container.append(html); + + // intercept keyboard events + $(document).keydown(function(e){me.keyDown(e)}); + + // configure annotation tools + $("#tools-minimized").click(function(){me.changeToolbarDisplay("maximize")}); + me.push($(".push#display-minimize"),function(){me.changeToolbarDisplay("minimize")}); + me.push($(".push#display-left"),function(){me.changeToolbarDisplay("left")}); + me.push($(".push#display-right"),function(){me.changeToolbarDisplay("right")}); + me.slider($(".slider#slice"),function(x){me.changeSlice(Math.round(x))}); + me.chose($(".chose#plane"),me.changeView); + me.chose($(".chose#paintTool"),me.changeTool); + me.chose($(".chose#penSize"),me.changePenSize); + me.toggle($(".toggle#precise"),me.togglePreciseCursor); + me.toggle($(".toggle#fill"),me.toggleFill); + me.toggle($(".toggle#fullscreen"),me.toggleFullscreen); + me.toggle($(".toggle#bubble"),me.toggleChat); + me.push($(".push#3drender"),me.render3D); + me.push($(".push#link"),me.link); + me.push($(".push#upload"),me.upload); + me.push($(".push#download"),me.download); + me.push($(".push#color"),me.color); + me.push($(".push#undo"),me.sendUndoMessage); + me.push($(".push#save"),me.sendSaveMessage); + me.push($(".push#prev"),me.prevSlice); + me.push($(".push#next"),me.nextSlice); + + // connect chat message input + $("#msg").keypress(function keypress_fromInitAtlasMaker(e) {me.onkey(e)}); + + $("#tools-minimized").hide(); + }) + .then(function from_initAtlasMaker() { + // Init web socket connection + return me.initSocketConnection(); + }).then(function() { + def.resolve() + }); + + return def.promise(); + }, /** * @function configureAtlasMaker */ - configureAtlasMaker: function configureAtlasMaker(info,index) { - var me=AtlasMakerWidget; - var l=me.traceLog(configureAtlasMaker,0,"#bbd");if(l)console.log.apply(undefined,l); + configureAtlasMaker: function configureAtlasMaker(info,index) { + var me=AtlasMakerWidget; + var l=me.traceLog(configureAtlasMaker,0,"#bbd");if(l)console.log.apply(undefined,l); var def=$.Deferred(); @@ -273,146 +274,151 @@ var AtlasMakerWidget = { }); return def.promise(); - }, + }, /** * @function configureOntology */ - configureOntology: function configureOntology(json) { - var me=AtlasMakerWidget; - var l=me.traceLog(configureOntology,0,"#bbd");if(l)console.log.apply(undefined,l); - - me.ontology=json - me.ontology.valueToIndex=[]; - me.ontology.labels.forEach(function(o,i){me.ontology.valueToIndex[o.value]=i}); - // to clear the region name being displayed on the info text-layer when having used eyedrop - delete me.info.region; - }, + configureOntology: function configureOntology(json) { + var me=AtlasMakerWidget; + var l=me.traceLog(configureOntology,0,"#bbd");if(l)console.log.apply(undefined,l); + + me.ontology=json + me.ontology.valueToIndex=[]; + me.ontology.labels.forEach(function(o,i){me.ontology.valueToIndex[o.value]=i}); + // to clear the region name being displayed on the info text-layer when having used eyedrop + delete me.info.region; + }, /** * @function requestMRIInfo * @desc Request to download an MRI, with polling to prevent hangouts on lengthy * downloads */ - requestMRIInfo: function requestMRIInfo(source, def) { - var me=AtlasMakerWidget; - var l=me.traceLog(requestMRIInfo,0,"#bbd");if(l)console.log.apply(undefined,l); + requestMRIInfo: function requestMRIInfo(source, def) { + var me=AtlasMakerWidget; + var l=me.traceLog(requestMRIInfo,0,"#bbd");if(l)console.log.apply(undefined,l); + + if(!def) { + var def=$.Deferred(); + } $.post("/mri/json",{url:source}, function(info) { if(info.success == true) { - return Promise.resolve(info); + def.resolve(info); } else if(info.success == 'downloading') { if(me.User.source != source) return; + setTimeout(function(){me.requestMRIInfo(source,def)},2000); $("#loadingIndicator p").text("Loading... "+parseInt(info.cur/info.len*100,10)+"%"); - return new Promise(function(resolve, reject) { - setTimeout(function(){ - resolve(me.requestMRIInfo(source)) - }, 2000); - }) } else { console.log("ERROR: requestMRIInfo",info); - return Promise.reject(); + def.reject(); } }); - }, + + return def.promise(); + }, /** * @function configureMRI */ - configureMRI: function configureMRI(info,index) { - var me=AtlasMakerWidget; - var l=me.traceLog(configureMRI,0,"#bbd");if(l)console.log.apply(undefined,l); - - return new Promise(function(resolve, reject) { - me.User.source=info.source; - me.requestMRIInfo(info.source) - .then( function(info2) { - - if(!info.dim) { - // the mri object used to call this function does not have a 'dim' - // property, indicating that it had not been downloaded at the time of the - // call. Here we merge the fields from info2 that are initialised upon - // download of the mri server-side. The mri field in the original 'info', - // which contains the newly created text 'annotations', is conserved - $.extend(true, info, info2); - } - info2=info; - - // Get data from AtlasMaker object - me.name=info2.name||"Untitled"; - me.url=info2.url; - me.atlasFilename=info2.mri.atlas[index].filename; - me.atlasName=info2.mri.atlas[index].name; - - // get local file path from url - me.User.dirname=me.url; // TEMPORARY - me.User.mri=info2.mri.brain; - me.User.specimenName=me.name; - me.User.atlasFilename=info2.mri.atlas[index].filename; - me.User.isMRILoaded=false; - - // TODO: it's silly to have to put vol dim twice... - // (first here, once again further down) - me.User.dim=info2.dim; - me.User.pixdim=info2.pixdim; - - // compute space transformations - me.User.v2w=info2.voxel2world; - me.User.wori=info2.worldOrigin; - me.computeS2VTransformation(); - - //me.testS2VTransformation(); - - me.flagLoadingImg={loading:false}; - - me.brain_img.img=null; - - // get volume dimensions - me.brain_dim=info2.dim; - if(info2.pixdim) - me.brain_pixdim=info2.pixdim; - else - me.brain_pixdim=[1,1,1]; - - resolve(info2); - }) - .catch(function(err) { - console.log("ERROR: DOWNLOAD FAILED", err); - reject(err); - }); + configureMRI: function configureMRI(info,index) { + var me=AtlasMakerWidget; + var l=me.traceLog(configureMRI,0,"#bbd");if(l)console.log.apply(undefined,l); + + var def=$.Deferred(); + + me.User.source=info.source; + me.requestMRIInfo(info.source) + .then(function(info2) { + + if(!info.dim) { + // the mri object used to call this function does not have a 'dim' + // property, indicating that it had not been downloaded at the time of the + // call. Here we merge the fields from info2 that are initialised upon + // download of the mri server-side. The mri field in the original 'info', + // which contains the newly created text 'annotations', is conserved + $.extend(true, info, info2); + } + info2=info; + + // Get data from AtlasMaker object + me.name=info2.name||"Untitled"; + me.url=info2.url; + me.atlasFilename=info2.mri.atlas[index].filename; + me.atlasName=info2.mri.atlas[index].name; + + // get local file path from url + me.User.dirname=me.url; // TEMPORARY + me.User.mri=info2.mri.brain; + me.User.specimenName=me.name; + me.User.atlasFilename=info2.mri.atlas[index].filename; + me.User.isMRILoaded=false; + + // TODO: it's silly to have to put vol dim twice... + // (first here, once again further down) + me.User.dim=info2.dim; + me.User.pixdim=info2.pixdim; + + // compute space transformations + me.User.v2w=info2.voxel2world; + me.User.wori=info2.worldOrigin; + me.computeS2VTransformation(); + + //me.testS2VTransformation(); + + me.flagLoadingImg={loading:false}; + + me.brain_img.img=null; + + // get volume dimensions + me.brain_dim=info2.dim; + if(info2.pixdim) + me.brain_pixdim=info2.pixdim; + else + me.brain_pixdim=[1,1,1]; + + def.resolve(info2); + }) + .catch(function(err) { + console.log("ERROR: DOWNLOAD FAILED", err); + def.reject(err); }); - } + + + return def.promise(); + } }; /* - 0 int sizeof_hdr; //!< MUST be 348 // // int sizeof_hdr; // - 4 char data_type[10]; //!< ++UNUSED++ // // char data_type[10]; // - 14 char db_name[18]; //!< ++UNUSED++ // // char db_name[18]; // - 32 int extents; //!< ++UNUSED++ // // int extents; // - 36 short session_error; //!< ++UNUSED++ // // short session_error; // - 38 char regular; //!< ++UNUSED++ // // char regular; // - 39 char dim_info; //!< MRI slice ordering. // // char hkey_un0; // - - //--- was image_dimension substruct ---// - 40 short dim[8]; //!< Data array dimensions.// // short dim[8]; // - 56 float intent_p1 ; //!< 1st intent parameter. // // short unused8; // - // short unused9; // - 60 float intent_p2 ; //!< 2nd intent parameter. // // short unused10; // - // short unused11; // - 64 float intent_p3 ; //!< 3rd intent parameter. // // short unused12; // - // short unused13; // - 68 short intent_code ; //!< NIFTI_INTENT_* code. // // short unused14; // - 70 short datatype; //!< Defines data type! // // short datatype; // - 72 short bitpix; //!< Number bits/voxel. // // short bitpix; // - 74 short slice_start; //!< First slice index. // // short dim_un0; // - 76 float pixdim[8]; //!< Grid spacings. // // float pixdim[8]; // - 108 float vox_offset; //!< Offset into .nii file // // float vox_offset; // - 112 float scl_slope ; //!< Data scaling: slope. // // float funused1; // - 116 float scl_inter ; //!< Data scaling: offset. // // float funused2; // - 120 short slice_end; //!< Last slice index. // // float funused3; // - 122 char slice_code ; //!< Slice timing order. // - 123 char xyzt_units ; //!< Units of pixdim[1..4] // - 124 float cal_max; //!< Max display intensity // // float cal_max; // - 128 float cal_min; //!< Min display intensity // // float cal_min; // - 132 float slice_duration;//!< Time for 1 slice. // // float compressed; // - 136 float toffset; //!< Time axis shift. // // float verified; // - 140 int glmax; //!< ++UNUSED++ // // int glmax; // - 144 int glmin; //!< ++UNUSED++ // // int glmin; // + 0 int sizeof_hdr; //!< MUST be 348 // // int sizeof_hdr; // + 4 char data_type[10]; //!< ++UNUSED++ // // char data_type[10]; // + 14 char db_name[18]; //!< ++UNUSED++ // // char db_name[18]; // + 32 int extents; //!< ++UNUSED++ // // int extents; // + 36 short session_error; //!< ++UNUSED++ // // short session_error; // + 38 char regular; //!< ++UNUSED++ // // char regular; // + 39 char dim_info; //!< MRI slice ordering. // // char hkey_un0; // + + //--- was image_dimension substruct ---// + 40 short dim[8]; //!< Data array dimensions.// // short dim[8]; // + 56 float intent_p1 ; //!< 1st intent parameter. // // short unused8; // + // short unused9; // + 60 float intent_p2 ; //!< 2nd intent parameter. // // short unused10; // + // short unused11; // + 64 float intent_p3 ; //!< 3rd intent parameter. // // short unused12; // + // short unused13; // + 68 short intent_code ; //!< NIFTI_INTENT_* code. // // short unused14; // + 70 short datatype; //!< Defines data type! // // short datatype; // + 72 short bitpix; //!< Number bits/voxel. // // short bitpix; // + 74 short slice_start; //!< First slice index. // // short dim_un0; // + 76 float pixdim[8]; //!< Grid spacings. // // float pixdim[8]; // + 108 float vox_offset; //!< Offset into .nii file // // float vox_offset; // + 112 float scl_slope ; //!< Data scaling: slope. // // float funused1; // + 116 float scl_inter ; //!< Data scaling: offset. // // float funused2; // + 120 short slice_end; //!< Last slice index. // // float funused3; // + 122 char slice_code ; //!< Slice timing order. // + 123 char xyzt_units ; //!< Units of pixdim[1..4] // + 124 float cal_max; //!< Max display intensity // // float cal_max; // + 128 float cal_min; //!< Min display intensity // // float cal_min; // + 132 float slice_duration;//!< Time for 1 slice. // // float compressed; // + 136 float toffset; //!< Time axis shift. // // float verified; // + 140 int glmax; //!< ++UNUSED++ // // int glmax; // + 144 int glmin; //!< ++UNUSED++ // // int glmin; // */ diff --git a/public/js/brainbox-intro.js b/public/js/brainbox-intro.js index 68f41a8c..1bdcd674 100644 --- a/public/js/brainbox-intro.js +++ b/public/js/brainbox-intro.js @@ -1,31 +1,31 @@ -function startIntro() { - const intro = introJs(); +function startIntro(){ + var intro = introJs(); intro.setOptions({ steps: [ { - intro: 'Welcome to BrainBox.' + - 'BrainBox allows you to visualise and segment collaboratively ' + - 'any brain MRI dataset available online. Follow this tutorial ' + - 'to learn how to enter data into BrainBox, view it, edit it, ' + - 'and create collaborative segmentation projects. BrainBox is an ' + - 'open project – you will learn how to help us improving it by ' + - 'reporting bugs and suggestions to our GitHub repository' + intro: "Welcome to BrainBox." + + "BrainBox allows you to visualise and segment collaboratively " + + "any brain MRI dataset available online. Follow this tutorial " + + "to learn how to enter data into BrainBox, view it, edit it, " + + "and create collaborative segmentation projects. BrainBox is an " + + "open project – you will learn how to help us improving it by " + + "reporting bugs and suggestions to our GitHub repository" }, { - element: '#url', - intro: 'This is BrainBox\'s URL field. You can paste here a link ' + - 'to any MRI on the web. Currently, BrainBox supports Nifti ' + - 'format (.nii.gz files) and MGH format (.mgz files). Any link ' + - 'on the Web should work, from Zenodo, FigShare, DropBox, Amazon, ' + - 'etc. If you want to try BrainBox but you don\'t have any link to ' + - 'an MRI, you can select a brain from the "Take me to a brain" ' + - 'list, or browse among the community created projects.', + element: "#url", + intro: "This is BrainBox's URL field. You can paste here a link " + + "to any MRI on the web. Currently, BrainBox supports Nifti " + + "format (.nii.gz files) and MGH format (.mgz files). Any link " + + "on the Web should work, from Zenodo, FigShare, DropBox, Amazon, " + + "etc. If you want to try BrainBox but you don't have any link to " + + "an MRI, you can select a brain from the \"Take me to a brain\" " + + "list, or browse among the community created projects.", position: 'top' }, { element: '#go', - intro: 'Once you have entered the link to an MRI in the URL field ' + - 'click the Go button to go to BrainBox\'s viewer' + intro: "Once you have entered the link to an MRI in the URL field " + + "click the Go button to go to BrainBox's viewer" } ] }); diff --git a/public/js/brainbox.js b/public/js/brainbox.js index 62e1b3c0..9acb02ff 100644 --- a/public/js/brainbox.js +++ b/public/js/brainbox.js @@ -9,50 +9,50 @@ */ var BrainBox={ - version: 1, - debug: 1, - info:{}, - labelSets:null, - annotationType:["volume","text"], - accessLevels: ["none","view","edit","add","remove"], + version: 1, + debug: 1, + info:{}, + labelSets:null, + annotationType:["volume","text"], + accessLevels: ["none","view","edit","add","remove"], /** * @function traceLog */ - traceLog: function traceLog(f,l) { - /* - if(BrainBox.debug && (l==undefined || BrainBox.debug>l)) - // return "bb> "+(f.name)+" "+(f.caller?(f.caller.name||"annonymous"):"root"); - return "bb> ";//+(f.name); - */ - }, + traceLog: function traceLog(f,l) { + /* + if(BrainBox.debug && (l==undefined || BrainBox.debug>l)) + // return "bb> "+(f.name)+" "+(f.caller?(f.caller.name||"annonymous"):"root"); + return "bb> ";//+(f.name); + */ + }, - /* - JavaScript implementation of Java's hashCode method from - http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/ - */ - /** + /* + JavaScript implementation of Java's hashCode method from + http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/ + */ + /** * @function hash */ - hash: function hash(str) { - var l=BrainBox.traceLog(hash);if(l)console.log(l); - - var v0=0,v1,abc="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - for(i=0;i
'); - $("#atlasMaker").addClass('edit-mode'); - - BrainBox.loadScript('/lib/jquery-ui.min.js', function(){return window.jQuery.ui != undefined}) - .then(function(){return BrainBox.loadScript('/lib/pako/pako.min.js', function(){return window.pako != undefined})}) - .then(function(){return BrainBox.loadScript('/lib/purify.min.js', function(){return window.DOMPurify != undefined})}) - .then(function(){return BrainBox.loadScript('/lib/json-patch-duplex.min.js', function(){return window.jsonpatch != undefined})}) - .then(function(){return BrainBox.loadScript('/lib/npm-bundle.js')}) - .then(function(){return BrainBox.loadScript('/js/twoWayBinding.js')}) - .then(function(){ - $.when( - BrainBox.loadScript('/js/atlasMaker-draw.js'), - BrainBox.loadScript('/js/atlasMaker-interaction.js'), - BrainBox.loadScript('/js/atlasMaker-io.js'), - BrainBox.loadScript('/js/atlasMaker-paint.js'), - BrainBox.loadScript('/js/atlasMaker-ui.js'), - BrainBox.loadScript('/js/atlasMaker-ws.js'), - BrainBox.loadScript('/js/atlasMaker.js') - ).then(function () { - $.extend(AtlasMakerWidget,AtlasMakerDraw); - $.extend(AtlasMakerWidget,AtlasMakerInteraction); - $.extend(AtlasMakerWidget,AtlasMakerIO); - $.extend(AtlasMakerWidget,AtlasMakerPaint); - $.extend(AtlasMakerWidget,AtlasMakerUI); - $.extend(AtlasMakerWidget,AtlasMakerWS); - AtlasMakerWidget.initAtlasMaker($("#atlasMaker")) - .then(function() { - resolve(); - }); - }) - }); + initBrainBox: function initBrainBox() { + var l=BrainBox.traceLog(initBrainBox);if(l)console.log(l); + + var def=$.Deferred(); - // store state on exit - $(window).on('unload',BrainBox.unload); + // Add AtlasMaker and friends + $("#stereotaxic").html('
'); + $("#atlasMaker").addClass('edit-mode'); + + BrainBox.loadScript('/lib/jquery-ui.min.js', function(){return window.jQuery.ui != undefined}) + .then(function(){return BrainBox.loadScript('/lib/pako/pako.min.js', function(){return window.pako != undefined})}) + .then(function(){return BrainBox.loadScript('/lib/purify.min.js', function(){return window.DOMPurify != undefined})}) + .then(function(){return BrainBox.loadScript('/lib/json-patch-duplex.min.js', function(){return window.jsonpatch != undefined})}) + .then(function(){return BrainBox.loadScript('/lib/npm-bundle.js')}) + .then(function(){return BrainBox.loadScript('/js/twoWayBinding.js')}) + .then(function(){ + $.when( + BrainBox.loadScript('/js/atlasMaker-draw.js'), + BrainBox.loadScript('/js/atlasMaker-interaction.js'), + BrainBox.loadScript('/js/atlasMaker-io.js'), + BrainBox.loadScript('/js/atlasMaker-paint.js'), + BrainBox.loadScript('/js/atlasMaker-ui.js'), + BrainBox.loadScript('/js/atlasMaker-ws.js'), + BrainBox.loadScript('/js/atlasMaker.js') + ).then(function () { + $.extend(AtlasMakerWidget,AtlasMakerDraw); + $.extend(AtlasMakerWidget,AtlasMakerInteraction); + $.extend(AtlasMakerWidget,AtlasMakerIO); + $.extend(AtlasMakerWidget,AtlasMakerPaint); + $.extend(AtlasMakerWidget,AtlasMakerUI); + $.extend(AtlasMakerWidget,AtlasMakerWS); + AtlasMakerWidget.initAtlasMaker($("#atlasMaker")) + .then(function() { + def.resolve(); + }); + }) }); - }, - /** + + // store state on exit + $(window).on('unload',BrainBox.unload); + + return def.promise(); + }, + /** * @function configureBrainBox */ - configureBrainBox: function configureBrainBox(param) { - var l=BrainBox.traceLog(configureBrainBox);if(l)console.log(l); - - var def=new Promise(function(resolve, reject) { - var date=new Date(); - var index=param.annotationItemIndex||0; - - // Copy MRI from source - $("#msgLog").html("

Downloading from source to server..."); - - // Configure MRI into atlasMaker - if(param.info.success===false) { - date=new Date(); - $("#msgLog").append("

ERROR: "+param.info.message+"."); - console.log("

ERROR: "+param.info.message+"."); - return reject(); - } - BrainBox.info=param.info; + configureBrainBox: function configureBrainBox(param) { + var l=BrainBox.traceLog(configureBrainBox);if(l)console.log(l); + + var def=$.Deferred(); + var date=new Date(); + var index=param.annotationItemIndex||0; + + // Copy MRI from source + $("#msgLog").html("

Downloading from source to server..."); - var arr=param.url.split("/"); - var name=arr[arr.length-1]; - date=new Date(); - $("#msgLog").append("

Downloading from server..."); - - /** - * @todo Check it these two lines are of any use... - */ - param.dim=BrainBox.info.dim; // this allows to keep dim and pixdim through annotation changes - param.pixdim=BrainBox.info.pixdim; - - // re-instance stored configuration - var stored=localStorage.AtlasMaker; - if(stored) { - var stored=JSON.parse(stored); - if(stored.version && stored.version==BrainBox.version) { - for(var i=0;iERROR: "+param.info.message+"."); + console.log("

ERROR: "+param.info.message+"."); + return def.promise().reject(); + } + BrainBox.info=param.info; - // enact configuration in param, eventually overriding the stored one - if(param.view) { - AtlasMakerWidget.User.view=param.view; - AtlasMakerWidget.User.slice=null; // this will set the slider to the middle slice in case no slice were specified - } - if(param.slice) - AtlasMakerWidget.User.slice=param.slice; + var arr=param.url.split("/"); + var name=arr[arr.length-1]; + date=new Date(); + $("#msgLog").append("

Downloading from server..."); - if(param.fullscreen) - AtlasMakerWidget.fullscreen=param.fullscreen; - else - AtlasMakerWidget.fullscreen=false; + /** + * @todo Check it these two lines are of any use... + */ + param.dim=BrainBox.info.dim; // this allows to keep dim and pixdim through annotation changes + param.pixdim=BrainBox.info.pixdim; - AtlasMakerWidget.configureAtlasMaker(BrainBox.info,index) - .then(function(info2) { - BrainBox.info = info2; + // re-instance stored configuration + var stored=localStorage.AtlasMaker; + if(stored) { + var stored=JSON.parse(stored); + if(stored.version && stored.version==BrainBox.version) { + for(var i=0;iBrainBox.accessLevels.length-1) - accessLvl = 0; - if(accessLvl>=2) - AtlasMakerWidget.editMode = 1; - else - AtlasMakerWidget.editMode = 0; + // enact configuration in param, eventually overriding the stored one + if(param.view) { + AtlasMakerWidget.User.view=param.view; + AtlasMakerWidget.User.slice=null; // this will set the slider to the middle slice in case no slice were specified + } + if(param.slice) + AtlasMakerWidget.User.slice=param.slice; - resolve(); - }) - .catch(function(err) { - console.log("ERROR:",err); - reject(); - }); + if(param.fullscreen) + AtlasMakerWidget.fullscreen=param.fullscreen; + else + AtlasMakerWidget.fullscreen=false; + + AtlasMakerWidget.configureAtlasMaker(BrainBox.info,index) + .then(function(info2) { + BrainBox.info = info2; + + // check 'edit' access + var accessStr = BrainBox.info.mri.atlas[index].access; + var accessLvl = BrainBox.accessLevels.indexOf(accessStr); + if(accessLvl<0 || accessLvl>BrainBox.accessLevels.length-1) + accessLvl = 0; + if(accessLvl>=2) + AtlasMakerWidget.editMode = 1; + else + AtlasMakerWidget.editMode = 0; + + def.resolve(); + }) + .catch(function(err) { + console.log("ERROR:",err); + def.reject(); }); - }, - /** + + return def.promise(); + }, + /** * @function convertImgObjectURLToDataURL * @desc Encodes the ObjectURL obtained from the server jpg images as DataURL, * suitable to be stored as a string in localStorage */ - convertImgObjectURLToDataURL: function convertImgObjectURLToDataURL(objURL) { - return new Promise(function(resolve, reject) { - var x = new XMLHttpRequest(), f = new FileReader(); - x.open('GET',objURL,true); - x.responseType = 'blob'; - x.onload = function (e) { - f.onload = function (evt) { - resolve(evt.target.result); - }; - f.readAsDataURL(x.response); + convertImgObjectURLToDataURL: function convertImgObjectURLToDataURL(objURL) { + var def = $.Deferred(); + var x = new XMLHttpRequest(), f = new FileReader(); + x.open('GET',objURL,true); + x.responseType = 'blob'; + x.onload = function (e) { + f.onload = function (evt) { + def.resolve(evt.target.result); }; - x.send(); - }) + f.readAsDataURL(x.response); + }; + x.send(); + return def; }, - /** + /** * @function addCurrentMRIToHistory */ addCurrentMRIToHistory: function addCurrentMRIToHistory() { var l=BrainBox.traceLog(addCurrentMRIToHistory);if(l)console.log(l); - BrainBox.convertImgObjectURLToDataURL(AtlasMakerWidget.brain_img.img.src) - .then(function(data) { + BrainBox.convertImgObjectURLToDataURL(AtlasMakerWidget.brain_img.img.src) + .then(function(data) { var i, foundStored=false; var stored=localStorage.AtlasMaker; if(stored) { @@ -245,7 +249,7 @@ var BrainBox={ } if(foundStored==false) stored={version:BrainBox.version,history:[]}; - stored.history.push({ + stored.history.push({ url: BrainBox.info.source, view: AtlasMakerWidget.User.view?AtlasMakerWidget.User.view.toLowerCase():"sag", slice: AtlasMakerWidget.User.slice?AtlasMakerWidget.User.slice:0, @@ -253,143 +257,143 @@ var BrainBox={ lastVisited: (new Date()).toJSON() }); localStorage.AtlasMaker=JSON.stringify(stored); - }); + }); }, - /** + /** * @function unload */ - unload: function unload() { - /* - var l=BrainBox.traceLog(unload);if(l)console.log(l); - var i, obj0, obj1, foundStored=false; - var stored=localStorage.AtlasMaker; - if(stored) { - stored=JSON.parse(stored); - if(stored.version && stored.version==BrainBox.version) { - foundStored=true; - for(i=0;i=0 && currentIndex!=index) { - console.log("bb>> change selected annotation"); - $(table).find("tr").removeClass("selected"); - $(table).find('tbody tr:eq('+index+')').addClass("selected"); - AtlasMakerWidget.configureAtlasMaker(BrainBox.info,index); - } - }, - /** + selectAnnotationTableRow: function selectAnnotationTableRow(index,param) { + var l=BrainBox.traceLog(selectAnnotationTableRow);if(l)console.log(l); + + var table=param.table; + var currentIndex=$(table).find("tr.selected").index(); + + if(index>=0 && currentIndex!=index) { + console.log("bb>> change selected annotation"); + $(table).find("tr").removeClass("selected"); + $(table).find('tbody tr:eq('+index+')').addClass("selected"); + AtlasMakerWidget.configureAtlasMaker(BrainBox.info,index); + } + }, + /** * @function appendAnnotationTableRow */ - appendAnnotationTableRow: function appendAnnotationTableRow(irow,param) { - var l=BrainBox.traceLog(appendAnnotationTableRow);if(l)console.log(l); - - $(param.table).append(param.trTemplate); + appendAnnotationTableRow: function appendAnnotationTableRow(irow,param) { + var l=BrainBox.traceLog(appendAnnotationTableRow);if(l)console.log(l); + + $(param.table).append(param.trTemplate); - for(var icol=0;icol> 1)) : 0; - } - edge_table[i] = em; - } + var k = 0; + for(var i=0; i<8; ++i) { + for(var j=1; j<=4; j<<=1) { + var p = i^j; + if(i <= p) { + cube_edges[k++] = i; + cube_edges[k++] = p; + } + } + } + for(var i=0; i<256; ++i) { + var em = 0; + for(var j=0; j<24; j+=2) { + var a = !!(i & (1<> 1)) : 0; + } + edge_table[i] = em; + } } function SurfaceNets(data, dims, pixdims, level) { self.postMessage({msg:"SurfaceNets"}); - var vertices = []; - var faces = []; - var n = 0; - var x = new Int32Array(3); - var R = new Int32Array([1, (dims[0]+1), (dims[0]+1)*(dims[1]+1)]); - var grid = new Float32Array(8); - var buf_no = 1; + var vertices = []; + var faces = []; + var n = 0; + var x = new Int32Array(3); + var R = new Int32Array([1, (dims[0]+1), (dims[0]+1)*(dims[1]+1)]); + var grid = new Float32Array(8); + var buf_no = 1; - if(R[2] * 2 > buffer.length) - buffer = new Int32Array(R[2] * 2); + if(R[2] * 2 > buffer.length) + buffer = new Int32Array(R[2] * 2); - for(x[2]=0; x[2] 1e-6) - t = g0 / t; - else - continue; - for(var j=0, k=1; j<3; ++j, k<<=1) - { - var a = e0 & k; - var b = e1 & k; - if(a !== b) - v[j] += a ? 1.0 - t : t; - else - v[j] += a ? 1.0 : 0; - } - } - var s = 1.0 / e_count; - for(var i=0; i<3; ++i) - v[i] = (x[i] + s * v[i])*pixdims[i]; - buffer[m] = vertices.length; - vertices.push(v); - for(var i=0; i<3; ++i) - { - if(!(edge_mask & (1< 1e-6) + t = g0 / t; + else + continue; + for(var j=0, k=1; j<3; ++j, k<<=1) + { + var a = e0 & k; + var b = e1 & k; + if(a !== b) + v[j] += a ? 1.0 - t : t; + else + v[j] += a ? 1.0 : 0; + } + } + var s = 1.0 / e_count; + for(var i=0; i<3; ++i) + v[i] = (x[i] + s * v[i])*pixdims[i]; + buffer[m] = vertices.length; + vertices.push(v); + for(var i=0; i<3; ++i) + { + if(!(edge_mask & (1< {{title}} - @@ -19,11 +18,11 @@