diff --git a/.gitignore b/.gitignore index 62c2a7b1ed244379a500cd0ff1a56f4749c358f1..b98e42fa46aec675604452cdb2e106fec242b6de 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ github-keys.json -data +public/data public/project/braincatalogue-dev -node_modules \ No newline at end of file +node_modules +tmp \ No newline at end of file diff --git a/.htaccess b/.htaccess deleted file mode 100644 index b9b6a9410e0703ea8b48f662c4ad6d10f69186fa..0000000000000000000000000000000000000000 --- a/.htaccess +++ /dev/null @@ -1,62 +0,0 @@ -Header set Access-Control-Allow-Origin "*" - - - Header add Cache-Control "max-age=600" - - Header set Cache-Control "max-age=2419200, public" - - - -Options -Indexes -ErrorDocument 403 /404.php -ErrorDocument 404 /404.php - -# ---------------------------------------------------------------------- -# | Compression | -# ---------------------------------------------------------------------- - - - - - SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding - RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding - - - - AddOutputFilterByType DEFLATE "application/atom+xml" \ - "application/javascript" \ - "application/json" \ - "application/ld+json" \ - "application/manifest+json" \ - "application/rdf+xml" \ - "application/rss+xml" \ - "application/schema+json" \ - "application/vnd.geo+json" \ - "application/vnd.ms-fontobject" \ - "application/x-font-ttf" \ - "application/x-javascript" \ - "application/x-web-app-manifest+json" \ - "application/xhtml+xml" \ - "application/xml" \ - "font/eot" \ - "font/opentype" \ - "image/bmp" \ - "image/svg+xml" \ - "image/vnd.microsoft.icon" \ - "image/x-icon" \ - "text/cache-manifest" \ - "text/css" \ - "text/html" \ - "text/javascript" \ - "text/plain" \ - "text/vcard" \ - "text/vnd.rim.location.xloc" \ - "text/vtt" \ - "text/x-component" \ - "text/x-cross-domain-policy" \ - "text/xml" - - - AddEncoding gzip svgz - - diff --git a/app.js b/app.js index a9955fa7b8a6c5afdb692e3034e19245066ef5dd..11436e5774b65a18b209cab350e4213e237d0569 100644 --- a/app.js +++ b/app.js @@ -1,11 +1,13 @@ +"use strict"; + /* - Atlas Maker Server - Roberto Toro, 25 July 2014 - - Launch using > node atlasMakerServer.js + Atlas Maker Server + Roberto Toro, 25 July 2014 + + Launch using > node atlasMakerServer.js */ -var debug=1; +var debug = 0; var express = require('express'); var path = require('path'); @@ -13,378 +15,256 @@ 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 http = require('http'), - server = http.createServer(), - url = require('url'), - WebSocketServer = require('ws').Server, - websocket, - port = 8080; -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 request = require("request"); +var url = require("url"); +var async = require("async"); var mongo = require('mongodb'); var monk = require('monk'); var db = monk('localhost:27017/brainbox'); +var fs = require('fs'); +var expressValidator = require('express-validator'); -var Atlases=[]; -var Brains=[]; -var Users=[]; -var usrsckts=[]; -var uidcounter=1; -var niiTag=bufferTag("nii",8); -var mghTag=bufferTag("mgh",8); -var jpgTag=bufferTag("jpg",8); -var enterCommands=0; -var UndoStack=[]; - -console.log("atlasMakerServer.js"); -console.log(new Date()); -setInterval(function(){console.log(new Date())},60*60*1000); // time mark every 60 minutes -console.log("free memory",os.freemem()); +var atlasMakerServer = require('./js/atlasMakerServer.js'); // init web server -var routes = require('./routes/index'); -var users = require('./routes/users'); +//var routes = require('./routes/index'); +// var users = require('./routes/users'); + +/*jslint nomen: true*/ +var dirname = __dirname; // local directory +/*jslint nomen: false*/ var app = express(); app.engine('mustache', mustacheExpress()); -app.set('views', path.join(__dirname, 'views')); +app.set('views', path.join(dirname, 'views')); app.set('view engine', 'mustache'); -app.use(favicon(__dirname + '/public/favicon.png')); +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(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); +app.use(expressValidator()); app.use(cookieParser()); -app.use(express.static(path.join(__dirname, 'public'))); +app.use(express.static(path.join(dirname, 'public'))); -/* -app.use('/', routes); -app.use('/users', users); -*/ +app.use(function (req, res, next) { + req.dirname = dirname; + req.db = db; + next(); +}); + +//app.use('/', routes); +// app.use('/users', users); //{-----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")), - function(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", - resave:false, - saveUninitialized:false + 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(function(user, done) {done(null, user);}); -passport.deserializeUser(function(user, done) {done(null, user);}); +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()) {return next();} - else res.redirect('/'); -} -app.get('/secure-route-example',ensureAuthenticated,function(req, res){res.send("access granted");}); -app.get('/logout', function(req, res){ - console.log('logging out'); - req.logout(); - res.redirect('/'); + if (req.isAuthenticated()) { + return next(); + } + res.redirect('/'); +} +app.get('/secure-route-example', ensureAuthenticated, function (req, res) {res.send("access granted"); }); +app.get('/logout', function (req, res) { + req.logout(); + res.redirect('/'); }); -app.get('/loggedIn', function(req, res){ - if (req.isAuthenticated()) - res.send({loggedIn:true,username:req.user.username}); - else - res.send({loggedIn:false}); +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')); +app.get('/auth/github', passport.authenticate('github')); app.get('/auth/github/callback', - passport.authenticate('github',{failureRedirect:'/'}), - 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 - json={ - name: req.user.displayName, - nickname: req.user.username, - url:req.user._json.blog, - brainboxURL:"/user/"+req.user.username, - avatarURL:req.user._json.avatar_url, - joined: (new Date()).toJSON() - } - db.get('user').insert(json); - } - }); - res.redirect('/'); - }); + passport.authenticate('github', {failureRedirect: '/'}), + 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 + json = { + name: req.user.displayName, + nickname: req.user.username, + url: req.user._json.blog, + brainboxURL: "/user/" + req.user.username, + avatarURL: req.user._json.avatar_url, + joined: (new Date()).toJSON() + }; + db.get('user').insert(json); + } + }); + res.redirect('/'); + }); //-----} // GUI routes -app.get('/', function(req,res) { // /auth/github - var login= (req.isAuthenticated())? - (""+req.user.username+" (Log Out)") - :("Log in with GitHub"); - res.render('index', { - title: 'BrainBox', - login: login - }); +app.get('/', function (req, res) { // /auth/github + var login = (req.isAuthenticated()) ? + ("" + req.user.username + " (Log Out)") + : ("Log in with GitHub"); + res.render('index', { + title: 'BrainBox', + login: login + }); }); -app.get('/mri', function(req, res) { - var login= (req.isAuthenticated())? - (""+req.user.username+" (Log Out)") - :("Log in with GitHub"); - var myurl = req.query.url; - var hash = crypto.createHash('md5').update(myurl).digest('hex'); - - console.log("i'll query the db"); - db.get('mri').find({url:"/data/"+hash+"/"}, {fields:{_id:0},sort:{$natural:-1},limit:1}) - .then(function(json) { - json=json[0]; - console.log("query finished"); - if(json) { - console.log("a known mri file:",json); - res.render('mri', { - title: json.name||'Untitled MRI', - params: JSON.stringify(req.query), - mriInfo: JSON.stringify(json), - login: login - }); - } else { - console.log("an unknown mri file"); - console.log("check if its folder exists"); - if (!fs.existsSync(__dirname+"/public/data/"+hash)) { - console.log("didn't exist: creating it"); - fs.mkdirSync(__dirname+"/public/data/"+hash,0777); - } - - console.log("downloading the file"); - var myurl = req.query.url; - var filename = url.parse(req.query.url).pathname.split("/").pop(); - var dest=__dirname+"/public/data/"+hash+"/"+filename; - var file = fs.createWriteStream(dest,{mode:0777}); - console.log(filename); - request({uri:myurl}) - .pipe(fs.createWriteStream(dest)) - .on('close', function() { - console.log("file completely downloaded"); - console.log("loading it"); - getBrainAtPath(dest,function(mri) { - console.log("file loaded, get info"); - // create json file for new dataset - var ip = req.headers['x-forwarded-for'] || - req.connection.remoteAddress || - req.socket.remoteAddress || - req.connection.socket.remoteAddress; - var username=(req.isAuthenticated())?req.user.username:ip - var json = { - localpath: dest, - filename: filename, - success: true, - source: myurl, - url: "/data/"+hash+"/", - included: (new Date()).toJSON(), - dim: mri.dim, - pixdim: mri.pixdim, - owner:username, - mri: { - brain: filename, - atlas: [{ - owner:username, - created: (new Date()).toJSON(), - modified: (new Date()).toJSON(), - access: 'Read/Write', - type: 'volume', - filename: 'Atlas.nii.gz', - labels: '/labels/foreground.json' - }] - } - }; - console.log("insert metadata in the database"); - db.get('mri').insert(json); - res.render('mri', { - title: json.name||'Untitled MRI', - params: JSON.stringify(req.query), - mriInfo: JSON.stringify(json), - login: login - }); - }); - }); - } - }, function(err) { - console.error(err); - }); -}); -app.get('/user/:id', function(req, res) { - var login= (req.isAuthenticated())? - (""+req.user.username+" (Log Out)") - :("Log in with GitHub"); - var username=req.params.id; - db.get('user').findOne({nickname:username},"-_id") - .then(function(json) { - // gather user information on mri, atlas and projects - var mri,atlas,projects; - db.get('mri').find({owner:username,backup:{$exists:false}}) - .then(function(arr) { - mri=arr; - return db.get('mri').find({"mri.atlas":{$elemMatch:{owner:username}},backup:{$exists:false}}); - }) - .then(function(arr) { - atlas=arr; - return db.get('project').find({owner:username,backup:{$exists:false}}); - }) - .then(function(arr) { - projects=arr; - - var context={ - title: req.params.id, - userInfo: JSON.stringify(json), - login: login, - atlasFiles:[] - } - context.MRIFiles=mri.map(function(o){return { - url:o.source, - name:o.name, - included:dateFormat(o.included,"d mmm yyyy, HH:MM"), - volDimensions:o.dim.join(" x ") - }}); - atlas.map(function(o){ - var i,arr=[]; - for(i in o.mri.atlas) context.atlasFiles.push({ - url:o.source, - parentName:o.name, - name:o.mri.atlas[i].name, - project:o.mri.atlas[i].project, - projectURL:'/project/braincatalogue', - modified:dateFormat(o.mri.atlas[i].modified,"d mmm yyyy, HH:MM") - }); - }); - context.projects=projects.map(function(o){return { - project:o.name, - projectURL:o.brainboxURL, - numFiles:o.files.length, - numCollaborators:o.collaborators.length, - owner:o.owner, - modified:dateFormat(o.modified,"d mmm yyyy, HH:MM") - }}); - context.username=json.name; - context.nickname=json.nickname; - context.joined=dateFormat(json.joined, "dddd d mmm yyyy, HH:MM"); - context.numMRI=context.MRIFiles.length; - context.numAtlas=context.atlasFiles.length; - context.numProjects=context.projects.length; - context.avatar=json.avatarURL; - res.render('user',context); - }); - }); -}); -app.get('/project/:id', function(req, res) { - var login= (req.isAuthenticated())? - (""+req.user.username+" (Log Out)") - :("Log in with GitHub"); - db.get('project').findOne({shortname:req.params.id},"-_id") - .then(function(json) { - res.render('project', { - title: json.name, - projectInfo: JSON.stringify(json), - projectName: json.name, - login: login - }); - }); +app.use('/mri', require('./controller/mri/')); +app.use('/project', require('./controller/project/')); +app.use('/user', require('./controller/user/')); + +// app.get('/mri', function (req, res) { +// }); + +/* +app.get('/user/:id', function (req, res) { + + var login = (req.isAuthenticated()) ? + ("" + req.user.username + " (Log Out)") + : ("Log in with GitHub"); + var username = req.params.id; + db.get('user').findOne({nickname: username}, "-_id") + .then(function (json) { + // gather user information on mri, atlas and projects + var mri, atlas, projects; + db.get('mri').find({owner: username, backup: {$exists: false}}) + .then(function (arr) { + console.log(arr); + mri = arr; + return db.get('mri').find({"mri.atlas": {$elemMatch: {owner: username}}, backup: {$exists: false}}); + }) + .then(function (arr) { + console.log(arr); + atlas = arr; + return db.get('project').find({owner: username, backup: {$exists: false}}); + }) + .then(function (arr) { + projects = arr; + console.log(arr); + var context = { + title: req.params.id, + userInfo: JSON.stringify(json), + login: login, + atlasFiles: [] + }; + context.MRIFiles = mri.map(function (o) {return { + url: o.source, + name: o.name, + included: dateFormat(o.included, "d mmm yyyy, HH:MM"), + volDimensions: o.dim.join(" x ") + }; }); + atlas.map(function (o) { + var i; + console.log("WARNING: this is not working"); + for (i in o.mri.atlas) { + context.atlasFiles.push({ + url: o.source, + parentName: o.name, + name: o.mri.atlas[i].name, + project: o.mri.atlas[i].project, + projectURL: '/project/braincatalogue', + modified: dateFormat(o.mri.atlas[i].modified, "d mmm yyyy, HH:MM") + }); + } + }); + context.projects = projects.map(function (o) {return { + project: o.name, + projectURL: o.brainboxURL, + numFiles: o.files.length, + numCollaborators: o.collaborators.length, + owner: o.owner, + modified: dateFormat(o.modified, "d mmm yyyy, HH:MM") + }; }); + + context.username = json.name; + context.nickname = json.nickname; + context.joined = dateFormat(json.joined, "dddd d mmm yyyy, HH:MM"); + context.numMRI = context.MRIFiles.length; + context.numAtlas = context.atlasFiles.length; + context.numProjects = context.projects.length; + context.avatar = json.avatarURL; + + res.render('user', context).end(); + }); + }); }); + // API routes -app.get('/api/user/:name', function(req, res) { - db.get('user').findOne({nickname:req.params.name,backup:{$exists:false}},"-_id") - .then(function(json) { - if(json) { - 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(); - } - }); -}); -app.get('/api/project/:name', function(req, res) { - db.get('project').findOne({shortname:req.params.name,backup:{$exists:false}},"-_id") - .then(function(json) { - if(json) { - 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(); - } - }) -}); -app.get('/api/mri', function(req, res) { - var myurl=req.query.url; - var hash = crypto.createHash('md5').update(myurl).digest('hex'); - // shell equivalent: db.mri.find({source:"http://braincatalogue.org/data/Pineal/P001/t1wdb.nii.gz"}).limit(1).sort({$natural:-1}) - - db.get('mri').find({url:"/data/"+hash+"/",backup:{$exists:false}}, "-_id", {sort:{$natural:-1},limit:1}) - .then(function(json) { - json=json[0]; - if(json) { - 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(); - } - }, function(err) { - console.error(err); - }); +app.get('/api/user/:name', function (req, res) { + db.get('user').findOne({nickname: req.params.name, backup: {$exists: false}}, "-_id") + .then(function (json) { + if (json) { + 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(); + } + }); }); -app.get('/api/getLabelsets', function(req, res) { - var i,arr=fs.readdirSync(__dirname+"/public/labels/"),info=[]; - for(i in arr) { - var json=JSON.parse(fs.readFileSync(__dirname+"/public/labels/"+arr[i])); - info.push({ - name:json.name, - source:"/labels/"+arr[i] - }); - } - res.send(info); +*/ + +app.get('/api/getLabelsets', function (req, res) { + var i, arr = fs.readdirSync(dirname + "/public/labels/"), info = []; + for (i in arr) { + var json = JSON.parse(fs.readFileSync(dirname + "/public/labels/" + arr[i])); + info.push({ + name: json.name, + source: "/labels/" + arr[i] + }); + } + res.send(info); }); -app.post('/api/log', function(req, res) { - var json=req.body; - logToDatabase(json.key,json.value,json.username); - res.send(); +app.post('/api/log', function (req, res) { + var json = req.body; + db.get('log').insert({ + key: json.key, + value: json.value, + username: json.username, + date: (new Date()).toJSON(), + ip: req.headers['x-forwarded-for'] || + req.connection.remoteAddress || + req.socket.remoteAddress || + req.connection.socket.remoteAddress + }); + res.send(); }); + // init web socket server -initSocketConnection(); -server.on('request', app); -server.listen(port, function () { console.log('Listening on ' + server.address().port,server.address()) }); +atlasMakerServer.initSocketConnection(); +atlasMakerServer.dataDirectory = dirname + "/public"; // catch 404 and forward to error handler -app.use(function(req, res, next) { +app.use(function (req, res, next) { var err = new Error('Not Found'); err.status = 404; next(err); @@ -394,7 +274,7 @@ app.use(function(req, res, next) { // development error handler // will print stacktrace if (app.get('env') === 'development') { - app.use(function(err, req, res, next) { + app.use(function (err, req, res, next) { res.status(err.status || 500); res.render('error', { message: err.message, @@ -404,7 +284,7 @@ if (app.get('env') === 'development') { } // production error handler // no stacktraces leaked to user -app.use(function(err, req, res, next) { +app.use(function (err, req, res, next) { res.status(err.status || 500); res.render('error', { message: err.message, @@ -412,1284 +292,4 @@ app.use(function(err, req, res, next) { }); }); - -module.exports = app; - - -function 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(Atlases[i]); - } -} -function displayBrains() { - console.log("\n"+Brains.length+" Brains:"); - for(var i=0;i=2) console.log("[connection: message]",msg); - - var uid=getUserId(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=uid; - - // integrate paint messages - switch(data.type) { - case "intro": - receiveUserDataMessage(data,this); - break; - case "paint": - receivePaintMessage(data); - break; - case "requestSlice": - receiveRequestSliceMessage(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; - } - - // broadcast - var n=0; - for(var i in websocket.clients) { - // i-th user - var uid=getUserId(websocket.clients[i]); - - // do not auto-broadcast - if(data.uid==uid) { - if(debug>1) console.log("no broadcast to self"); - continue; - } - - // do not broadcast to unknown users - if( Users[data.uid]==undefined || Users[uid]==undefined) { - if(debug) console.log("User "+data.uid+": "+(Users[data.uid]==undefined)?"undefined":"defined"); - if(debug) console.log("User "+uid+": "+(Users[uid]==undefined)?"undefined":"defined"); - continue; - } - - if( Users[uid].iAtlas!=Users[data.uid].iAtlas && data.type!="chat" && data.type!="intro" ) { - if(debug) console.log("no broadcast to user "+Users[uid].username+" [uid: "+uid+"] of atlas "+Users[uid].specimenName+"/"+Users[uid].atlasFilename); - continue; - } - - if(data.type=="atlas") { - sendAtlasToUser(data.data,websocket.clients[i],false); - } - else { - websocket.clients[i].send(JSON.stringify(data)); - } - n++; - } - if(debug>=2) console.log("broadcasted to",n,"users"); - }); - - s.on('close',function(msg) { - console.log(new Date(),"[connection: close]"); - console.log("usrsckts length",usrsckts.filter(function(o){return o!=undefined}).length); - for(var i in usrsckts) - if(usrsckts[i].socket==s) - console.log("user",usrsckts[i].uid,"is closing connection"); - var uid=getUserId(this); - console.log("User ID "+uid+" is disconnecting"); - if(Users[uid]==undefined) { - console.log(" User ID "+uid+" is undefined."); - console.log("Users:",Users); - console.log("usrsckts:",usrsckts); - console.log(""); - } else if(Users[uid].dirname) { - console.log("User was connected to MRI "+ Users[uid].dirname+Users[uid].mri); - console.log("User was connected to atlas "+ Users[uid].dirname+Users[uid].atlasFilename); - } else { - console.log("WARNING: dirname was not defined"); - } - - // count how many users remain connected to the MRI after user leaves - sum=numberOfUsersConnectedToMRI(Users[uid].dirname+Users[uid].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 " - + Users[uid].dirname - + Users[uid].mri+": unloading it"); - unloadMRI(Users[uid].dirname+Users[uid].mri); - } - - // count how many users remain connected to the atlas after user leaves - sum=numberOfUsersConnectedToAtlas(Users[uid].dirname,Users[uid].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 " - + Users[uid].dirname - + Users[uid].atlasFilename+": unloading it"); - unloadAtlas(Users[uid].dirname,Users[uid].atlasFilename); - } - - // remove the user from the list - delete Users[uid]; - removeUser(this); - - // send user disconnect message to remaining users - sendDisconnectMessage(uid); - - // display the total number of connected users - var nusers=0; - for(var i in Users) nusers++; - if(debug) console.log("user",uid,"closed connection"); - if(debug) console.log(nusers+" connected"); - }); - }); - } catch (ex) { - console.log(new Date(),"ERROR: Unable to create a server",ex); - } -} -function receivePaintMessage(data) { - if(debug>=2) console.log("[receivePaintMessage]"); - - var msg=data.data; - var uid=data.uid; // user id - var user=Users[uid]; // user data - var c=msg.c; // command - var x=msg.x; // x coordinate - var y=msg.y; // y coordinate - var undoLayer=getCurrentUndoLayer(user); // current undoLayer for user - - // console.log("PaintMessage u",user,"user",user); - paintxy(uid,c,x,y,user,undoLayer); -} -function receiveRequestSliceMessage(data,user_socket) { - if(debug>=2) console.log("[receiveRequestSliceMessage]"); - - var uid=data.uid; // user id - var user=Users[uid]; // user data - var brainPath=user.dirname+user.mri; - var view=user.view; // user view - var slice=parseInt(user.slice); // user slice - - var brain=getBrainAtPath(__dirname+"/public"+brainPath,function(data){ - sendSliceToUser(data,view,slice,user_socket); - }); - - if(brain) { - sendSliceToUser(brain,view,slice,user_socket); - } -} -function receiveSaveMetadataMessage(data,user_socket) { - if(debug>=1) console.log("[receiveSaveMetadataMessage]"); - - var uid=data.uid; // user id - console.log("received save metadata message from user uid:",uid); - var json=data.metadata; - console.log("the metadata was json:",json); - json.modified=(new Date()).toJSON(); - console.log("the modification date was:",json.modified); - json.modifiedBy=Users[uid].username||"unknown"; - console.log("and the user that modified it:",json.modifiedBy); - console.log("going to insert this json object in the db"); - // mark previous one as backup - db.get('mri').update({url:json.url,backup:{$exists:false}},{$set:{backup:true}},{multi:true}); - // insert new one - db.get('mri').insert(json); -} -function receiveAtlasFromUserMessage(data,user_socket) { - if(debug>=1) console.log("[receiveAtlasFromUserMessage]"); - zlib.inflate(data.data,function(err,atlasData){ - // Save current atlas - var uid=data.uid; // user id - var iAtlas=Users[uid].iAtlas; - var atlas=Atlases[iAtlas]; - saveNifti(atlas); - - // Replace current atlas with new atlas - atlas.data=atlasData; - }); -} -function getBrainAtPath(brainPath,callback) { - if(debug) console.log("[getBrainAtPath]"); - var i; - for(i=0;i1) console.log("[sendSliceToUser]"); - - 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(new Date(),"ERROR: Cannot send slice to user"); - } -} - -function receiveUserDataMessage(data,user_socket) { - if(debug>1) console.log("[receiveUserDataMessage]"); - - - var uid=data.uid; - var user=data.user; - var i,atlasLoadedFlag,firstConnectionFlag,switchingAtlasFlag; - - firstConnectionFlag=(Users[uid]==undefined); - - user.uid=uid; - - if(data.description=="sendAtlas") { - // 1. Check if the atlas the user is requesting has not been loaded - atlasLoadedFlag=false; - - // check whether user is switching atlas. - switchingAtlasFlag=false; - if(Users[uid]) { - if((Users[uid].atlasFilename!=user.atlasFilename)||(Users[uid].dirname!=user.dirname)) { - switchingAtlasFlag=true; - } - } - - for(i in Atlases) - if(Atlases[i].dirname==user.dirname && Atlases[i].name==user.atlasFilename) { - atlasLoadedFlag=true; - break; - } - user.iAtlas=atlasLoadedFlag?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); - } - } else { - // The atlas requested has not been loaded before: - // Load the atlas s/he's requesting - addAtlas(user,function(atlas){sendAtlasToUser(atlas,user_socket,true)}); - } - } - - // 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(Users[uid]==undefined) - console.log("No User yet for id "+uid); - else - if(!Users[uid].hasOwnProperty('username')) { - console.log("User "+user.username+", id "+uid+" logged in"); - } - } - if(Users[uid]==null) Users[uid]={}; - for(var prop in user) Users[uid][prop]=user[prop]; - - // 4. Update number of users connected to atlas - if(firstConnectionFlag) { - var sum=0; - for(i in Users) - if(Users[i].dirname==user.dirname && Users[i].atlasFilename==user.atlasFilename) - sum++; - console.log(sum+" user"+((sum==1)?" is":"s are")+" connected to the 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(); - } -} - -/* - send new user information to old users, - and old users information to new user. -*/ -function sendPreviousUserDataMessage(new_uid) { - if(debug) console.log("[sendPreviousUserDataMessage]"); - - var new_user=Users[new_uid]; - var msg,found=false; - try { - var i,n=0; - for(i in websocket.clients) { - var uid=getUserId(websocket.clients[i]); - if(new_uid==uid) { - new_socket=websocket.clients[i]; - found=true; - break; - } - } - if(debug) console.log("[sendPreviousUserDataMessage] check if socket already exists:",found); - - if(found) { - for(i in websocket.clients) { - if(websocket.clients[i]==new_socket) - continue; - var uid=getUserId(websocket.clients[i]); - var msg=JSON.stringify({type:"intro",user:Users[uid],uid:uid,description:"old user intro to new user"}); - new_socket.send(msg); - n++; - } - if(debug) console.log("send user data from "+n+" users"); - } else { - console.log("ERROR: new user socket not found"); - } - - } catch (ex) { - console.log("ERROR: Unable to sendPreviousUserDataMessage",ex); - } -} -function sendAtlasToUser(atlasdata,user_socket,flagCompress) { - if(debug>=1) console.log("[sendAtlasToUser]"); - - if(flagCompress) { - zlib.gzip(atlasdata,function(err,atlasdatagz) { - try { - user_socket.send(Buffer.concat([atlasdatagz,niiTag]), {binary: true, mask: false}); - } catch(e) { - console.log(new Date(),"ERROR: Cannot send atlas data to user"); - } - }); - } else { - try { - user_socket.send(Buffer.concat([atlasdata,niiTag]), {binary: true, mask: false}); - } catch(e) { - console.log(new Date(),"ERROR: Cannot send atlas data to user"); - } - } -} -function broadcastPaintVolumeMessage(msg,user) { - if(debug) console.log("> broadcastPaintVolumeMessage()"); - - try { - var n=0,i,msg=JSON.stringify({"type":"paintvol","data":msg}); - for(i in websocket.clients) { - var uid=getUserId(websocket.clients[i]); - if( Users[uid]!=undefined && - Users[uid].iAtlas!=user.iAtlas ) - continue; - websocket.clients[i].send(msg); - n++; - } - if(debug) console.log("paintVolume message broadcasted to "+n+" users"); - - } catch (ex) { - console.log("ERROR: Unable to broadcastPaintVolumeMessage",ex); - } -} -function sendDisconnectMessage(uid) { - if(debug) console.log("> sendDisconnectMessage()"); - - try { - var n=0,i,msg=JSON.stringify({type:"disconnect",uid:uid}); - for(i in websocket.clients) { - websocket.clients[i].send(msg); - n++; - } - if(debug) console.log("user disconnect message sent to "+n+" users"); - - } catch (ex) { - console.log("ERROR: Unable to sendDisconnectMessage",ex); - } -} - -//======================================================================================== -// Load & Save -//======================================================================================== -function addAtlas(user,callback) { - if(debug) console.log("[add atlas]"); - - var atlas={ - name:user.atlasFilename, - specimen:user.specimenName, - dirname:user.dirname, - dim:user.dim - }; - - console.log("User requests atlas "+atlas.name+" from "+atlas.dirname); - - loadAtlasNifti(atlas,user.username,callback); - - Atlases.push(atlas); - user.iAtlas=Atlases.indexOf(atlas); - - atlas.timer=setInterval(function(){saveNifti(atlas)},60*60*1000); // 60 minutes -} -function loadNifti(nii) { - if(debug>=1) console.log("[loadNifti]"); - - 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); - - switch(mri.datatype) { - case 2: // UCHAR - mri.data=nii.slice(vox_offset); - break; - case 4: // SHORT - var tmp=nii.slice(vox_offset); - mri.data=new Int16Array(mri.dim[0]*mri.dim[1]*mri.dim[2]); - for(j=0;j=1) console.log("[loadAtlasNifti]"); - - // Load nifty label - - var path=__dirname+"/public"+atlas.dirname+atlas.name; - var datatype=2; - var vox_offset=352; - - if(!fs.existsSync(path)) { - console.log("Atlas "+path+" does not exists. Create a new one"); -/* -To create this buffer, I used an hex editor to dump the 1st 352 bytes of a -nii file, and converted them to decimal using: -gawk 'BEGIN{s="5C 01 ...";split(s,a," ");for(i=1;i<=352;i++)printf"%s,",strtonum("0x"a[i])}' -*/ - atlas.hdr=new Buffer([ - 92,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,230,0, - 44,1,14,1,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,8,0,0,0,0,0,128,191,195,245,168, - 62,195,245,168,62,195,245,168,62,0,0,0,0,0,0,128,63,0,0,128,63,0,0,128,63,0,0,176,67,0,0,0, - 0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - // AtlasMaker, braincatalogue.org - 65,116,108,97,115,77,97,107,101,114,44,32,98,114,97,105,110,99,97,116,97,108,111,103,117,101,46,111,114,103, - 0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,128,0,0,128,63,0,0,0,0,144,194,93,66,164,112, - 125,194,195,245,40,194,195,245,168,190,0,0,0,128,0,0,0,0,144,194,93,66,0,0,0,128,195,245,168, - 62,0,0,0,128,164,112,125,194,0,0,0,0,0,0,0,0,195,245,168,62,195,245,40,194,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,110,43,49,0,0,0,0,0]); - try { - atlas.hdr.writeUInt16LE(datatype,72,2); // datatype 2: unsigned char (8 bits/voxel) - atlas.hdr.writeUInt16LE(atlas.dim[0],42,2); - atlas.hdr.writeUInt16LE(atlas.dim[1],44,2); - atlas.hdr.writeUInt16LE(atlas.dim[2],46,2); - atlas.data=new Buffer(atlas.dim[0]*atlas.dim[1]*atlas.dim[2]); - - var i; - for(i=0;i=1) console.log("[saveNifti]"); - - if(atlas && atlas.dim ) { - if(atlas.data==undefined) { - console.log("ERROR: [saveNifti] atlas is still in Atlas array, but it has not data"); - return; - } - - var i,sum=0; - for(i=0;i=1) console.log("[logToDatabase]"); - - var ip=req.headers['x-forwarded-for'] || - req.connection.remoteAddress || - req.socket.remoteAddress || - req.connection.socket.remoteAddress; - var date=(new Date()).toJSON(); - - db.get('log').insert({ - key:key, - value:value, - username:username, - ip: ip, - date: date - }); -} - -//======================================================================================== -// Undo -//======================================================================================== - -/* TODO - UndoStacks should be stored separately for each user, in that way - when a user leaves, its undo stack is disposed. With the current - implementation, we'll be storing undo stacks for long gone users... -*/ - -function pushUndoLayer(user) { - if(debug) console.log("[pushUndoLayer] for user "+user.username+" "+user.specimenName+" "+user.atlasFilename); - - var undoLayer={"user":user,"actions":[]}; - UndoStack.push(undoLayer); - - if(debug) console.log("Number of layers: "+UndoStack.length); - - return undoLayer; -} -function getCurrentUndoLayer(user) { - if(debug>=2) console.log("[getCurrentUndoLayer]"); - - 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; -} -function undo(user) { - if(debug) console.log("[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 vol=Atlases[user.iAtlas].data; - var val; - - for(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 -//======================================================================================== -function paintxy(u,c,x,y,user,undoLayer) -/* - From 'user' we know slice, atlas, vol, view, dim. - [issue: undoLayer also has a user field. Maybe only undoLayer should be kept?] -*/ -{ - if(Atlases[user.iAtlas].data==undefined) { - console.log(new Date(),"ERROR: No atlas to draw into"); - return; - } - - var coord={"x":x,"y":y,"z":user.slice}; - - switch(c) { - case 'me': - case 'mf': - if(user.x0<0) { - 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 'f': // Fill, painting - fill(coord.x,coord.y,coord.z,user.penValue,user,undoLayer); - break; - case 'e': // Fill, erasing - fill(coord.x,coord.y,coord.z,0,user,undoLayer); - break; - case 'mu': // Mouse up (touch ended) - pushUndoLayer(user); - break; - case 'u': - undo(user); - break; - } -} -function paintVoxel(mx,my,mz,user,vol,val,undoLayer) { - var myView=user.view; - var dim=Atlases[user.iAtlas].dim; - var x,y,z; - var i=-1; - switch(myView) { - case 'sag': x=mz; y=mx; z=my;break; // sagital - case 'cor': x=mx; y=mz; z=my;break; // coronal - case 'axi': x=mx; y=my; z=mz;break; // axial - } - if(z>=0&&z=0&&y=0&&x=0&&z=0&&y=0&&x10*10) - console.log("WARNING: long line from",x1,y1,"to",x2,y2,user); - - // Define differences and error check - 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; - - for(j=0;j -dy) { - err -= dy; - x1 += sx; - } - if (e2 < dx) { - err += dx; - y1 += sy; - } - for(j=0;j0) { - n=Q.pop(); - x=n.x; - y=n.y; - if(vol[sliceXYZ2index(x,y,z,user)]==bval) { - paintVoxel(x,y,z,user,vol,val,undoLayer); - - i=sliceXYZ2index(x-1,y,z,user); - if(i>=0 && vol[i]==bval) - Q.push({"x":x-1,"y":y}); - - i=sliceXYZ2index(x+1,y,z,user); - if(i>=0 && vol[i]==bval) - Q.push({"x":x+1,"y":y}); - - i=sliceXYZ2index(x,y-1,z,user); - if(i>=0 && vol[i]==bval) - Q.push({"x":x,"y":y-1}); - - i=sliceXYZ2index(x,y+1,z,user); - if(i>=0 && vol[i]==bval) - Q.push({"x":x,"y":y+1}); - } - } -} -/* - Serve brain slices -*/ -function loadBrainNifti(nii,callback) { - - brain=loadNifti(nii); - - console.log(new Date()); - console.log("brain size",brain.data.length); - console.log("brain dim",brain.dim); - console.log("brain datatype",brain.datatype); - console.log("free memory",os.freemem()); - - 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; - - console.log("nii file loaded, sum:",sum); - console.log("min:",min,"max:",max); - callback(brain); -} -function loadBrainMGZ(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); - - switch(datatype) { - case 0: // MGHUCHAR - brain.data=data.slice(hdr_sz); - break; - case 1: // MGHINT - var tmp=data.slice(hdr_sz); - brain.data=new Uint32Array(brain.dim[0]*brain.dim[1]*brain.dim[2]); - for(j=0;jmax) max=brain.data[i]; - } - brain.sum=sum; - brain.min=min; - brain.max=max; - - console.log("mgh file loaded, sum:",sum); - console.log("min:",min,"max:",max); - callback(brain); -} -function loadBrainCompressed(path,callback) { - if(debug) - console.log("[loadBrainCompressed]",path); - if(!fs.existsSync(path)) { - console.log("ERROR: File does not exist:",path); - return; - } else { - var datagz; - try { - datagz=fs.readFileSync(path); - var ft=fileType(datagz); - console.log("fileType",ft); - var ext=path.split('.').pop(); - console.log("extension",ext); - - switch(ft.ext) { - case 'gz': { - switch(ext) { - case 'gz': - zlib.gunzip(datagz,function(err,nii){if(err) console.log("ERROR:",err);loadBrainNifti(nii,callback)}); - break; - case 'mgz': - zlib.gunzip(datagz,function(err,nii){if(err) console.log("ERROR:",err);loadBrainMGZ(nii,callback)}); - break; - } - break; - } - case 'zip': - zlib.inflate(datagz,function(err,nii){if(err) console.log("ERROR:",err);loadBrainNifti(nii,callback)}); - break; - } - } catch(e) { - console.log(new Date(),"ERROR: Cannot read brain data"); - } - } - return null; -} - -function drawSlice(brain,view,slice) { - var x,y,i,j; - var brain_W, brain_H; - var ys,ya,yc; - var val; - - switch(view) { - case 'sag': brain_W=brain.dim[1]; brain_H=brain.dim[2]; brain_D=brain.dim[0]; break; // sagital - case 'cor': brain_W=brain.dim[0]; brain_H=brain.dim[2]; brain_D=brain.dim[1]; break; // coronal - case 'axi': brain_W=brain.dim[0]; brain_H=brain.dim[1]; brain_D=brain.dim[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" + req.user.username + " (Log Out)") + : ("Log in with GitHub"); + var myurl = req.query.url, + hash = crypto.createHash('md5').update(myurl).digest('hex'); + req.db.get('mri').find({url: "/data/" + hash + "/"}, {fields: {_id: 0}, sort: {$natural: -1}, limit: 1}) + .then(function (json) { + console.log(json); + json = json[0]; + if (json) { + res.render('mri', { + title: json.name || 'Untitled MRI', + params: JSON.stringify(req.query), + mriInfo: JSON.stringify(json), + login: login + }); + } else { + (function (my, rq, rs) { + downloadMRI(my, rq, rs, function (obj) { + if(obj) { + req.db.get('mri').insert(obj); + rs.render('mri', { + title: obj.name || 'Untitled MRI', + params: JSON.stringify(rq.query), + mriInfo: JSON.stringify(obj), + login: login + }); + } else { + console.log("ERROR: Cannot read file"); + rs.render('mri', { + title: 'ERROR: Unreadable file', + params: JSON.stringify(rq.query), + mriInfo: JSON.stringify({}), + login: login + }); + + } + }); + }(myurl, req, res)); + } + }, function (err) { + console.error(err); + }); +}; + +var api_mri = function (req, res) { + var myurl = req.query.url, + hash = crypto.createHash('md5').update(myurl).digest('hex'); + // shell equivalent: req.db.mri.find({source:"http://braincatalogue.org/data/Pineal/P001/t1wreq.db.nii.gz"}).limit(1).sort({$natural:-1}) + + req.db.get('mri').find({url: "/data/" + hash + "/", backup: {$exists: false}}, "-_id", {sort: {$natural: -1}, limit: 1}) + .then(function (json) { + json = json[0]; + if (json) { + if (req.query.var) { + var i, arr = req.query.var.split("/"); + for (i in arr) { + json = json[arr[i]]; + } + } + res.json(json); + } else { + if (req.query.var) { + res.json({}); + } else { + (function (my, rq, rs) { + downloadMRI(my, rq, rs, function (obj) { + if(obj) { + rq.db.get('mri').insert(obj); + rs.json(obj); + } else { + console.log("ERROR: Cannot read file"); + rs.json({}); + } + }); + }(myurl, req, res)); + } + } + }, function (err) { + console.error(err); + }); +}; + +var mriController = function () { + this.validator = validator; + this.api_mri = api_mri; + this.mri = mri; +}; + +module.exports = new mriController(); + diff --git a/controller/mri/upload.controller.js b/controller/mri/upload.controller.js new file mode 100644 index 0000000000000000000000000000000000000000..a529a631e41637d423ebbb53deb2111dcc9448a3 --- /dev/null +++ b/controller/mri/upload.controller.js @@ -0,0 +1,185 @@ +"use strict"; + +var fs = require('fs'); +var atlasMakerServer = require('../../js/atlasMakerServer'); +//expressValidator = require('express-validator') + +var tokenDuration = 24 * (1000 * 3600); // in milliseconds + +var validator = function (req, res, next) { + //CHECK URL + req.checkBody('url', 'please enter a valid URL') + .notEmpty() + .isURL(); + req.checkBody('atlasName', 'please enter an Atlas Name') + .notEmpty() + .isAlphanumeric(); + req.checkBody('atlasProject', 'please enter an Atlas Project') + .notEmpty() + .isAlphanumeric(); + req.checkBody('atlasLabelSet', 'please enter an Atlas Project') + .notEmpty() + /* + Check for all these required fields: + url: url + atlas: a file + atlasName: Alphanumeric string + atlasProject: Alphanumeric string + atlasLabelSet: One of the labels available inside the /public/labels/ directory + */ + + var errors = req.validationErrors(); + console.log(errors); + if (errors) { + return res.send(errors).status(403).end(); + } else { + return next(); + } +}; + +var other_validations = function(req, res, next) { + + var token = req.body.token; + req.db.get("log").findOne({"token":token}) + .then(function (obj) { + console.log(obj); + if(obj) { + // Check token expiry date + var now = new Date(); + if(obj.expiryDate.getTime()-now.getTime() < 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 { + 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); + return res.json(err).status(403).end(); + } + }) + } else { + return res.send("ERROR: Token expired").status(403).end(); + } + } else { + return res.send("ERROR: Cannot find token").status(403).end(); + } + }) + .catch(function (err) { + console.log("ERROR:",err); + res.send().status(403).end(); + }); +} + +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; + + 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); + + // Check that there is not an atlas with this atlasName and atlasProject + // ==> something like this should be empty: db.get("mri").find({"mri.atlas.$.name":atlasName, "mri.atlas.$.project":atlasProject}) + + // Check that the dimensions of the nifti atlas are the same as its parent mri + atlasMakerServer.readNifti(files[0].path) + .then(function(atlas){ + console.log("atlas.dim: ",atlas.dim); + console.log("mri.dim: ",mri.dim); + + // check volume dimensions + if (atlas.dim[0] != mri.dim[0] || + atlas.dim[1] != mri.dim[1] || + atlas.dim[2] != mri.dim[2]) { + return res.json({error:"the Atlas doesn't match with the mri"}).status(400).end(); + } + + // create the atlas object + var date = new Date(); + var atlasMetadata = { + name: atlasName, + project: atlasProject, + access: "Read/Write", + created: date.toJSON(), + modified: date.toJSON(), + filename: Math.random().toString(36).slice(2)+".nii.gz", // automatically generated filename + originalname: files[0].originalname, + labels: atlasLabelSet, + owner: username, + type: "volume" + }; + + // move atlas file to final directory + fs.rename(req.dirname + "/" + files[0].path, req.dirname + "/public" + mri.url + atlasMetadata.filename); + + // update the database + mri.mri.atlas.push(atlasMetadata); + // mark previous version as backup + db.get('mri').update({url:req.body.url,backup:{$exists:false}},{$set:{backup:true}},{multi:true}); + // insert new version + db.get('mri').insert(mri); + + // return the full mri object ??? + return res.json(mri).status(200).end(); + }) + .catch(function (err) { + return res.json({error:"mri file is not valid: "+err}).status(400).end(); + }); +} + +var token = function token(req, res) { + if (req.isAuthenticated()) { + 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 + // generate a random token + obj.token = a + b; + // expiration date: now plus tokenDuration milliseconds + now = new Date(); + obj.expiryDate = new Date(now.getTime() + tokenDuration); + // record the username + obj.username = req.user.username; + // 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); + }, tokenDuration); + */ + + res.json(obj); + } else { + res.redirect('/'); + } +} + +var uploadController = function () { + this.validator = validator; + this.other_validations = other_validations; + this.upload = upload; + this.token = token; +}; + +module.exports = new uploadController(); + diff --git a/controller/project/index.js b/controller/project/index.js new file mode 100644 index 0000000000000000000000000000000000000000..541c8aedd2f1bb46a37da524b326ad2bfd8bafe2 --- /dev/null +++ b/controller/project/index.js @@ -0,0 +1,11 @@ +var express = require('express'); +var controller = require('./project.controller'); + +var router = express.Router(); + +// +router.get('/:projectName', controller.validator , controller.project); +router.get('/json/:projectName', controller.validator , controller.api_project); + + +module.exports = router; \ No newline at end of file diff --git a/controller/project/project.controller.js b/controller/project/project.controller.js new file mode 100644 index 0000000000000000000000000000000000000000..057da34567a3db1c7c7c5b6af4391d498f81cd73 --- /dev/null +++ b/controller/project/project.controller.js @@ -0,0 +1,84 @@ +var async = require("async"); + +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(); + console.log(errors); + if (errors) { + res.send(errors).status(403).end(); + } else { + return next(); + } +} + +var project = function(req, res) { + var login= (req.isAuthenticated())? + (""+req.user.username+" (Log Out)") + :("Log in with GitHub"); + req.db.get('project').findOne({shortname:req.params.projectName},"-_id") + .then(function(json) { + if (json) { + async.each( + json.files, + function(item,cb) { + req.db.get('mri').find({source:item,backup:{$exists:0}},{name:1,_id:0}) + .then(function(obj) { + if(obj[0]) { + json.files[json.files.indexOf(item)]={ + source: item, + name: obj[0].name + } + } else { + json.files[json.files.indexOf(item)]={ + source: item, + name: "" + } + } + cb(); + }); + }, + function() { + res.render('project', { + title: json.name, + projectInfo: JSON.stringify(json), + projectName: json.name, + login: login + }); + } + ); + } else { + res.status(404).send("Project Not Found"); + } + }); +} + +var api_project = function(req, res) { + req.db.get('project').findOne({shortname:req.params.projectName,backup:{$exists:false}},"-_id") + .then(function(json) { + if(json) { + 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(); + } + }) +}; + +var projectController = function(){ + this.validator = validator; + this.api_project = api_project; + this.project = project; +} + +module.exports = new projectController(); \ No newline at end of file diff --git a/controller/user/index.js b/controller/user/index.js new file mode 100644 index 0000000000000000000000000000000000000000..8771e82f5503d9a4d0e1a580d47d962761542840 --- /dev/null +++ b/controller/user/index.js @@ -0,0 +1,10 @@ +var express = require('express'); +var controller = require('./user.controller'); + +var router = express.Router(); + +router.get('/:userName', controller.validator , controller.user); +router.get('/json/:userName', controller.validator , controller.api_user); + + +module.exports = router; \ No newline at end of file diff --git a/controller/user/user.controller.js b/controller/user/user.controller.js new file mode 100644 index 0000000000000000000000000000000000000000..a2f5068f19c5aa485ad70c3d37395f845886754e --- /dev/null +++ b/controller/user/user.controller.js @@ -0,0 +1,122 @@ +var async = require("async"); +var dateFormat = require('dateformat'); + +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(); +} + +var user = function(req, res) { + var login = (req.isAuthenticated()) ? + ("" + req.user.username + " (Log Out)") + : ("Log in with GitHub"); + var username = req.params.userName; + req.db.get('user').findOne({nickname: username}, "-_id") + .then(function (json) { + if(json) { + // gather user information on mri, atlas and projects + var mri, atlas, projects; + + req.db.get('mri').find({owner: username, backup: {$exists: false}}) + .then(function (arr) { + mri = arr; + return req.db.get('mri').find({"mri.atlas": {$elemMatch: {owner: username}}, backup: {$exists: false}}); + }) + .then(function (arr) { + atlas = arr; + return req.db.get('project').find({owner: username, backup: {$exists: false}}); + }) + .then(function (arr) { + projects = arr; + var context = { + username: json.name, + nickname: json.nickname, + joined: dateFormat(json.joined, "dddd d mmm yyyy, HH:MM"), + avatar: json.avatarURL, + title: req.params.userName, + userInfo: JSON.stringify(json), + login: login, + atlasFiles: [] + }; + context.MRIFiles = mri.map(function (o) { + return { + url: o.source, + name: o.name, + included: dateFormat(o.included, "d mmm yyyy, HH:MM"), + volDimensions: o.dim.join(" x ") + }; + }); + atlas.map(function (o) { + var i; + console.log("WARNING: THE APPROPRIATE projectURL HAS TO BE SETUP"); + for (i in o.mri.atlas) { + context.atlasFiles.push({ + url: o.source, + parentName: o.name, + name: o.mri.atlas[i].name, + project: o.mri.atlas[i].project, + projectURL: '/project/braincatalogue', + modified: dateFormat(o.mri.atlas[i].modified, "d mmm yyyy, HH:MM") + }); + } + }); + context.projects = projects.map(function (o) {return { + project: o.name, + projectURL: o.brainboxURL, + numFiles: o.files.length, + numCollaborators: o.collaborators.length, + owner: o.owner, + modified: dateFormat(o.modified, "d mmm yyyy, HH:MM") + }; }); + context.numMRI = context.MRIFiles.length; + context.numAtlas = context.atlasFiles.length; + context.numProjects = context.projects.length; + + res.render('user',context); + }) + } else { + res.status(404).send("User Not Found"); + } + }) + .catch(function(err) { + console.log("ERROR:",err); + res.status(400).send("Error"); + }); +}; + +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) { + var i, arr = req.query.var.split("/"); + for (i in arr) { + json = json[arr[i]]; + } + } + res.send(json); + } else { + res.send(); + } + }); +}; + +var userController = function(){ + this.validator = validator; + this.api_user = api_user; + this.user = user; +} + +module.exports = new userController(); \ No newline at end of file diff --git a/js/.DS_Store b/js/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 Binary files /dev/null and b/js/.DS_Store differ diff --git a/js/atlasMakerServer.js b/js/atlasMakerServer.js new file mode 100644 index 0000000000000000000000000000000000000000..eadb05b63b4adac2d6ada4db4e7e24535708fe2a --- /dev/null +++ b/js/atlasMakerServer.js @@ -0,0 +1,1719 @@ +var request = require('request'); +var http = require('http'), + server = http.createServer(), + url = require('url'), + WebSocketServer = require('ws').Server, + websocket, + port = 8080; +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 niijs = require('nifti-js'); + +var mongo = require('mongodb'); +var monk = require('monk'); +var db = monk('localhost:27017/brainbox'); + +var atlasMakerServer = function() { + +var debug=1; +this.dataDirectory = ""; +var Atlases=[]; +this.Brains=[]; +var US=[]; +var uidcounter=1; +var enterCommands=0; +var UndoStack=[]; + +console.log("atlasMakerServer.js"); +console.log(new Date()); +setInterval(function(){console.log(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")); +}; + +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(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); + } +} +keypress(process.stdin); +process.stdin.on('keypress', function (ch, key) { + if(key) { + if(key.name==='c' && key.ctrl) { + console.log("Exit."); + process.exit(); + } + if(key.name==='escape') { + enterCommands=!enterCommands; + console.log("enterCommands: "+enterCommands); + } + if(enterCommands===0) { + if(key.name==='return') + console.log(); + else + process.stdout.write(key.sequence); + } else { + switch (key.name) { + case 'a': + displayAtlases(); + break; + case 'b': + displayBrains(); + break; + case 'u': + displayUsers(); + break; + } + } + } +}); +process.stdin.setRawMode(true); +process.stdin.resume(); + +//======================================================================================== +// Web socket +//======================================================================================== +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; + } + } +} + +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) { + saveNifti(Atlases[i]) + .then(function () { + console.log(" Atlas saved. Unloading it"); + clearInterval(Atlases[i].timer); + delete Atlases[i]; + console.log(" free memory",os.freemem()); + }); + break; + } + } +}; +var initSocketConnection = function initSocketConnection() { + traceLog(initSocketConnection); + + // WS connection + try { + websocket = new WebSocketServer({ server: server }); + + websocket.on("connection",function connection_fromInitSocketConnection(s) { + traceLog(connection_fromInitSocketConnection); + + 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 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; + + if(debug>1) { + console.log(); + console.log("data type:",data.type); + } + + // integrate paint messages + switch(data.type) { + case "intro": + receiveUserDataMessage(data,this); + break; + case "paint": + receivePaintMessage(data); + break; + case "requestSlice": + receiveRequestSliceMessage(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; + } + + // broadcast + var n=0; + 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 unknown 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.iAtlas!==sourceUS.User.iAtlas && data.type!=="chat" && data.type!=="intro" ) { + if(debug>1) console.log(" no broadcast to user "+targetUS.User.username+" [uid: "+targetUS.uid+"] of atlas "+targetUS.User.specimenName+"/"+targetUS.User.atlasFilename); + continue; + } + + if(data.type==="atlas") { + sendAtlasToUser(data.data,websocket.clients[i],false); + } + else { + websocket.clients[i].send(JSON.stringify(data)); + } + n++; + } + if(debug>2) console.log(" broadcasted to",n,"users"); + }); + + s.on('close',function close_fromInitSocketConnection(msg) { + traceLog(close_fromInitSocketConnection); + + console.log(" US length",US.filter(function(o){return o!=undefined}).length); + for(var i in US) + if(US[i].socket===s) + console.log(" User",US[i].uid,"is closing connection"); + var sourceUS=getUserFromSocket(this); + console.log(" User ID "+sourceUS.uid+" is disconnecting"); + if(sourceUS.User===undefined) { + console.log(" User ID "+sourceUS.uid+" is undefined."); + console.log("US:",US); + console.log(""); + } else if(sourceUS.User.dirname) { + console.log(" User was connected to MRI "+ sourceUS.User.dirname+sourceUS.User.mri); + console.log(" User was connected to atlas "+ sourceUS.User.dirname+sourceUS.User.atlasFilename); + } else { + console.log("WARNING: dirname was not defined"); + } + + // 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"); + 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"); + } else { + console.log(" No user connected to atlas " + + sourceUS.User.dirname + + sourceUS.User.atlasFilename+": unloading it"); + unloadAtlas(sourceUS.User.dirname,sourceUS.User.atlasFilename); + } + + // remove the user from the list + removeUser(this); + + // send user disconnect message to remaining users + sendDisconnectMessage(sourceUS.uid); + + // display the total number of connected users + var nusers=0; + for(var i in US) nusers++; + if(debug) console.log(" User",sourceUS.uid,"closed connection"); + if(debug) console.log(" "+nusers+" connected"); + }); + }); + 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 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(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); + }) +}; +var receiveSaveMetadataMessage = function receiveSaveMetadataMessage(data,user_socket) { + traceLog(receiveSaveMetadataMessage); + + var sourceUS=getUserFromUserId(data.uid); + var json=data.metadata; + json.modified=(new Date()).toJSON(); + json.modifiedBy=sourceUS.User.username||"unknown"; + // mark previous one as backup + db.get('mri').update({url:json.url,backup:{$exists:false}},{$set:{backup:true}},{multi:true}); + // insert new one + db.get('mri').insert(json); +}; +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]; + saveNifti(atlas) + .then(function () { + console.log(" Replace current atlas with new atlas"); + atlas.data=atlasData; + }); + }); +}; + +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"); + } +} + +var receiveUserDataMessage = function receiveUserDataMessage(data, user_socket) { + traceLog(receiveUserDataMessage); + + console.log(" data.description:", data.description); + + var sourceUS=getUserFromUserId(data.uid); + var User=data.user; + var i, + atlasLoadedFlag, + firstConnectionFlag=false, + switchingAtlasFlag=false; + + if(sourceUS.User===undefined) { + firstConnectionFlag=true; + } else if(sourceUS.User.isMRILoaded===false) { + firstConnectionFlag=true; + } + + User.uid=data.uid; + + if(data.description==="sendAtlas") { + // 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; + } + } + 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 { + // The atlas requested has not been loaded before: + // Load the atlas s/he's requesting + addAtlas(User) + .then(function(atlas) { + sendAtlasToUser(atlas.data,user_socket,true); + sourceUS.User.isMRILoaded=true; + }); + } + } + + // 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); + } +*/ + + // 5. Unload unused data (the check is only done if new data has been added) + if(data.description==="sendAtlas") { + unloadUnusedBrains(); + unloadUnusedAtlases(); + } +} + +/* + send new user information to old users, + and old users information to new user. +*/ +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:"intro",user:US[i].User,uid:US[i].uid,description:"old user intro to new user"}); + 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) { + zlib.gzip(atlasdata, function (err,atlasdatagz) { + try { + user_socket.send(Buffer.concat([atlasdatagz,niiTag]), {binary: true, mask: false}); + } catch(e) { + console.log("ERROR: Cannot send atlas data to user"); + } + }); + } else { + try { + user_socket.send(Buffer.concat([atlasdata,niiTag]), {binary: true, mask: false}); + } catch(e) { + console.log("ERROR: Cannot send atlas data to user"); + } + } +}; +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); + + try { + var n=0,i,msg=JSON.stringify({type:"disconnect",uid:uid}); + for(i in US) { + US[i].socket.send(msg); + n++; + } + if(debug) console.log(" user disconnect message sent to "+n+" users"); + + } catch (ex) { + console.log("ERROR: Unable to sendDisconnectMessage",ex); + } +} + +//======================================================================================== +// Load & Save +//======================================================================================== +var addAtlas = function addAtlas(User) { + traceLog(addAtlas); + + /* + 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) + */ + + var atlas = { + name:User.atlasFilename, + specimen:User.specimenName, + dirname:User.dirname, + dim:User.dim + }; + console.log(" User requests atlas "+atlas.name+" from "+atlas.dirname); + + 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(function () {saveNifti(atlas)},60*60*1000); // 60 minutes + + resolve(atlas); + }); + }); + + return pr; +}; +var getBrainAtPath = function getBrainAtPath(brainPath) { + traceLog(getBrainAtPath,1); + + /* + 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) + */ + 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) { + loadBrain(this.dataDirectory+brainPath) + .then(function _fromGetBrainAtPath(mri) { + var brain={path:brainPath,data:mri}; + Brains.push(brain); + resolve(mri); // callback: sendSliceToUser + }) + .catch(function (err) { + console.log("ERROR: getBrainAtPath cannot load brain. Corrupted file?",err); + reject(err); + }); + }); + + return pr; +} +this.getBrainAtPath = getBrainAtPath; + +var loadAtlas = function loadAtlas(User) { + traceLog(loadAtlas); + + /* + loadAtlas + input: A User structure providing information about the requested atlas + process: the requested atlas is sent if it was already loaded, loaded from disk + if it was already downloaded but not yet loaded, or created if it's a + new atlas. + output: an atlas (mri structure) + */ + var pr = new Promise(function promise_fromloadAtlas(resolve,reject) { + var path=this.dataDirectory+User.dirname+User.atlasFilename; + + if(!fs.existsSync(path)) { + // Create new empty atlas + 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(function (newAtlas) { + newAtlas.name = User.atlasFilename; + newAtlas.dirname = User.dirname; + + // log atlas creation + db.get('log').insert({ + key: "createAtlas", + value: JSON.stringify({atlasDirectory:User.dirname,atlasFilename:User.atlasFilename}), + username: User.username, + date: (new Date()).toJSON() + }); + + resolve(newAtlas); + }) + .catch(function (err) { + console.log("ERROR Cannot create nifti",err); + reject(err); + return; + }); + }) + .catch(function (err) { + console.log("ERROR Cannot get template brain for new atlas", err); + reject(err); + }); + } else { + // Load existing atlas + console.log(" Atlas found. Loading it"); +// <<<<<<< HEAD + readNifti(path) + .then(function (loadedAtlas) { + loadedAtlas.name = User.atlasFilename; + loadedAtlas.dirname = User.dirname; + resolve(loadedAtlas); + }) + .catch(function (err) { + console.log("ERROR Cannot read nifti", err); + reject(err); + }); +/*======= + atlas = readAtlasNifti(path, atlas) + .then(function(atlas){resolve(atlas.data)}); +>>>>>>> origin/dev-felix +*/ + + } + }); + return pr; +}; +//<<<<<<< HEAD +var loadBrain = function loadBrain(path) { + traceLog(loadBrain); + + /* + loadBrain + input: path to an mri file, .nii.gz and .mgz formats are recognised + output: an mri structure + */ + var pr = new Promise(function promise_fromloadBrain(resolve, reject) { + if(path.match(/.nii.gz$/)) { + readNifti(path) + .then(function (mri) { + resolve(mri); + }) + .catch(function (err) { + console.log("ERROR reading nii.gz file:",err); + reject(); + }); + } else + if(path.match(/.mgz$/)) { + readMGZ(path) + .then(function(mri) { + resolve(mri); + }) + .catch(function (err) { + console.log("ERROR reading mgz file:",err); + }); + } else { + reject(); + } + }); + + return pr; +} +/*======= + +var readAtlasNifti = function readAtlasNifti(path, atlas) +{ + var pr = new Promise(function promise_fromreadAtlasNifti(resolve,reject) { + var niigz; + try { + niigz=fs.readFileSync(path); + zlib.gunzip(niigz, function (err, nii) { + loadNifti(nii) + .then(function(mri) { + atlas.hdr=mri.hdr; + atlas.dim=mri.dim; + atlas.datatype=mri.datatype; + atlas.pixdim=mri.pixdim; + atlas.data=mri.data; + + var i,sum=0; + for(i=0;i>>>>>> origin/dev-felix +*/ + +var readNifti = function readNifti(path) { + traceLog(readNifti); + + /* + readNifti + input: path to a .nii.gz file + output: an mri structure + */ + + var pr = new Promise(function (resolve, reject) { + try { + var niigz=fs.readFileSync(path); + zlib.gunzip(niigz, function (err, nii) { + var i, j, tmp, sum, mri={}; + + // standard nii header + try { + var niiHdr=niijs.parseNIfTIHeader(nii); + var sizeof_hdr=niiHdr.sizeof_hdr; + mri.dim=niiHdr.dim.slice(1); + mri.pixdim=niiHdr.pixdim.slice(1); + mri.vox_offset=niiHdr.vox_offset; + + // nrrd-compatible header, computes space directions and space origin + if(niiHdr.qform_code>0) { + var nrrdHdr=niijs.parseHeader(nii); + mri.dir=nrrdHdr.spaceDirections; + mri.ori=nrrdHdr.spaceOrigin; + } 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); + + // test if the transformation looks incorrect. Reset it if it does + testS2VTransformation(mri); + + // manually parsed information + mri.hdr=nii.slice(0,352); + mri.datatype=nii.readUInt16LE(70); + + 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; + + resolve(mri); + }); + } catch(e) { + reject("ERROR Cannot uncompress nifti file:" + e); + } + }); + return pr; +}; +//<<<<<<< HEAD +this.readNifti = readNifti; +/*======= +var loadNifti = function loadNifti(nii) { + traceLog(loadNifti); + + var mri={}; + try { + // standard nii header + var niiHdr=niijs.parseNIfTIHeader(nii); + var sizeof_hdr=niiHdr.sizeof_hdr; + mri.dim=niiHdr.dim.slice(1); + mri.pixdim=niiHdr.pixdim.slice(1); + mri.vox_offset=niiHdr.vox_offset; + + // nrrd-compatible header, computes space directions and space origin + if(niiHdr.qform_code>0) { + var nrrdHdr=niijs.parseHeader(nii); + mri.dir=nrrdHdr.spaceDirections; + mri.ori=nrrdHdr.spaceOrigin; + } else { + mri.dir=[[mri.pixdim[0],0,0],[0,mri.pixdim[1],0],[0,0,mri.pixdim[2]]]; + mri.ori=[0,0,0]; + } + } catch (e) { + console.log(e); + return Promise.reject(); + } + // compute the transformation from voxel space to screen space + computeS2VTransformation(mri); + + // test if the transformation looks incorrect. Reset it if it does + testS2VTransformation(mri); +} +>>>>>>> origin/dev-felix +*/ + +var readMGZ = function readMGZ(data) { + traceLog(readMGZ); + + /* + readMGZ + input: path to a .mgz file + output: an mri structure + */ + + var pr = new Promise(function (resolve, reject) { + try { + var mgz=fs.readFileSync(path); + zlib.gunzip(mgz, function (err, mgh) { + if(err) { + reject(err); + return; + } + + var i, j, tmp, sum, mri={}; + var datatype; + var sz; + var hdr_sz=284; + var mri={}; + + mri.dim=[]; + mri.dim[0]=mgh.readInt32BE(4); + mri.dim[1]=mgh.readInt32BE(8); + mri.dim[2]=mgh.readInt32BE(12); + datatype=mgh.readInt32BE(20); + mri.pixdim=[]; + mri.pixdim[0]=mgh.readFloatBE(30); + mri.pixdim[1]=mgh.readFloatBE(34); + mri.pixdim[2]=mgh.readFloatBE(38); + sz = mri.dim[0]*mri.dim[1]*mri.dim[2]; + + switch(datatype) { + case 0: // MGHUCHAR + mri.data=mgh.slice(hdr_sz); + break; + case 1: // MGHINT + tmp=mgh.slice(hdr_sz); + 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 createNifti = function createNifti(templateMRI) { + traceLog(createNifti); + + /* + createNifti + input: a template mri structure + output: a new empty mri structure, datatype=2 (1 byte per voxel), same dimensions as template + */ + + var mri = {}, + props = ["dim", "pixdim", "hdr"], + datatype = 2, + vox_offset = 352, + sz, + i; + + // clone templateMRI + for( i in props) + mri[props[i]] = templateMRI[props[i]]; + + // get volume size + sz = mri.dim[0]*mri.dim[1]*mri.dim[2]; + + // update the header + mri.hdr = new Buffer(templateMRI.hdr); + mri.hdr.writeUInt16LE(datatype,70,2); // set datatype to 2:unsigned char (8 bits/voxel) + mri.hdr.writeFloatLE(vox_offset,108,4); // set voxel_offset to 352 (minimum size of a nii header) + + // zero the data + mri.data = new Buffer(sz); + 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; +}; +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); +/* + From 'User' we know slice, atlas, vol, view, dim. + [issue: undoLayer also has a User field. Maybe only undoLayer should be kept?] +*/ + 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}; + + switch(c) { + case 'me': + case 'mf': + if(User.x0<0) { + 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 'f': // Fill, painting + fill(coord.x,coord.y,coord.z,User.penValue,User,undoLayer); + break; + case 'e': // Fill, erasing + fill(coord.x,coord.y,coord.z,0,User,undoLayer); + break; + case 'mu': // Mouse up (touch ended) + pushUndoLayer(User); + break; + case 'u': + undo(User); + 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 x2=x; + var y2=y; + var i; + + if(Math.pow(x1-x2,2)+Math.pow(y1-y2,2)>10*10) + console.log("WARNING: long line from",x1,y1,"to",x2,y2,User); + + // Define differences and error check + 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; + + for(j=0;j -dy) { + err -= dy; + x1 += sx; + } + if (e2 < dx) { + err += dx; + y1 += sy; + } + for(j=0;j0) { + n=Q.pop(); + x=n.x; + y=n.y; + if(vol[sliceXYZ2index(x,y,z,User)]===bval) { + paintVoxel(x,y,z,User,vol,val,undoLayer); + + i=sliceXYZ2index(x-1,y,z,User); + if(i>=0 && vol[i]===bval) + Q.push({"x":x-1,"y":y}); + + i=sliceXYZ2index(x+1,y,z,User); + if(i>=0 && vol[i]===bval) + Q.push({"x":x+1,"y":y}); + + i=sliceXYZ2index(x,y-1,z,User); + if(i>=0 && vol[i]===bval) + Q.push({"x":x,"y":y-1}); + + i=sliceXYZ2index(x,y+1,z,User); + if(i>=0 && vol[i]===bval) + Q.push({"x":x,"y":y+1}); + } + } +} +/* + 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] + ]; +}; +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 + 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),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]); + + mri.s2v = { + sdim: [(wmax[0]-wmin[0])/Math.abs(wpixdim[0]),(wmax[1]-wmin[1])/Math.abs(wpixdim[1]),(wmax[2]-wmin[2])/Math.abs(wpixdim[2])], + s2w: invMat(w2s), + sori: [-wmin[0]/Math.abs(wpixdim[0]),-wmin[1]/Math.abs(wpixdim[1]),-wmin[2]/Math.abs(wpixdim[2])], + w2v: invMat(v2w), + wori: wori + }; + mri.v2w=v2w; + mri.wori=wori; +}; +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"); + } 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"); + mri.dir=[[mri.pixdim[0],0,0],[0,-mri.pixdim[1],0],[0,0,-mri.pixdim[2]]]; + mri.ori=[0,mri.dim[1],mri.dim[2]]; + 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 S2V = function S2V(s, mri) { + traceLog(S2V,3); + + var s2v=mri.s2v; + var i,w,s,v,v1=[]; + w=mulMatVec(s2v.s2w,subVecVec(s,s2v.sori)); // screen to world: w=s2w*(s-sori) + v=mulMatVec(s2v.w2v,subVecVec(w,s2v.wori)); // world to voxel + v1=[Math.round(v[0]),Math.round(v[1]),Math.round(v[2])]; // round to integer + return v1; +}; +var S2I = function S2I(s, mri) { + traceLog(S2I,3); + + var s2v=mri.s2v; + var i=null,w,s,v; + w=mulMatVec(s2v.s2w,subVecVec(s,s2v.sori)); // screen to world: w=s2w*(s-sori) + v=mulMatVec(s2v.w2v,subVecVec(w,s2v.wori)); // world to voxel + v=[Math.round(v[0]),Math.round(v[1]),Math.round(v[2])]; // round to integer + if(v[0]>=0&&v[0]=0&&v[0]=0&&v[0]l) - return "bb> "+(f.name)+" "+(f.caller?(f.caller.name||"annonymous"):"root"); - }, - - /* - JavaScript implementation of Java's hashCode method from - http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/ - */ - hash: function hash(str) { - console.log(BrainBox.traceLog(hash)); - - var v0=0,v1,abc="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - for(i=0;i'); - $("#atlasMaker").addClass('edit-mode'); - var s = document.createElement("script"); - s.src = "/js/atlasMaker.js"; - s.onload=function from_initBrainBox(){ - AtlasMakerWidget.initAtlasMaker($("#atlasMaker")) - .then(function() { - def.resolve(); - }); - } - document.body.appendChild(s); - - // store state on exit - $(window).unload(BrainBox.unload); - - return def.promise(); - }, - // configure is executed every time the mri data is replaced - configureBrainBox: function configureBrainBox(param) { - console.log(BrainBox.traceLog(configureBrainBox)); - - var def=$.Deferred(); - var date=new Date(); - - // Copy MRI from source - $("#msgLog").html("

Downloading from source to server..."); - $.getJSON("/php/brainbox.php",{ - action: "download", - url: param.url //,hash: BrainBox.hash(param.url) - }).done(function from_configureBrainBox(data) { - // Configure MRI into atlasMaker - //data=JSON.parse(data); - if(data.success==false) { - date=new Date(); - $("#msgLog").append("

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

ERROR: "+data.message+"."); - def.reject(); - return; - } - BrainBox.info=data; - - var arr=param.url.split("/"); - var name=arr[arr.length-1]; - date=new Date(); - $("#msgLog").append("

Downloading from server..."); - - 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: Cannot load MRI at specified URL."); - }); - - return def.promise(); - }, - unload: function unload() { - var foundStored=false; - var stored=localStorage.AtlasMaker; - if(stored) { - stored=JSON.parse(stored); - if(stored.version && stored.version==BrainBox.version) { - foundStored=true; - for(var i=0;i=0 && currentIndex!=index) { - console.log("bb>> change selected annotation"); - $(table).find("tr").removeClass("selected"); - $(this).addClass("selected"); - AtlasMakerWidget.configureAtlasMaker(BrainBox.info,index); - } - }, - appendAnnotationTableRow: function appendAnnotationTableRow(irow,param) { - console.log(BrainBox.traceLog(appendAnnotationTableRow)); - - $(param.table).append(param.trTemplate); - - for(var icol=0;icolmax) 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); +} + +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; +} +*/ + +}; + +module.exports = new MRILoader(); diff --git a/package.json b/package.json index cc5937e5322f2be49074aaeff0eb0f2ac29b0397..e42314f91424e08fa6b899767fd9c0ab4447cf01 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "start": "node ./bin/www" }, "dependencies": { + "async": "^2.0.1", "body-parser": "~1.8.1", "cookie-parser": "~1.3.3", "crypto": "0.0.3", @@ -13,6 +14,7 @@ "debug": "~2.0.0", "express": "~4.9.0", "express-session": "^1.14.0", + "express-validator": "^2.20.8", "file-type": "^3.8.0", "http": "0.0.0", "jpeg-js": "^0.2.0", @@ -20,7 +22,9 @@ "mongodb": "^2.2.5", "monk": "^3.1.1", "morgan": "~1.3.0", + "multer": "^1.2.0", "mustache-express": "^1.2.2", + "nifti-js": "^1.0.1", "passport": "^0.3.2", "passport-github": "^1.1.0", "passport-local": "^1.0.0", diff --git a/public/.DS_Store b/public/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..65bd21ea4f9a8d67d0a7779e0395a6d790fe32fd Binary files /dev/null and b/public/.DS_Store differ diff --git a/public/img/adjust.svg b/public/img/adjust.svg new file mode 100644 index 0000000000000000000000000000000000000000..bacf674404dd34b816a9fc2d4225920f0e1a768c --- /dev/null +++ b/public/img/adjust.svg @@ -0,0 +1,60 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/public/img/alpha.svg b/public/img/alpha.svg new file mode 100644 index 0000000000000000000000000000000000000000..0996cd3a038c3eaf512a8f7cc3a4ef95f106ad55 --- /dev/null +++ b/public/img/alpha.svg @@ -0,0 +1,86 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/public/img/brainbox.svg b/public/img/brainbox_.svg similarity index 99% rename from public/img/brainbox.svg rename to public/img/brainbox_.svg index c1d67cb701d52c7df00b7a467521813196aeda55..100d4da29d7c942878af6b9e194e9e72b59622e4 100644 --- a/public/img/brainbox.svg +++ b/public/img/brainbox_.svg @@ -9,21 +9,21 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="94.304131mm" - height="68.824051mm" - viewBox="0 0 334.1485 243.86475" + width="33.17865mm" + height="33.17865mm" + viewBox="0 0 0 117.56215" id="svg2" version="1.1" inkscape:version="0.91 r13725" - sodipodi:docname="brainbox.svg"> + sodipodi:docname="brainbox_.svg"> image/svg+xml - + @@ -135,7 +135,7 @@ inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" - transform="translate(-233.29586,-422.67515)"> + transform="translate(-343.68882,-422.67515)"> @@ -151,7 +151,7 @@ id="tspan13288" sodipodi:role="line">BrainBox + + + + + image/svg+xml + + + + + + + + + diff --git a/public/img/fullscreen.svg b/public/img/fullscreen.svg index e4d795736e197a783cdfc2545097f57d474a3e6a..b12577bacaacd7c492ba5763da4b06f7009b45ed 100644 --- a/public/img/fullscreen.svg +++ b/public/img/fullscreen.svg @@ -45,14 +45,14 @@ fit-margin-right="0" fit-margin-bottom="0" inkscape:zoom="0.35907429" - inkscape:cx="162.79003" - inkscape:cy="912.40976" + inkscape:cx="1237.7765" + inkscape:cy="689.61464" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="0" inkscape:current-layer="svg2" /> diff --git a/public/img/sun-o.svg b/public/img/sun-o.svg new file mode 100644 index 0000000000000000000000000000000000000000..634baa5d49d49fbbbe7091d4719ff942407846fa --- /dev/null +++ b/public/img/sun-o.svg @@ -0,0 +1,54 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/public/js/atlasMaker.js b/public/js/atlasMaker.js index 7afb98be0122740bd2da235678c2814e22870d0a..7e9f2f83423a314a7eab2a2672af372ccc1d5ed5 100755 --- a/public/js/atlasMaker.js +++ b/public/js/atlasMaker.js @@ -2,7 +2,7 @@ var AtlasMakerWidget = { //======================================================================================== // Globals //======================================================================================== - debug: 1, + debug: 2, container: null, // Element where atlasMaker lives brain_offcn: null, brain_offtx: null, @@ -15,14 +15,21 @@ var AtlasMakerWidget = { brain_Wdim: null, brain_Hdim: null, max: 0, + /* + {FIX: TRY TO KEEP ALL 3D STUFF INSIDE Users + */ brain_dim: new Array(3), brain_pixdim: new Array(3), brain_datatype: null, + /* + } + */ brain_img: { img: null, view: null, slice: null }, brain: 0, + alphaLevel: 0.5, annotationLength:0, measureLength: null, User: { view:null, @@ -110,18 +117,27 @@ var AtlasMakerWidget = { var me=AtlasMakerWidget; var l=me.traceLog(changeTool);if(l)console.log(l); + if(theTool.toLowerCase()==me.User.tool) + return; + switch(theTool) { case 'Paint': me.User.tool='paint'; - //me.User.penValue=1; break; case 'Erase': me.User.tool='erase'; - //me.User.penValue=0; 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; } me.sendUserDataMessage("change tool"); me.User.measureLength=null; @@ -137,14 +153,12 @@ var AtlasMakerWidget = { var me=AtlasMakerWidget; var l=me.traceLog(changeSlice,1);if(l)console.log(l); - console.log("to",x); - var max=$("#slice").data("max"); $("#slice").data("val",x); $("#slice .thumb")[0].style.left=(x*100/max)+"%"; me.User.slice=x; - me.sendUserDataMessage("change slice"); + //me.sendUserDataMessage("change slice"); me.drawImages(); }, @@ -230,7 +244,7 @@ var AtlasMakerWidget = { }, render3D: function render3D() { var me=AtlasMakerWidget; - var l=me.traceLog(reder3D);if(l)console.log(l); + var l=me.traceLog(render3D);if(l)console.log(l); // puts a fresh version of the segmentation in localStorage localStorage.brainbox=URL.createObjectURL(new Blob([me.encodeNifti()])); @@ -461,6 +475,7 @@ var AtlasMakerWidget = { mri.pixdim[1]=dv.getFloat32(84,true); mri.pixdim[2]=dv.getFloat32(88,true); vox_offset=dv.getFloat32(108,true); + switch(mri.datatype) { case 2: // UCHAR @@ -487,20 +502,20 @@ var AtlasMakerWidget = { if(me.User.view==null) me.User.view="sag"; - - // init query image + + var s2v=me.User.s2v; switch(me.User.view) { - case 'sag': me.brain_W=me.brain_dim[1]/*PA*/; me.brain_H=me.brain_dim[2]/*IS*/; me.brain_D=me.brain_dim[0]; me.brain_Wdim=me.brain_pixdim[1]; me.brain_Hdim=me.brain_pixdim[2]; break; // sagital - case 'cor': me.brain_W=me.brain_dim[0]/*LR*/; me.brain_H=me.brain_dim[2]/*IS*/; me.brain_D=me.brain_dim[1]; me.brain_Wdim=me.brain_pixdim[0]; me.brain_Hdim=me.brain_pixdim[2]; break; // coronal - case 'axi': me.brain_W=me.brain_dim[0]/*LR*/; me.brain_H=me.brain_dim[1]/*PA*/; me.brain_D=me.brain_dim[2]; me.brain_Wdim=me.brain_pixdim[0]; me.brain_Hdim=me.brain_pixdim[1]; break; // axial + 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); - me.User.dim=me.brain_dim; if(me.User.slice==null || me.User.slice>=me.brain_D) me.User.slice=parseInt(me.brain_D/2); @@ -576,17 +591,17 @@ var AtlasMakerWidget = { var me=AtlasMakerWidget; var l=me.traceLog(drawImages,1);if(l)console.log(l); - if(me.brain_img.img && me.brain_img.view==me.User.view && me.brain_img.slice==me.User.slice) { + if(me.brain_img.img) { 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.context.globalAlpha = 0.8; - me.context.globalCompositeOperation = "lighter"; me.drawAtlasImage(me.flagLoadingImg.view,me.flagLoadingImg.slice); - } else { + } + + if(!me.brain_img.img || me.brain_img.view!=me.User.view || me.brain_img.slice!=me.User.slice) { me.sendRequestSliceMessage(); } }, @@ -599,23 +614,34 @@ var AtlasMakerWidget = { var data=me.atlas.data; var dim=me.atlas.dim; - var val; + 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 ]=255; + me.atlas_px.data[ i+3 ]=alpha*me.alphaLevel; } me.atlas_offtx.putImageData(me.atlas_px, 0, 0); @@ -872,6 +898,11 @@ var AtlasMakerWidget = { 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; } // init annotation length counter @@ -898,6 +929,11 @@ var AtlasMakerWidget = { 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; } /* @@ -983,8 +1019,7 @@ var AtlasMakerWidget = { me.msg0=msg; } - var layer=me.atlas; - var dim=layer.dim; + var dim=me.atlas.dim; var coord={"x":x,"y":y,"z":usr.slice}; if(usr.x0<0) { @@ -1018,16 +1053,12 @@ var AtlasMakerWidget = { var i, ind, // voxel index - val, // voxel delta-value, such that -=val undoes - layer=me.atlas; + val; // voxel delta-value, such that -=val undoes for(i=0;i=0 && layer.data[me.slice2index(x-1,y,z,myView)]==bval) + if(atlas.data[me.slice2index(x,y,z,myView)]==bval) { + atlas.data[me.slice2index(x,y,z,myView)]=val; + if(x-1>=0 && atlas.data[me.slice2index(x-1,y,z,myView)]==bval) Q.push({"x":x-1,"y":y}); - if(x+1=0 && layer.data[me.slice2index(x,y-1,z,myView)]==bval) + if(y-1>=0 && atlas.data[me.slice2index(x,y-1,z,myView)]==bval) Q.push({"x":x,"y":y-1}); - if(y+11) console.log("received binary blob",msg.data.size,"bytes long"); 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) console.log("type: "+ext); + if(me.debug>1) console.log("type: "+ext); switch(ext) { - case "nii": { + case 'nii': { var inflate=new pako.Inflate(); inflate.push(data,true); - var layer=new Object(); - layer.data=inflate.result; - layer.name=me.atlasFilename; - layer.dim=me.brain_dim; + var atlas=new Object(); + atlas.data=inflate.result; + atlas.name=me.atlasFilename; + atlas.dim=me.brain_dim; - me.atlas=layer; + me.atlas=atlas; me.configureBrainImage(); me.configureAtlasImage(); @@ -1260,13 +1292,16 @@ var AtlasMakerWidget = { // setup download link var link=me.container.find("span#download_atlas"); - link.html(""+layer.name); + link.html(""+atlas.name); break; } - case "jpg": { + case 'jpg': { var urlCreator = window.URL || window.webkitURL; var imageUrl = urlCreator.createObjectURL(msg.data); 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; @@ -1280,6 +1315,9 @@ var AtlasMakerWidget = { if(flagFirstImage || me.flagLoadingImg.view!=me.User.view ||me.flagLoadingImg.slice!=me.User.slice) { me.sendRequestSliceMessage(); } + + // remove loading indicator + $("#loadingIndicator").hide(); } img.src=imageUrl; @@ -1293,7 +1331,6 @@ var AtlasMakerWidget = { // Message: interaction message var data=JSON.parse(msg.data); - if(me.debug) console.log("message: "+data.type); // [deprecated] // If we receive a message from an unknown user, @@ -1351,7 +1388,7 @@ var AtlasMakerWidget = { var u=data.uid; - if(me.Collab[u]==undefined) { + if(me.Collab[u]===undefined) { try { //var msg=""+data.user.username+" entered atlas "+data.user.specimenName+"/"+data.user.atlasFilename+"
" var msg=""+data.user.username+" entered
" @@ -1386,6 +1423,7 @@ var AtlasMakerWidget = { receiveChatMessage: function receiveChatMessage(data) { var me=AtlasMakerWidget; var l=me.traceLog(receiveChatMessage);if(l)console.log(l); + console.log(data); var theView=me.Collab[data.uid].view; var theSlice=me.Collab[data.uid].slice; @@ -1408,7 +1446,7 @@ var AtlasMakerWidget = { }, receivePaintMessage: function receivePaintMessage(data) { var me=AtlasMakerWidget; - var l=me.traceLog(receivePaintMessage);if(l)console.log(l); + var l=me.traceLog(receivePaintMessage,3);if(l)console.log(l); var msg=data.data; var u=data.uid; // user @@ -1448,7 +1486,11 @@ var AtlasMakerWidget = { if(me.flagLoadingImg.loading==true) return; try { - me.socket.send(JSON.stringify({type:"requestSlice"})); + me.socket.send(JSON.stringify({ + type:"requestSlice", + view:me.User.view, + slice:me.User.slice + })); me.flagLoadingImg.loading=true; me.flagLoadingImg.view=me.User.view; me.flagLoadingImg.slice=me.User.slice; @@ -1563,7 +1605,7 @@ var AtlasMakerWidget = { ].join("\n")); me.canvas = me.container.find('canvas')[0]; me.context = me.canvas.getContext('2d'); - + // Add div to display slice number me.container.find("#resizable").append(""); @@ -1600,7 +1642,7 @@ var AtlasMakerWidget = { $(document).keydown(function(e){me.keyDown(e)}); // configure annotation tools - me.slider($(".slider#slice"),me.changeSlice); + 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); @@ -1619,7 +1661,7 @@ var AtlasMakerWidget = { }) .then(function from_initAtlasMaker() { // Init web socket connection - me.initSocketConnection(); + return me.initSocketConnection(); }).then(function() { def.resolve() }); @@ -1630,6 +1672,8 @@ var AtlasMakerWidget = { var me=AtlasMakerWidget; var l=me.traceLog(configureAtlasMaker);if(l)console.log(l); + $("#loadingIndicator").show(); + // Load segmentation labels return $.getJSON(info.mri.atlas[index].labels,function from_configureAtlasMaker(d){me.configureOntology(d);}) .then(function from_configureAtlasMaker() { @@ -1648,6 +1692,7 @@ var AtlasMakerWidget = { $(".chose#plane .a:contains('"+view+"')").addClass("pressed"); } + me.sendUserDataMessage(); me.sendUserDataMessage("sendAtlas"); def.resolve(); }); @@ -1680,11 +1725,18 @@ var AtlasMakerWidget = { me.User.mri=info.mri.brain; me.User.specimenName=me.name; me.User.atlasFilename=info.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=info.dim; me.User.pixdim=info.pixdim; + + // compute space transformations + me.User.v2w=info.voxel2world; + me.User.wori=info.worldOrigin; + me.computeS2VTransformation(); + me.testS2VTransformation(); me.flagLoadingImg={loading:false}; @@ -1696,9 +1748,141 @@ var AtlasMakerWidget = { me.brain_pixdim=info.pixdim; else me.brain_pixdim=[1,1,1]; + return def.resolve().promise(); }, + /* + {Linear algebra + */ + computeS2VTransformation: function computeS2VTransformation() { + var me=AtlasMakerWidget; + var l=me.traceLog(computeS2VTransformation);if(l)console.log(l); + + var v2w=me.User.v2w; + var wori=me.User.wori; + var wpixdim=me.subVecVec(me.mulMatVec(v2w,[1,1,1]),me.mulMatVec(v2w,[0,0,0])); + var wvmax=me.addVecVec(me.mulMatVec(v2w,me.User.dim),wori); + var wvmin=me.addVecVec(me.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])]]; + var s2w=me.invMat(w2s); + + console.log(["v2w",v2w, "wori",wori, "wpixdim",wpixdim, "wvmax",wvmax, "wvmin",wvmin, "wmin",wmin, "wmax",wmax, "w2s",w2s]); + + me.User.s2v = { + sdim: [(wmax[0]-wmin[0])/Math.abs(wpixdim[0]),(wmax[1]-wmin[1])/Math.abs(wpixdim[1]),(wmax[2]-wmin[2])/Math.abs(wpixdim[2])], + s2w: s2w, + sori: [-wmin[0]/Math.abs(wpixdim[0]),-wmin[1]/Math.abs(wpixdim[1]),-wmin[2]/Math.abs(wpixdim[2])], + wpixdim: [Math.abs(wpixdim[0]),Math.abs(wpixdim[1]),Math.abs(wpixdim[2])], + w2v: me.invMat(v2w), + wori: wori + }; + }, + testS2VTransformation: function testS2VTransformation() { + var me=AtlasMakerWidget; + var l=me.traceLog(testS2VTransformation);if(l)console.log(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],mri.dim[2]]; + + // 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); + } + }, + + S2I: function S2I(s,mri) { + var me=AtlasMakerWidget; + var l=me.traceLog(S2I,3);if(l)console.log(l); + + var s2v=mri.s2v; + var i=null,w,s,v; + w=me.mulMatVec(s2v.s2w,me.subVecVec(s,s2v.sori)); // screen to world: w=s2w*(s-sori) + v=me.mulMatVec(s2v.w2v,me.subVecVec(w,mri.wori)); // world to voxel + v=[Math.round(v[0]),Math.round(v[1]),Math.round(v[2])]; // round to integer + if(v[0]>=0&&v[0]=0&&v[0]=0&&v[0]1) x=1; - x=Math.round(x*$("#slice").data("max")); - if(x!=$("#slice").data("val")) { - me.changeSlice(x); + x=x*$(el).data("max"); + if(x!=$(el).data("val")) { + callback(x); } } }; - $(document).on("mousemove",function from_slider(ev){movex(ev.clientX);}); - $(document).on("touchmove",function from_slider(ev){movex(ev.originalEvent.changedTouches[0].pageX);}); + $(document).on("mousemove",function from_slider(ev){movex(elem,ev.clientX);}); + $(document).on("touchmove",function from_slider(ev){movex(elem,ev.originalEvent.changedTouches[0].pageX);}); $(document).on("mouseup touchend",function from_slider(){$(elem).data({drag:false})}); $(elem).on('mousedown touchstart',function from_slider(){$(elem).data({drag:true})}); }, @@ -1733,6 +1917,10 @@ var AtlasMakerWidget = { var ch=$(elem).find(".a"); ch.each(function(c,d){ $(d).click(function(){ + if($(this).hasClass("pressed")) { + callback($(this).attr('title')); + return; + } ch.each(function(){$(this).removeClass("pressed")}); $(this).addClass("pressed"); if(callback) @@ -1755,6 +1943,9 @@ var AtlasMakerWidget = { callback(); }); } + /* + GUI Widgets} + */ }; /* 0 int sizeof_hdr; //!< MUST be 348 // // int sizeof_hdr; // diff --git a/public/js/brainbox.js b/public/js/brainbox.js index 0a1b9bc1a731453e459a8d609d429f3615899bab..6c59df0160db45208b8258ccc488d6aac460d70f 100644 --- a/public/js/brainbox.js +++ b/public/js/brainbox.js @@ -67,12 +67,11 @@ var BrainBox={ // Configure MRI into atlasMaker //data=JSON.parse(data); - if(data.success==false) { + if(data.success===false) { date=new Date(); $("#msgLog").append("

ERROR: "+data.message+"."); console.log("

ERROR: "+data.message+"."); - def.reject(); - return; + return def.promise().reject(); } BrainBox.info=data; @@ -141,8 +140,8 @@ var BrainBox={ stored={version:BrainBox.version,history:[]}; stored.history.push({ url:BrainBox.info.source, - view:AtlasMakerWidget.User.view.toLowerCase(), - slice:AtlasMakerWidget.User.slice, + view:AtlasMakerWidget.User.view?AtlasMakerWidget.User.view.toLowerCase():"sag", + slice:AtlasMakerWidget.User.slice?AtlasMakerWidget.User.slice:0, lastVisited:(new Date()).toJSON() }); localStorage.AtlasMaker=JSON.stringify(stored); @@ -200,14 +199,14 @@ var BrainBox={ var date=new Date(); // add data to annotations array BrainBox.info.mri.atlas.push({ - name:"Untitled", - project:"Untitled", + name:"", + project:"", access: "Read/Write", created: date.toJSON(), modified: date.toJSON(), filename: Math.random().toString(36).slice(2)+".nii.gz", // automatically generated filename labels: "/labels/foreground.json", - owner: "/user/"+AtlasMakerWidget.User.username, + owner: AtlasMakerWidget.User.username, type: "volume" }); diff --git a/public/lib/.DS_Store b/public/lib/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 Binary files /dev/null and b/public/lib/.DS_Store differ diff --git a/public/project/FCP1000/info.json b/public/project/FCP1000/info.json new file mode 100644 index 0000000000000000000000000000000000000000..6c3ff3212046feffed4eaa8501031ff531b50cc9 --- /dev/null +++ b/public/project/FCP1000/info.json @@ -0,0 +1,230 @@ +{ + "name": "FCP1000", + "shortname":"FCP1000", + "url":"http://www.nitrc.org/projects/fcon_1000", + "brainboxURL":"http://brainbox.pasteur.fr/project/FCP1000", + "created": "2016-08-06T17:50:31.572Z", + "owner":"r03ert0", + "collaborators":[ + ], + "files":[ + "https://dl.dropboxusercontent.com/u/9020198/data/sub23750-sub23750-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub17017-sub17017-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub19738-sub19738-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub23927-sub23927-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub29158-sub29158-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub30072-sub30072-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub31837-sub31837-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub37548-sub37548-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub52358-sub52358-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub54257-sub54257-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub54329-sub54329-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub76160-sub76160-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub73823-sub73823-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub77572-sub77572-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub80221-sub80221-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub81887-sub81887-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub90893-sub90893-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub91622-sub91622-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub96234-sub96234-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub00031-sub00031-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub90658-sub90658-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub01903-sub01903-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub03557-sub03557-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub04097-sub04097-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub94042-sub94042-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub85922-sub85922-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub86414-sub86414-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub12855-sub12855-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub06716-sub06716-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub06204-sub06204-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub14388-sub14388-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub23506-sub23506-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub18913-sub18913-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub27711-sub27711-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub27519-sub27519-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub27797-sub27797-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub28092-sub28092-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub33248-sub33248-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub36736-sub36736-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub27536-sub27536-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub38279-sub38279-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub40143-sub40143-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub46870-sub46870-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub47066-sub47066-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub48632-sub48632-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub49134-sub49134-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub61418-sub61418-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub61908-sub61908-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub57028-sub57028-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub63767-sub63767-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub66585-sub66585-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub67166-sub67166-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub68050-sub68050-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub73082-sub73082-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub75506-sub75506-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub77520-sub77520-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub77281-sub77281-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub81464-sub81464-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub47791-sub47791-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub54976-sub54976-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub85681-sub85681-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub82625-sub82625-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub87568-sub87568-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub91116-sub91116-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub91556-sub91556-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub86111-sub86111-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub95068-sub95068-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub00917-sub00917-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub09931-sub09931-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub14692-sub14692-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub16666-sub16666-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub17004-sub17004-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub17987-sub17987-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub97162-sub97162-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub18955-sub18955-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub21350-sub21350-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub23607-sub23607-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub24237-sub24237-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub28782-sub28782-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub30157-sub30157-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub36386-sub36386-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub39259-sub39259-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub44912-sub44912-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub45019-sub45019-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub91966-sub91966-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub45852-sub45852-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub46312-sub46312-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub49975-sub49975-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub50771-sub50771-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub51182-sub51182-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub53971-sub53971-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub56108-sub56108-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub56084-sub56084-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub55176-sub55176-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub56333-sub56333-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub56582-sub56582-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub58967-sub58967-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub58677-sub58677-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub59359-sub59359-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub63196-sub63196-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub64463-sub64463-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub67948-sub67948-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub73547-sub73547-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub75919-sub75919-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub76042-sub76042-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub76378-sub76378-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub77073-sub77073-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub61779-sub61779-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub84314-sub84314-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub87784-sub87784-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub87910-sub87910-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub91468-sub91468-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub93170-sub93170-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub98971-sub98971-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub99479-sub99479-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub00448-sub00448-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub00623-sub00623-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub02382-sub02382-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub02503-sub02503-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub07286-sub07286-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub05208-sub05208-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub08255-sub08255-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub08806-sub08806-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub09539-sub09539-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub13789-sub13789-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub10582-sub10582-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub13384-sub13384-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub13478-sub13478-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub20718-sub20718-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub22674-sub22674-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub26183-sub26183-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub26796-sub26796-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub28422-sub28422-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub16607-sub16607-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub28795-sub28795-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub28808-sub28808-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub29353-sub29353-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub19395-sub19395-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub30003-sub30003-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub30623-sub30623-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub32549-sub32549-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub33677-sub33677-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub34252-sub34252-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub35262-sub35262-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub40482-sub40482-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub40217-sub40217-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub37140-sub37140-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub44077-sub44077-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub44395-sub44395-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub47658-sub47658-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub47753-sub47753-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub35370-sub35370-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub48210-sub48210-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub49215-sub49215-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub41764-sub41764-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub53282-sub53282-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub53801-sub53801-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub48830-sub48830-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub54887-sub54887-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub55114-sub55114-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub55656-sub55656-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub41546-sub41546-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub51677-sub51677-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub57738-sub57738-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub59589-sub59589-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub59739-sub59739-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub59914-sub59914-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub62937-sub62937-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub63280-sub63280-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub65921-sub65921-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub66085-sub66085-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub68850-sub68850-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub66794-sub66794-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub70595-sub70595-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub72135-sub72135-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub71932-sub71932-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub73490-sub73490-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub76325-sub76325-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub77431-sub77431-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub76678-sub76678-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub82754-sub82754-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub86203-sub86203-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub85442-sub85442-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub86516-sub86516-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub78297-sub78297-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub86665-sub86665-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub87217-sub87217-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub82228-sub82228-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub89049-sub89049-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub92028-sub92028-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub93262-sub93262-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub93975-sub93975-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub94103-sub94103-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub94945-sub94945-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub95400-sub95400-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub95971-sub95971-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub97008-sub97008-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub98802-sub98802-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub94169-sub94169-000-MPRAGE/t1.nii.gz", + "https://dl.dropboxusercontent.com/u/9020198/data/sub98317-sub98317-000-MPRAGE/t1.nii.gz" + ], + + "access":{ + "files":{ + "view":"public", + "add":"collaborators", + "edit":"collaborators", + "remove":"owner" + }, + "collaborators": { + "view":"public", + "add":"owner", + "remove":"owner" + } + }, + "tags":[ + "human", + "open data" + ] +} diff --git a/public/project/braincatalogue-dev/info.json b/public/project/braincatalogue-dev/info.json new file mode 100644 index 0000000000000000000000000000000000000000..3c91d52e0f3934e19696a7cb644d8c0aa8e071a0 --- /dev/null +++ b/public/project/braincatalogue-dev/info.json @@ -0,0 +1,67 @@ +{ + "name": "Brain Catalogue", + "shortname":"braincatalogue", + "url":"http://braincatalogue.org", + "brainboxURL":"http://brainbox.dev/project/braincatalogue", + "created": "2016-07-11T16:03:47+0200", + "owner":"r03ert0", + "collaborators":[ + "katjaq", + "aniv0s" + ], + "files":[ + "http://braincatalogue.dev/data/Baboon/MRI.nii.gz", + "http://braincatalogue.dev/data/Black_rhinoceros/MRI-n4.nii.gz", + "http://braincatalogue.dev/data/Blackbuck/MRI-n4.nii.gz", + "http://braincatalogue.dev/data/Bottlenose_dolphin/MRI-n4.nii.gz", + "http://braincatalogue.dev/data/Cat/MRI-n4.nii.gz", + "http://braincatalogue.dev/data/Cheetah/MRI.nii.gz", + "http://braincatalogue.dev/data/Chimpanzee/MRI-n4.nii.gz", + "http://braincatalogue.dev/data/Crab-eating_macaque/MRI.nii.gz", + "http://braincatalogue.dev/data/Ferret/MRI-n4.nii.gz", + "http://braincatalogue.dev/data/Giant_panda/MRI.nii.gz", + "http://braincatalogue.dev/data/Giraffe/MRI.nii.gz", + "http://braincatalogue.dev/data/Gorilla/MRI-n4.nii.gz", + "http://braincatalogue.dev/data/Gray_wolf/MRI.nii.gz", + "http://braincatalogue.dev/data/Grey-cheeked_mangabey/MRI.nii.gz", + "http://braincatalogue.dev/data/House_mouse/Mouse.nii.gz", + "http://braincatalogue.dev/data/Human/MRI-n4.nii.gz", + "http://braincatalogue.dev/data/La_Plata_dolphin/MRI.nii.gz", + "http://braincatalogue.dev/data/Leopard/MRI-n4.nii.gz", + "http://braincatalogue.dev/data/Lion/MRI-n4.nii.gz", + "http://braincatalogue.dev/data/Night_monkey/MRI.nii.gz", + "http://braincatalogue.dev/data/Nile_crocodile/MRI.nii.gz", + "http://braincatalogue.dev/data/Nursehound/MRI.nii.gz", + "http://braincatalogue.dev/data/Okapi/MRI.nii.gz", + "http://braincatalogue.dev/data/Orangutan/MRI-n4.nii.gz", + "http://braincatalogue.dev/data/Ostrich/MRI.nii.gz", + "http://braincatalogue.dev/data/Plains_zebra/MRI.nii.gz", + "http://braincatalogue.dev/data/Platypus/MRI.nii.gz", + "http://braincatalogue.dev/data/Red_kangaroo/MRI-n4.nii.gz", + "http://braincatalogue.dev/data/Red_squirrel/MRI-n4.nii.gz", + "http://braincatalogue.dev/data/Red-necked_wallaby/MRI-n4.nii.gz", + "http://braincatalogue.dev/data/Rhesus_macaque/MRI.nii.gz", + "http://braincatalogue.dev/data/Sloth_bear/MRI-n4.nii.gz", + "http://braincatalogue.dev/data/Slow_loris/MRI-n4.nii.gz", + "http://braincatalogue.dev/data/Thylacine/MRI.nii.gz", + "http://braincatalogue.dev/data/White-headed_capuchin/MRI.nii.gz" + ], + + "access":{ + "files":{ + "view":"public", + "add":"collaborators", + "edit":"collaborators", + "remove":"owner" + }, + "collaborators": { + "view":"public", + "add":"owner", + "remove":"owner" + } + }, + "tags":[ + "comparative anatomy", + "non-human" + ] +} \ No newline at end of file diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 997983e78cd7bdb6b89e25e446f48ca3d283ee4f..e2a12d9c6577ceddba4eed5ad072f8226408e731 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -254,7 +254,7 @@ width:100%; height:100%; text-align:left; - background:#333; + background-color:#333; z-index:21; } .label-color { @@ -295,10 +295,13 @@ th { font-weight: bold; border-bottom:thin solid white; } -#info td { +table#annotations { + width:100%; +} +#annotations td { font-weight: normal; } -#info th, #info td { +#annotations th, #annotations td { padding:4px; } ul { @@ -330,9 +333,11 @@ img.icon { cursor:pointer; } -#NAATlogo { - position:relative; - z-index:25; +#NAATlogo.project-page { + position:absolute; + right:10px; + top:10px; + z-index:25; } /* Content: Splash @@ -356,7 +361,7 @@ img.icon { select { border:none; - background:none; + background:none; /* no color, no decoration */ color:white; -webkit-appearance: none; -moz-appearance: none; @@ -379,7 +384,7 @@ select { height:20px; top:50%; left:100%; - background:#333; + background-color:#333; transform:translate(-50%, -50%); border-radius:5px; z-index:5; diff --git a/public/templates/adjust.html b/public/templates/adjust.html new file mode 100644 index 0000000000000000000000000000000000000000..0e510be310e6d083ae10454865f2fcc1067e875c --- /dev/null +++ b/public/templates/adjust.html @@ -0,0 +1,95 @@ +

+ + +
+ +
+
+
+
+
+ + +
+ +
+
+
+
+
+ + +
+ +
+
+
+
+
+ + +
diff --git a/public/templates/tools.html b/public/templates/tools.html index cc8799a51cf23708f6c15600d6d0e6d04b1a49be..fce6b685dd5e9a394015da7230732a941fb86632 100644 --- a/public/templates/tools.html +++ b/public/templates/tools.html @@ -80,6 +80,7 @@
+
diff --git a/routes/index.js b/routes/index.js deleted file mode 100644 index e2cefa1c951bff41b040b083a0938eb294d029b7..0000000000000000000000000000000000000000 --- a/routes/index.js +++ /dev/null @@ -1,11 +0,0 @@ -var express = require('express'); -var router = express.Router(); - -/* GET home page. */ -router.get('/', function(req, res) { - res.render('index', { - title: 'Express' - }); -}); - -module.exports = router; diff --git a/routes/users.js b/routes/users.js deleted file mode 100644 index c00d7de16b09a1b429443e7f753e99ae09967481..0000000000000000000000000000000000000000 --- a/routes/users.js +++ /dev/null @@ -1,9 +0,0 @@ -var express = require('express'); -var router = express.Router(); - -/* GET users listing. */ -router.get('/', function(req, res) { - res.send('respond with a resource'); -}); - -module.exports = router; diff --git a/test/data/001.mgz b/test/data/001.mgz new file mode 100644 index 0000000000000000000000000000000000000000..2232c1f3ebd5a6421469a280b9272464f49754cd Binary files /dev/null and b/test/data/001.mgz differ diff --git a/test/data/bert_aseg.nii.gz b/test/data/bert_aseg.nii.gz new file mode 100644 index 0000000000000000000000000000000000000000..1792d6b7e37182d9234ff5a851f5f8b29af6869a Binary files /dev/null and b/test/data/bert_aseg.nii.gz differ diff --git a/test/data/bert_brain.mgz b/test/data/bert_brain.mgz new file mode 100644 index 0000000000000000000000000000000000000000..5d7dd3f581adfda8b191cce7c8c33666cab1d569 Binary files /dev/null and b/test/data/bert_brain.mgz differ diff --git a/test/data/bert_brain.nii.gz b/test/data/bert_brain.nii.gz new file mode 100644 index 0000000000000000000000000000000000000000..3554ef146c2177962c78ee4f0a6cf8b472316b3c Binary files /dev/null and b/test/data/bert_brain.nii.gz differ diff --git a/test/data/bert_wmparc.mgz b/test/data/bert_wmparc.mgz new file mode 100644 index 0000000000000000000000000000000000000000..e1f166df7bff6c677a7c3a0c23127f0955479b37 Binary files /dev/null and b/test/data/bert_wmparc.mgz differ diff --git a/test/data/bert_wmparc.nii.gz b/test/data/bert_wmparc.nii.gz new file mode 100644 index 0000000000000000000000000000000000000000..2687dc453ced61b09bf08be6cd38c6d1c7ff8a3f Binary files /dev/null and b/test/data/bert_wmparc.nii.gz differ diff --git a/views/index.mustache b/views/index.mustache index 296dd61bd4edd81ce4a161e06546bd561574d189..c039ed96876098b3815ac6524820e1255f9cfdfe 100644 --- a/views/index.mustache +++ b/views/index.mustache @@ -31,7 +31,7 @@
-
+

Real-time collaboration

@@ -156,7 +156,7 @@

- + group de neuroanatomie appliquée et théorique @@ -216,7 +216,7 @@ var brainsToTry=[ } // Add URL loading $("#url").keyup(function(e) { - console.log(e,e.target); + //console.log(e,e.target); if (e.keyCode == 13) { goToURL(e); } diff --git a/views/mri.mustache b/views/mri.mustache index 1de41d1ef35f17092f2f6e1ef204645332ac09d7..b2d4c0a83034abee944e6ad1b78e659120c96463 100644 --- a/views/mri.mustache +++ b/views/mri.mustache @@ -51,7 +51,7 @@

-
+
@@ -59,14 +59,14 @@
-
+
  • Name
  • -
  • Data source
  • +
  • Data source
  • Inclusion date
  • Annotations
    - +
    @@ -163,141 +163,150 @@ https://dl.dropboxusercontent.com/u/363467/mprage003.nii.gz MyLoginWidget.init($("#MyLogin")); */ -var params=JSON.parse('{{{params}}}'); -var mriInfo=JSON.parse('{{{mriInfo}}}'); +var params={{{params}}}; +var mriInfo={{{mriInfo}}}; var version=1; var info_proxy={}; var hash_old; -params.info=mriInfo; +if( $.isEmptyObject(mriInfo)) { + $("#stereotaxic").prepend("

    ERROR: Cannot read the data.

    The file is maybe corrupt?

    "); + console.log("ERROR: Cannot read data. The file is maybe corrupt?"); -var fullscreen=false; -if(params.fullscreen) - params.fullscreen=(params.fullscreen=="true"); + $("#annotationsPane").hide(); + $("#data").show(); -// Present a splash screen while loading -$("#intro").hide(); -$("#splash").show(); +} else { -// Load data -BrainBox.initBrainBox() -.then(function(){return BrainBox.loadLabelsets()}) -.then(function(){return BrainBox.configureBrainBox(params)}) -.then(function(){ - // Remove splash - $("#splash").hide(); + params.info=mriInfo; - var aParam = { - table: $("table#info"), - info_proxy: info_proxy, - info: BrainBox.info, - saveWarning: $("#saveAnnotations"), - trTemplate: $.map([ - "", - " ", - " ", - " ", // append label sets - " ", - " ", - " ", - " ", // append label sets - ""],function(o){return o}).join(), - objTemplate: [ - { typeOfBinding:2, - path:"mri.atlas.#.name" - }, - { typeOfBinding:2, - path:"mri.atlas.#.project" - }, - { typeOfBinding:2, - path:"mri.atlas.#.labels", - format: function(e,d){$(e).find("select").prop('selectedIndex',$.map(BrainBox.labelSets,function(o){return o.source}).indexOf(d))}, - /* - ----------------------------------------------------------- - NOTE: this is a temporary fix, the final version should be: - ----------------------------------------------------------- - format: function(e,d){$(e).find("select").prop('selectedIndex',$.map(BrainBox.labelSets,function(o){return o.source}).indexOf(d))}, - */ - parse: function(e){var name=$(e).find("select").val(),i=$.map(BrainBox.labelSets,function(o){return o.name}).indexOf(name);return BrainBox.labelSets[i].source} - }, - { typeOfBinding:1, - path:"mri.atlas.#.owner", - format: function(e,d){$.get("/api/user/"+d.split("/").pop()+"?var=nickname",function(r){$(e).text(r);$(e).attr('href',d)})} - }, - { typeOfBinding:1, - path:"mri.atlas.#.created", - format: date_format - }, - { typeOfBinding:1, - path:"mri.atlas.#.modified", - format: date_format - }, - { typeOfBinding:2,path:"mri.atlas.#.access", - format: function(e,d){$(e).find("select").prop('selectedIndex',BrainBox.access.indexOf(d))}, - parse: function(e){return $(e).find("select").val()} - } - ] + var fullscreen=false; + if(params.fullscreen) + params.fullscreen=(params.fullscreen=="true"); - }; + // Present a splash screen while loading + $("#intro").hide(); + $("#splash").show(); - // bind interface - bind2(info_proxy,BrainBox.info,"name",$("#name")); - bind1(info_proxy,BrainBox.info,"source",$("#source")); - bind1(info_proxy,BrainBox.info,"included",$("#included"),date_format); - for(var i=0;i", + " ", + " ", + " ", // append label sets + " ", + " ", + " ", + " ", // append label sets + ""],function(o){return o}).join(), + objTemplate: [ + { typeOfBinding:2, + path:"mri.atlas.#.name" + }, + { typeOfBinding:2, + path:"mri.atlas.#.project" + }, + { typeOfBinding:2, + path:"mri.atlas.#.labels", + format: function(e,d){$(e).find("select").prop('selectedIndex',$.map(BrainBox.labelSets,function(o){return o.source}).indexOf(d))}, + /* + ----------------------------------------------------------- + NOTE: this is a temporary fix, the final version should be: + ----------------------------------------------------------- + format: function(e,d){$(e).find("select").prop('selectedIndex',$.map(BrainBox.labelSets,function(o){return o.source}).indexOf(d))}, + */ + parse: function(e){var name=$(e).find("select").val(),i=$.map(BrainBox.labelSets,function(o){return o.name}).indexOf(name);return BrainBox.labelSets[i].source} + }, + { typeOfBinding:1, + path:"mri.atlas.#.owner", + format: function(e,d){$.get("/user/json/"+d.split("/").pop()+"?var=nickname",function(r){$(e).text(r);$(e).attr('href',d)})} + }, + { typeOfBinding:1, + path:"mri.atlas.#.created", + format: date_format + }, + { typeOfBinding:1, + path:"mri.atlas.#.modified", + format: date_format + }, + { typeOfBinding:2,path:"mri.atlas.#.access", + format: function(e,d){$(e).find("select").prop('selectedIndex',BrainBox.access.indexOf(d))}, + parse: function(e){return $(e).find("select").val()} + } + ] - // annotations table: connect pop-down menus - $(document).on('change', "table#info select", function(){ - var col=$("table#info tr:eq(0) th:eq("+$(this).closest('td')[0].cellIndex+")").text(); - var index=$(this).closest('tr')[0].rowIndex-1; - switch(col) { - case "Label Set": - var url=info_proxy["mri.atlas."+index+".labels"]; - $.getJSON(url,function(json) { - AtlasMakerWidget.configureOntology(json); - AtlasMakerWidget.brain_img.img=null; // to force redraw with new colors - AtlasMakerWidget.drawImages(); - }); - break; - case "Access": - break; - } - }); + }; - // annotation table: select row - $(document).on('click', "#info tr",BrainBox.selectAnnotationTableRow); - - // annotations table: add, remove and save annotations - $(document).on('click', "#addAnnotation", function(){BrainBox.addAnnotation(aParam)}); - $(document).on('click', "#removeAnnotation", function(){BrainBox.removeAnnotation(aParam)}); - $(document).on('click', "#saveAnnotations", function(){BrainBox.saveAnnotations(aParam)}); - - // annotations table: select the first row by default - $("table#info tr").removeClass("selected"); - $("table#info tr").eq(1).addClass("selected"); + // bind interface + bind2(info_proxy,BrainBox.info,"name",$("#name")); + bind1(info_proxy,BrainBox.info,"source",$("#source")); + bind1(info_proxy,BrainBox.info,"included",$("#included"),date_format); + for(var i=0;i diff --git a/views/project.mustache b/views/project.mustache index ae344c922184d7e7e5b846bad85a4dcc905550be..76e89b0ea6adabd7c662b503fa622aeb033e4291 100644 --- a/views/project.mustache +++ b/views/project.mustache @@ -16,6 +16,9 @@ border:thin solid #555; padding: 5px; } + #right { + background-color: black; + } @@ -51,7 +54,7 @@
    -
    +
    @@ -88,14 +91,55 @@
    - -
    ", - " ", + " ", " ", " ", ""].join(""); } $("#projectFiles tbody").append(str); for(var i=0;i - - -
    - - - -
    Name
    "+name+""+filename+"