Skip to content
Snippets Groups Projects
Unverified Commit dea00b28 authored by xgui3783's avatar xgui3783 Committed by GitHub
Browse files

Merge pull request #154 from HumanBrainProject/dev_next

may release
parents 0db96278 5d0ee74a
No related branches found
No related tags found
No related merge requests found
Showing
with 1377 additions and 2 deletions
......@@ -2,4 +2,5 @@ node_modules
dist
package-lock.json
src/res/raw
src/plugin_examples/*/
\ No newline at end of file
src/plugin_examples/*/
.vscode
FROM node:8 as builder
ARG BACKEND_URL
ENV BACKEND_URL=$BACKEND_URL
COPY . /iv
WORKDIR /iv
ENV VERSION=devNext
RUN npm i
RUN npm run build-aot
# prod container
FROM node:8-alpine
ARG PORT
ENV PORT=$PORT
ENV NODE_ENV=production
RUN apk --no-cache add ca-certificates
RUN mkdir /iv-app
WORKDIR /iv-app
# Copy built interactive viewer
COPY --from=builder /iv/dist/aot ./public
# Copy the express server
COPY --from=builder /iv/deploy .
# Copy the resources files needed to respond to queries
COPY --from=builder /iv/src/res/ext ./res
RUN npm i
EXPOSE $PORT
ENTRYPOINT [ "node", "server.js" ]
\ No newline at end of file
......@@ -7,7 +7,7 @@ Interactive Atlas Viewer is an frontend module wrapping around [nehuba](https://
A live version of the Interactive Atlas Viewer is available at [https://kg.humanbrainproject.org/viewer/](https://kg.humanbrainproject.org/viewer/). This section is useful for developers who would like to develop this project.
### General information
Interactive atlas viewer is built with [Angular (v6.0)](https://angular.io/) and [Bootstrap (v3.3.4)](http://getbootstrap.com/docs/3.3/). Some other notable packages used are: [ng2-charts](https://valor-software.com/ng2-charts/) for charts visualisation, [ngx-bootstrap (v 2.0.5)](https://valor-software.com/ngx-bootstrap/old/2.0.5/) for UI and [ngrx/platform](https://github.com/ngrx/platform) for state management.
Interactive atlas viewer is built with [Angular (v6.0)](https://angular.io/), [Bootstrap (v4)](http://getbootstrap.com/), and [fontawesome icons](https://fontawesome.com/). Some other notable packages used are: [ng2-charts](https://valor-software.com/ng2-charts/) for charts visualisation, [ngx-bootstrap](https://valor-software.com/ngx-bootstrap/) for UI and [ngrx/store](https://github.com/ngrx/platform) for state management.
### Prerequisites
......
# URL State
Interactive Atlas Viewer could be run with already selected state. It is possible to create or save application URL so, that it will contain specific data which will usefull to run application with already defined state. In URL, this specific data, is saved as URL query parameters.
## URL query parameters in Interactive Atlas Viewer
URL query parameters are variables which are located after URL and separated from the URL with "?" mark. URL query parameter variable contains variable name and value. Url query parameters are divided by "&" symbol. it is not possible to use whitespaces in the URL query. In Interactive Atlas Viewer, URL query parameters are used to save or create an application state. There are 6 main parameters which are used to save the application state. They are: navigation, niftiLayers, parcellationSelected, pluginState, regionsSelected and templateSelected.
Example of already filled URL looks like this:
```
https://dev-next-interactive-viewer.apps-dev.hbp.eu/?templateSelected=MNI+152+ICBM+2009c+Nonlinear+Asymmetric&parcellationSelected=Fibre+Bundle+Atlas+-+Long+Bundle&navigation=0_0_0_1__0.11092895269393921_0.8756000399589539_-0.44895267486572266_-0.13950254023075104__2500000__1430285_13076858_4495181__204326.41684726204&regionsSelected=31_51_32
```
4 parameters are mentioned inside the example URL:
- templateSelected
- parcellationSelected
- navigation
- regionsSelected
### Template Selected - parameter
Template Selected parameter, is used to or __select the template__ with using URL. From the given example we can see the parameter:
```
templateSelected=MNI+152+ICBM+2009c+Nonlinear+Asymmetric
```
In parameter value, whitespaces should be replaced with "+" symbol. So, actually, the selected template name is:
```
MNI 152 ICBM 2009c Nonlinear Asymmetric
```
To select a template from URL it is not required to have any other parameter. "Parcellation Selected" and "Navigation" parameters will be automatically added by default values. So, before the application automatically adds parameters, the URL will look like this:
```
https://dev-next-interactive-viewer.apps-dev.hbp.eu/?templateSelected=MNI+152+ICBM+2009c+Nonlinear+Asymmetric
```
### Parcellation Selected - parameter
Given parameter is used to select parcellation on the template. As in template selection parameter value, whitespaces are changed with "+" symbol. To use Parcellation parameter, selection template should be in URL parameters too. Navigation parameter will be automatically added by default values. So, before the application automatically adds parameters, the URL will look like this:
```
https://dev-next-interactive-viewer.apps-dev.hbp.eu/?templateSelected=MNI+152+ICBM+2009c+Nonlinear+Asymmetric&parcellationSelected=Fibre+Bundle+Atlas+-+Long+Bundle
```
From this example selected parcellation name is
```
Fibre Bundle Atlas - Long Bundle
```
### Navigation - parameter
Navigation parameter is to determine orientation, position and zoom of 2d and 3d parts of the selected template. It does not depend on selected parcellation, but it depends on the selected template.
Navigation parameter itself includes 5 fields - orientation, perspectiveOrientation, perspectiveZoom, position and zoom. They are sorted appropriate and divided by "__" symbol.
This is the navigation parameters part from the example
```
navigation=0_0_0_1__0.11092895269393921_0.8756000399589539_-0.44895267486572266_-0.13950254023075104__2500000__1430285_13076858_4495181__204326.41684726204
```
**orientation** - the field is used to determine an orientation of the 2D part in atlas viewer. The field contains with 4 numbers, divided by "_" symbol. 3 of these numbers should have value "0" and one should have value "1". Value 1 will determine the orientation of the 2D image. In the example orientation is a first element - "0_0_0_1".
**perspectiveOrientation** - the field is used to determine an orientation of the 3D part in atlas viewer. The field contains with 4 numbers, divided by "_" symbol. In the example perspective orientation is the second element
```
0.11092895269393921_0.8756000399589539_-0.44895267486572266_-0.13950254023075104"
```
**perspectiveZoom** - the field is used to determine zoom in 3D pard in atlas viewer. It is the single number and is the third element - "2500000"
**position** - the field is used to determine the position of the dot where the camera is oriented in both 2D and 3D parts of atlas viewer. Field contains with 3 numbers, divided by "_" symbol - "1430285_13076858_4495181".
**zoom** - the field is used to determine zoom in 2D pard in atlas viewer. It is the single number and is the last element - "204326.41684726204"
### Regions Selected - parameter
Every region of selected parcellation has its own integer number as id. To select the region from URL, with templateSelected and parcellationSelected parameters, URL should contain the regionSelected parameter. In this parameter. In the given example, we can see the region parameter part.
```
regionsSelected=31_51_32
```
in parameter, different regions are divided with "_" symbol. It meant that in the given example, there are 3 selected parameters - 31, 51 and 32.
The region belongs to parcellation, so user can not add regionsSelected without templateSelected and parcellationSelected parameters. Navigation parameter will be automatically added by default values.
### Nifti Layers - parameter
Nifti Layers parameter adds to the application URL, when the user selects the nifti layer of selected regions. The value of this parameter is text. Especially, it is a link where the nifti file is located.
Example
```
https://dev-next-interactive-viewer.apps-dev.hbp.eu/?niftiLayers=https%3A%2F%2Fneuroglancer.humanbrainproject.org%2Fprecomputed%2FJuBrain%2Fv2.2c%2FPMaps%2FIPL_PF.nii&templateSelected=MNI+Colin+27&parcellationSelected=JuBrain+Cytoarchitectonic+Atlas
```
You see, that value of parameter "niftiLayers" is
```
https%3A%2F%2Fneuroglancer.humanbrainproject.org%2Fprecomputed%2FJuBrain%2Fv2.2c%2FPMaps%2FIPL_PF.nii
```
This is the encoded text for URL where %3A is for ":" and %2F is for "/". So, nifti file's URL is
```
https://Fneuroglancer.humanbrainproject.org/Fprecomputed/JuBrain/v2.2c/PMaps/IPL_PF.nii
```
## Contributing
Feel free to raise an issue in this repo and/or file a PR.
.env
res/*
raw
const path = require('path')
const express = require('express')
const app = express()
const session = require('express-session')
const MemoryStore = require('memorystore')(session)
app.disable('x-powered-by')
if (process.env.NODE_ENV !== 'production') {
app.use(require('cors')())
}
/**
* load env first, then load other modules
*/
const configureAuth = require('./auth')
const store = new MemoryStore({
checkPeriod: 86400000
})
const SESSIONSECRET = process.env.SESSIONSECRET || 'this is not really a random session secret'
/**
* passport application of oidc requires session
*/
app.use(session({
secret: SESSIONSECRET,
resave: true,
saveUninitialized: false,
store
}))
/**
* configure Auth
* async function, but can start server without
*/
configureAuth(app)
.then(() => console.log('configure auth properly'))
.catch(e => console.error('configure auth failed', e))
const PUBLIC_PATH = process.env.NODE_ENV === 'production'
? path.join(__dirname, 'public')
: path.join(__dirname, '..', 'dist', 'aot')
app.use(express.static(PUBLIC_PATH))
app.use((req, res, next) => {
res.set('Content-Type', 'application/json')
next()
})
const templateRouter = require('./templates')
const nehubaConfigRouter = require('./nehubaConfig')
const datasetRouter = require('./datasets')
const pluginRouter = require('./plugins')
app.use('/templates', templateRouter)
app.use('/nehubaConfig', nehubaConfigRouter)
app.use('/datasets', datasetRouter)
app.use('/plugins', pluginRouter)
const catchError = require('./catchError')
app.use(catchError)
module.exports = app
\ No newline at end of file
const passport = require('passport')
const { configureAuth } = require('./oidc')
const HOSTNAME = process.env.HOSTNAME || 'http://localhost:3000'
const clientId = process.env.HBP_CLIENTID || 'no hbp id'
const clientSecret = process.env.HBP_CLIENTSECRET || 'no hbp client secret'
const discoveryUrl = 'https://services.humanbrainproject.eu/oidc'
const redirectUri = `${HOSTNAME}/hbp-oidc/cb`
const cb = (tokenset, {sub, given_name, family_name, ...rest}, done) => {
return done(null, {
id: `hbp-oidc:${sub}`,
name: `${given_name} ${family_name}`,
type: `hbp-oidc`,
tokenset,
rest
})
}
module.exports = async (app) => {
const { oidcStrategy } = await configureAuth({
clientId,
clientSecret,
discoveryUrl,
redirectUri,
cb,
scope: 'openid offline_access',
clientConfig: {
redirect_uris: [ redirectUri ],
response_types: [ 'code' ]
}
})
passport.use('hbp-oidc', oidcStrategy)
app.get('/hbp-oidc/auth', passport.authenticate('hbp-oidc'))
app.get('/hbp-oidc/cb', passport.authenticate('hbp-oidc', {
successRedirect: '/',
failureRedirect: '/'
}))
}
const hbpOidc = require('./hbp-oidc')
const passport = require('passport')
const objStoreDb = new Map()
module.exports = async (app) => {
app.use(passport.initialize())
app.use(passport.session())
passport.serializeUser((user, done) => {
const { tokenset, rest } = user
objStoreDb.set(user.id, user)
done(null, user.id)
})
passport.deserializeUser((id, done) => {
const user = objStoreDb.get(id)
if (user)
return done(null, user)
else
return done(null, false)
})
await hbpOidc(app)
app.get('/user', (req, res) => {
if (req.user) {
return res.status(200).send(JSON.stringify(req.user))
} else {
return res.sendStatus(401)
}
})
app.get('/logout', (req, res) => {
if (req.user && req.user.id)
objStoreDb.delete(req.user.id)
req.logout()
res.redirect('/')
})
}
\ No newline at end of file
const { Issuer, Strategy } = require('openid-client')
const defaultCb = (tokenset, {id, ...rest}, done) => {
return done(null, {
id: id || Date.now(),
...rest
})
}
exports.configureAuth = async ({ discoveryUrl, clientId, clientSecret, redirectUri, clientConfig = {}, cb = defaultCb, scope = 'openid' }) => {
if (!discoveryUrl)
throw new Error('discoveryUrl must be defined!')
if (!clientId)
throw new Error('clientId must be defined!')
if (!clientSecret)
throw new Error('clientSecret must be defined!')
if (!redirectUri)
throw new Error('redirectUri must be defined!')
const issuer = await Issuer.discover(discoveryUrl)
const client = new issuer.Client({
client_id: clientId,
client_secret: clientSecret,
...clientConfig
})
const oidcStrategy = new Strategy({
client,
params: {
scope
},
}, cb)
return {
oidcStrategy,
issuer,
client
}
}
\ No newline at end of file
const { configureAuth } = require('./oidc')
const jwtDecode = require('jwt-decode')
const HOSTNAME = process.env.HOSTNAME || 'http://localhost:3000'
const clientId = process.env.HBP_CLIENTID || 'no hbp id'
const clientSecret = process.env.HBP_CLIENTSECRET || 'no hbp client secret'
const discoveryUrl = 'https://services.humanbrainproject.eu/oidc'
const redirectUri = `${HOSTNAME}/hbp-oidc/cb`
let REFRESH_TOKEN = process.env.REFRESH_TOKEN || null
const CLIENT_NOT_INIT = `Client is not initialised.`
const REFRESH_TOKEN_MISSING = `refresh token is missing`
let __client
let __publicAccessToken
const refreshToken = async () => {
if (!__client)
throw new Error(CLIENT_NOT_INIT)
if (!REFRESH_TOKEN)
throw new Error(REFRESH_TOKEN_MISSING)
const tokenset = await __client.refresh(REFRESH_TOKEN)
const {access_token: accessToken, refresh_token: refreshToken, id_token: idToken} = tokenset
if (refreshToken !== REFRESH_TOKEN) {
REFRESH_TOKEN = refreshToken
}
__publicAccessToken = accessToken
return true
}
const getPublicAccessToken = async () => {
if (!__client)
throw new Error(CLIENT_NOT_INIT)
if (!__publicAccessToken) {
await refreshToken()
}
const decoded = jwtDecode(__publicAccessToken)
const { exp } = decoded
if (!exp || isNaN(exp) || (exp * 1000 - Date.now() < 0)) {
await refreshToken()
}
return __publicAccessToken
}
module.exports = async () => {
const { client } = await configureAuth({
clientId,
clientSecret,
discoveryUrl,
redirectUri,
clientConfig: {
redirect_uris: [ redirectUri ],
response_types: [ 'code' ]
}
})
__client = client
return {
getPublicAccessToken: async () => await getPublicAccessToken()
}
}
\ No newline at end of file
const mocha = require('mocha')
const chai = require('chai')
const assert = chai.assert
const chaiAsPromised = require('chai-as-promised')
chai.use(chaiAsPromised)
const expect = chai.expect
const should = chai.should()
describe('mocha.js', () => {
it('mocha works properly', () => {
assert(true)
})
})
describe('chai-as-promised.js', () => {
it('resolving promise is resolved', () => {
return Promise.resolve(2 + 2).should.eventually.equal(4)
})
it('rejecting promise is rejected', () => {
return Promise.reject('no reason').should.be.rejected
})
})
describe('util.js with env', (done) => {
it('when client id and client secret and refresh token is set, util should not throw', async () => {
const util = require('./util')
const { getPublicAccessToken } = await util()
return getPublicAccessToken().should.be.fulfilled
})
})
\ No newline at end of file
const mocha = require('mocha')
const chai = require('chai')
const assert = chai.assert
const chaiAsPromised = require('chai-as-promised')
chai.use(chaiAsPromised)
const expect = chai.expect
const should = chai.should()
describe('mocha.js', () => {
it('mocha works properly', () => {
assert(true)
})
})
describe('chai-as-promised.js', () => {
it('resolving promise is resolved', () => {
return Promise.resolve(2 + 2).should.eventually.equal(4)
})
it('rejecting promise is rejected', () => {
return Promise.reject('no reason').should.be.rejected
})
})
describe('util.js without env', (done) => {
const util = require('./util')
it('even when no env is set, it should fulfill', () => {
const getUtil = util()
return getUtil.should.be.fulfilled
})
it('if env is not set, getPublicToken method should fail', async () => {
const utilObj = await util()
const { getPublicAccessToken } = utilObj
return getPublicAccessToken().should.be.rejected
})
})
\ No newline at end of file
module.exports = ({code = 500, error = 'an error had occured', trace = 'undefined trace'}, req, res, next) => {
/**
* probably use more elaborate logging?
*/
console.log('Catching error', {
code,
error,
trace
})
res.status(code).send()
}
\ No newline at end of file
const express = require('express')
const path = require('path')
const fs = require('fs')
const datasetsRouter = express.Router()
const { init, getDatasets, getPreview } = require('./query')
init().catch(e => {
console.warn(`dataset init failed`, e)
})
datasetsRouter.use((req, res, next) => {
res.setHeader('Cache-Control', 'no-cache')
next()
})
datasetsRouter.use('/spatialSearch', require('./spatialRouter'))
datasetsRouter.get('/templateName/:templateName', (req, res, next) => {
const { templateName } = req.params
const { user } = req
getDatasets({ templateName, user })
.then(ds => {
res.status(200).json(ds)
})
.catch(error => {
next({
code: 500,
error,
trace: 'templateName'
})
})
})
datasetsRouter.get('/parcellationName/:parcellationName', (req, res, next) => {
const { parcellationName } = req.params
const { user } = req
getDatasets({ parcellationName, user })
.then(ds => {
res.status(200).json(ds)
})
.catch(error => {
next({
code: 500,
error,
trace: 'parcellationName'
})
})
})
datasetsRouter.get('/preview/:datasetName', (req, res, next) => {
const { datasetName } = req.params
getPreview({ datasetName })
.then(preview => {
if (preview) {
res.status(200).json(preview)
} else {
next({
code: 404,
trace: 'preview'
})
}
})
.catch(error => {
next({
code: 500,
error,
trace: 'preview'
})
})
})
const previewFileMap = new Map()
const PUBLIC_PATH = process.env.NODE_ENV === 'production'
? path.join(__dirname, '..', 'public')
: path.join(__dirname, '..', '..', 'dist', 'aot')
const RECEPTOR_PATH = path.join(PUBLIC_PATH, 'res', 'image')
fs.readdir(RECEPTOR_PATH, (err, files) => {
if (err) {
console.log('reading receptor error', err)
return
}
files.forEach(file => previewFileMap.set(`res/image/receptor/${file}`, path.join(RECEPTOR_PATH, file)))
})
datasetsRouter.get('/previewFile', (req, res) => {
const { file } = req.query
const filePath = previewFileMap.get(file)
if (filePath) {
fs.createReadStream(filePath).pipe(res)
} else {
res.status(404).send()
}
})
module.exports = datasetsRouter
\ No newline at end of file
const fs = require('fs')
const request = require('request')
const path = require('path')
const { commonSenseDsFilter } = require('./supplements/commonSense')
const { getPreviewFile, hasPreview } = require('./supplements/previewFile')
const { manualFilter: manualFilterDWM, manualMap: manualMapDWM } = require('./supplements/util/mapDwm')
const kgQueryUtil = require('./../auth/util')
let cachedData = null
let otherQueryResult = null
const queryUrl = process.env.KG_DATASET_QUERY_URL || `https://kg.humanbrainproject.org/query/minds/core/dataset/v1.0.0/interactiveViewerKgQuery/instances?size=450&vocab=https%3A%2F%2Fschema.hbp.eu%2FmyQuery%2F`
const timeout = process.env.TIMEOUT || 5000
const STORAGE_PATH = process.env.STORAGE_PATH || path.join(__dirname, 'data')
let getPublicAccessToken
const fetchDatasetFromKg = async (arg) => {
const accessToken = arg && arg.user && arg.user.tokenset && arg.user.tokenset.access_token
const releasedOnly = !accessToken
let publicAccessToken
if (!accessToken && getPublicAccessToken) {
publicAccessToken = await getPublicAccessToken()
}
const option = accessToken || publicAccessToken || process.env.ACCESS_TOKEN
? {
auth: {
'bearer': accessToken || publicAccessToken || process.env.ACCESS_TOKEN
}
}
: {}
return await new Promise((resolve, reject) => {
request(`${queryUrl}${releasedOnly ? '&databaseScope=RELEASED' : ''}`, option, (err, resp, body) => {
if (err)
return reject(err)
if (resp.statusCode >= 400)
return reject(resp.statusCode)
const json = JSON.parse(body)
return resolve(json)
})
})
}
const cacheData = ({results, ...rest}) => {
cachedData = results
otherQueryResult = rest
return cachedData
}
const getPublicDs = () => Promise.race([
new Promise((rs, rj) => {
setTimeout(() => {
if (cachedData) {
rs(cachedData)
} else {
/**
* cached data not available, have to wait
*/
}
}, timeout)
}),
fetchDatasetFromKg().then(cacheData)
])
const getDs = ({ user }) => user
? fetchDatasetFromKg({ user }).then(({results}) => results)
: getPublicDs()
/**
* Needed by filter by parcellation
*/
const flattenArray = (array) => {
return array.concat(
...array.map(item => item.children && item.children instanceof Array
? flattenArray(item.children)
: [])
)
}
const readConfigFile = (filename) => new Promise((resolve, reject) => {
let filepath
if (process.env.NODE_ENV === 'production') {
filepath = path.join(__dirname, '..', 'res', filename)
} else {
filepath = path.join(__dirname, '..', '..', 'src', 'res', 'ext', filename)
}
fs.readFile(filepath, 'utf-8', (err, data) => {
if(err) reject(err)
resolve(data)
})
})
let juBrain = null
let shortBundle = null
let longBundle = null
let waxholm = null
let allen = null
readConfigFile('colin.json')
.then(data => JSON.parse(data))
.then(json => {
juBrain = flattenArray(json.parcellations[0].regions)
})
.catch(console.error)
readConfigFile('MNI152.json')
.then(data => JSON.parse(data))
.then(json => {
longBundle = flattenArray(json.parcellations[0].regions)
shortBundle = flattenArray(json.parcellations[1].regions)
})
.catch(console.error)
readConfigFile('waxholmRatV2_0.json')
.then(data => JSON.parse(data))
.then(json => {
waxholm = flattenArray(json.parcellations[0].regions)
})
.catch(console.error)
/**
* deprecated
*/
const filterByPRs = (prs, atlasPr) => atlasPr
? prs.some(pr => {
const regex = new RegExp(pr.name.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'), 'i')
return atlasPr.some(aPr => regex.test(aPr.name))
})
: false
const manualFilter = require('./supplements/parcellation')
const filter = (datasets = [], {templateName, parcellationName}) => datasets
.filter(ds => commonSenseDsFilter({ds, templateName, parcellationName }))
.filter(ds => {
if (/infant/.test(ds.name))
return false
if (templateName) {
return ds.referenceSpaces.some(rs => rs.name === templateName)
}
if (parcellationName) {
if (parcellationName === 'Fibre Bundle Atlas - Long Bundle'){
return manualFilterDWM(ds)
}
return ds.parcellationRegion.length > 0
? filterByPRs(
ds.parcellationRegion,
parcellationName === 'JuBrain Cytoarchitectonic Atlas' && juBrain
? juBrain
: parcellationName === 'Fibre Bundle Atlas - Short Bundle' && shortBundle
? shortBundle
: parcellationName === 'Waxholm Space rat brain atlas v.2.0'
? waxholm
: null
)
: false
}
return false
})
.map(ds => {
if (parcellationName && parcellationName === 'Fibre Bundle Atlas - Long Bundle') {
return manualMapDWM(ds)
}
return {
...ds,
...parcellationName && ds.parcellationRegion.length === 0
? { parcellationRegion: [{ name: manualFilter({ parcellationName, dataset: ds }) }] }
: {},
preview: hasPreview({ datasetName: ds.name })
}
})
/**
* on init, populate the cached data
*/
exports.init = async () => {
const { getPublicAccessToken: getPublic } = await kgQueryUtil()
getPublicAccessToken = getPublic
const {results = []} = await fetchDatasetFromKg()
cachedData = results
}
exports.getDatasets = ({ templateName, parcellationName, user }) => getDs({ user })
.then(json => filter(json, {templateName, parcellationName}))
exports.getPreview = ({ datasetName }) => getPreviewFile({ datasetName })
/**
* TODO
* change to real spatial query
*/
const cachedMap = new Map()
const fetchSpatialDataFromKg = async ({ templateName }) => {
const cachedResult = cachedMap.get(templateName)
if (cachedResult)
return cachedResult
try {
const filename = path.join(STORAGE_PATH, templateName + '.json')
const exists = fs.existsSync(filename)
if (!exists)
return []
const data = fs.readFileSync(filename, 'utf-8')
const json = JSON.parse(data)
cachedMap.set(templateName, json)
return json
} catch (e) {
console.log('datasets#query.js#fetchSpatialDataFromKg', 'read file and parse json failed', e)
return []
}
}
exports.getSpatialDatasets = async ({ templateName, queryGeometry, queryArg }) => {
return await fetchSpatialDataFromKg({ templateName })
}
\ No newline at end of file
const router = require('express').Router()
const { getSpatialDatasets } = require('./query')
const badRequestString = `spatialSearch endpoint uses param as follows:
GET /templateName/<templateName>/<queryGeometry>/<queryArg>
for example;
GET /templateName/Colin/bbox/0_0_0__1_1_1`
router.get('/templateName/:templateName/:queryGeometry/:queryArg', (req, res, next) => {
const { templateName, queryGeometry, queryArg } = req.params
let errorString = ``
if (!templateName)
errorString += `templateName is required\n`
if (!queryGeometry)
errorString += `queryGeometry is required\n`
if (!queryArg)
errorString += `queryArg is required\n`
if (errorString !== ``)
return next({
code: 400,
error: errorString,
trace: 'dataset#spatialRouter'
})
getSpatialDatasets({ templateName, queryGeometry, queryArg })
.then(arr => res.status(200).json(arr))
.catch(error => {
next({
code: 500,
error,
trace: 'dataset#spatialRouter#getSpatialDatasets'
})
})
})
router.use((req, res, next) => {
next({
code: 400,
error: badRequestString,
trace: 'dataset#spatialRouter#notFound'
})
})
module.exports = router
\ No newline at end of file
raw
const humanTemplateSet = new Set([
'Big Brain (Histology)',
'MNI Colin 27',
'MNI 152 ICBM 2009c Nonlinear Asymmetric'
])
const humanParcellationSet = new Set([
'Grey/White matter',
'Area V1',
'Area V2',
'JuBrain Cytoarchitectonic Atlas',
'Fibre Bundle Atlas - Short Bundle',
'Fibre Bundle Atlas - Long Bundle'
])
const ratTemplateSet = new Set([
'Waxholm Space rat brain atlas v.2.0'
])
const ratParcellationSet = new Set([
'Waxholm Space rat brain atlas v.2.0'
])
const mouseTemplateSet = new Set([
'Allen adult mouse brain reference atlas V3'
])
const mouseParcellationSet = new Set([
'Allen adult mouse brain reference atlas V3 Brain Atlas'
])
const dsIsHuman = ({ds}) => ds && ds.species.some(species => /homo\ sapiens/i.test(species))
const dsIsRat = ({ ds }) => ds && ds.species.some(species => /rattus\ norvegicus/i.test(species))
const dsIsMouse = ({ ds }) => ds && ds.species.some(species => /mus\ musculus/i.test(species))
const queryIsMouse = ({ templateName, parcellationName }) =>
(templateName && mouseTemplateSet.has(templateName))
|| (parcellationName && mouseParcellationSet.has(parcellationName))
const queryIsRat = ({ templateName, parcellationName }) =>
(templateName && ratTemplateSet.has(templateName))
|| (parcellationName && ratParcellationSet.has(parcellationName))
const queryIsHuman = ({ templateName, parcellationName }) =>
(templateName && humanTemplateSet.has(templateName))
|| (parcellationName && humanParcellationSet.has(parcellationName))
exports.commonSenseDsFilter = ({ ds , templateName, parcellationName}) =>
(queryIsHuman({ templateName, parcellationName }) && !dsIsRat({ ds }) && !dsIsMouse({ ds }))
|| (queryIsMouse({ templateName, parcellationName }) && !dsIsRat({ ds }) && !dsIsHuman({ ds }))
|| (queryIsRat({ templateName, parcellationName }) && !dsIsMouse({ ds }) && !dsIsHuman({ ds }))
This diff is collapsed.
This diff is collapsed.
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment