diff --git a/src/atlasViewer/atlasViewer.workerService.service.ts b/src/atlasViewer/atlasViewer.workerService.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..7e66dc31b5e5ab6b6935f3fcd7e9582bc906a4c8 --- /dev/null +++ b/src/atlasViewer/atlasViewer.workerService.service.ts @@ -0,0 +1,10 @@ +import { Injectable } from "@angular/core"; + +@Injectable({ + providedIn:'root' +}) + +export class AtlasWorkerService{ + public worker = new Worker('worker.js') + public safeMeshSet : Map<string, Set<number>> = new Map() +} \ No newline at end of file diff --git a/src/main.module.ts b/src/main.module.ts index 8b88b968c1355e3423d6058d725c5c045bdef609..8887dbc3e6f1257c08073a14605d639faa2c210a 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -25,6 +25,7 @@ import { AtlasViewerAPIServices } from "./atlasViewer/atlasViewer.apiService.ser import { PluginUnit } from "./atlasViewer/pluginUnit/pluginUnit.component"; import { NewViewerDisctinctViewToLayer } from "./util/pipes/newViewerDistinctViewToLayer.pipe"; import { ToastService } from "./services/toastService.service"; +import { AtlasWorkerService } from "./atlasViewer/atlasViewer.workerService.service"; @NgModule({ imports : [ @@ -80,6 +81,7 @@ import { ToastService } from "./services/toastService.service"; AtlasViewerURLService, AtlasViewerAPIServices, ToastService, + AtlasWorkerService, ], bootstrap : [ AtlasViewer diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index 71b944c9fba29764ab9e7a1b77eb8886a97cec11..50920f80cdf6d967cd5e8d851a3ca087bc58d014 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -239,7 +239,7 @@ export class NehubaContainer implements OnInit, OnDestroy{ const e = (event as any) const lastLoadedIdString = e.detail.lastLoadedMeshId.split(',')[0] const lastLoadedIdNum = Number(lastLoadedIdString) - return e.detail.meshesLoaded >= this.regionsLabelIndexMap.size + return e.detail.meshesLoaded >= this.nehubaViewer.numMeshesToBeLoaded ? null : isNaN(lastLoadedIdNum) ? 'Loading unknown chunk' diff --git a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts index fdc89f2940b3b53d5ae5245863568c3cf6a9ffdf..0876ffee9d8e72f1dd23c6076bf717b380a74afa 100644 --- a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts +++ b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts @@ -1,8 +1,10 @@ -import { Component, AfterViewInit, OnDestroy, Output, EventEmitter, ElementRef } from "@angular/core"; +import { Component, AfterViewInit, OnDestroy, Output, EventEmitter, ElementRef, NgZone } from "@angular/core"; import * as export_nehuba from 'third_party/export_nehuba/main.bundle.js' import 'third_party/export_nehuba/chunk_worker.bundle.js' -import { getActiveColorMapFragmentMain } from "../nehubaContainer.component"; +import { fromEvent, interval } from 'rxjs' +import { AtlasWorkerService } from "../../../atlasViewer/atlasViewer.workerService.service"; +import { buffer, map, filter } from "rxjs/operators"; @Component({ templateUrl : './nehubaViewer.template.html', @@ -22,9 +24,6 @@ export class NehubaViewerUnit implements AfterViewInit,OnDestroy{ initRegions : any[] initNiftiLayers : any[] = [] - /* deprecated */ - initDedicatedView : string[] - config : any nehubaViewer : any @@ -49,16 +48,87 @@ export class NehubaViewerUnit implements AfterViewInit,OnDestroy{ this._s8$ ] - constructor(public elementRef:ElementRef){ + ondestroySubscriptions: any[] = [] + + constructor( + public elementRef:ElementRef, + private workerService : AtlasWorkerService, + private zone : NgZone + ){ this.patchNG() + + this.ondestroySubscriptions.push( + + fromEvent(this.workerService.worker,'message').pipe( + filter((message:any) => { + + if(!message){ + // console.error('worker response message is undefined', message) + return false + } + if(!message.data){ + // console.error('worker response message.data is undefined', message.data) + return false + } + if(message.data.type !== 'CHECKED_MESH'){ + /* worker responded with not checked mesh, no need to act */ + return false + } + return true + }), + map(e => e.data), + buffer(interval(1000)), + map(arr => arr.reduce((acc:Map<string,Set<number>>,curr)=> { + + const newMap = new Map(acc) + const set = newMap.get(curr.baseUrl) + if(set){ + set.add(curr.checkedIndex) + }else{ + newMap.set(curr.baseUrl,new Set([curr.checkedIndex])) + } + return newMap + }, new Map())) + ).subscribe(map => { + + Array.from(map).forEach(item => { + const baseUrl : string = item[0] + const set : Set<number> = item[1] + + /* validation passed, add to safeMeshSet */ + const oldset = this.workerService.safeMeshSet.get(baseUrl) + if(oldset){ + this.workerService.safeMeshSet.set(baseUrl, new Set([...oldset, ...set])) + }else{ + this.workerService.safeMeshSet.set(baseUrl, new Set([...set])) + } + + /* if the active parcellation is the current parcellation, load the said mesh */ + if(baseUrl === this._baseUrl){ + this.nehubaViewer.setMeshesToLoad([...this.workerService.safeMeshSet.get(this._baseUrl)], { + name : this.parcellationId + }) + } + }) + }) + ) } - private _parcellationId : string + private _baseUrl : string + get numMeshesToBeLoaded():number{ + if(!this._baseUrl) + return 0 + const set = this.workerService.safeMeshSet.get(this._baseUrl) + return set + ? set.size + : 0 + } + + private _parcellationId : string get parcellationId(){ return this._parcellationId } - set parcellationId(id:string){ if(this._parcellationId && this.nehubaViewer){ @@ -89,7 +159,6 @@ export class NehubaViewerUnit implements AfterViewInit,OnDestroy{ ngAfterViewInit(){ this.nehubaViewer = export_nehuba.createNehubaViewer(this.config, (err)=>{ /* print in debug mode */ - console.log('xgui3783', err) }); if(this.regionsLabelIndexMap){ @@ -107,6 +176,7 @@ export class NehubaViewerUnit implements AfterViewInit,OnDestroy{ this._s$.forEach(_s$=>{ if(_s$) _s$.unsubscribe() }) + this.ondestroySubscriptions.forEach(s => s.unsubscribe()) this.nehubaViewer.dispose() } @@ -313,7 +383,8 @@ export class NehubaViewerUnit implements AfterViewInit,OnDestroy{ private newViewerInit(){ - /* isn't this segment specific? */ + /* isn't this layer specific? */ + /* TODO this is layer specific. need a way to distinguish between different segmentation layers */ this._s2$ = this.nehubaViewer.mouseOver.segment .subscribe(obj=>this.mouseOverSegment = obj.segment) @@ -331,22 +402,6 @@ export class NehubaViewerUnit implements AfterViewInit,OnDestroy{ this.hideAllSeg() } - /* dedicated view is deprecated */ - if(this.initDedicatedView){ - this.hideAllSeg() - const _ = {} - - this.initDedicatedView.forEach((layer,index) => { - _[`dedicatedview-${index}`] = { - type : 'image', - source : layer, - shader : getActiveColorMapFragmentMain() - } - }) - - this.loadLayer(_) - } - this._s8$ = this.nehubaViewer.mouseOver.segment.subscribe(({segment})=>{ if( segment && segment < 65500 ) { const region = this.regionsLabelIndexMap.get(segment) @@ -399,16 +454,42 @@ export class NehubaViewerUnit implements AfterViewInit,OnDestroy{ if(newlayer)newlayer.setVisible(true) else console.warn('could not find new layer',this.parcellationId) + const regex = /^(\S.*?)\:\/\/(.*?)$/.exec(newlayer.sourceUrl) + + if(!regex || !regex[2]){ + console.error('could not parse baseUrl') + return + } + if(regex[1] !== 'precomputed'){ + console.error('sourceUrl is not precomputed') + return + } + + const baseUrl = regex[2] + this._baseUrl = baseUrl + /* load meshes */ - this.nehubaViewer.setMeshesToLoad( - [ - ...Array.from(this.regionsLabelIndexMap.keys()), - ...getAuxilliaryLabelIndices() - ], - { + /* TODO could be a bug where user loads new parcellation, but before the worker could completely populate the list */ + const set = this.workerService.safeMeshSet.get(baseUrl) + if(set){ + this.nehubaViewer.setMeshesToLoad([...set],{ name : this.parcellationId + }) + }else{ + if(newlayer){ + this.zone.runOutsideAngular(() => + this.workerService.worker.postMessage({ + type : 'CHECK_MESHES', + parcellationId : this.parcellationId, + baseUrl, + indices : [ + ...Array.from(this.regionsLabelIndexMap.keys()), + ...getAuxilliaryLabelIndices() + ] + }) + ) } - ) + } this.defaultColormap = new Map( Array.from( diff --git a/src/util/worker.ts b/src/util/worker.ts new file mode 100644 index 0000000000000000000000000000000000000000..f5d34bdfa740da166c07f4199967e58fd522daca --- /dev/null +++ b/src/util/worker.ts @@ -0,0 +1,43 @@ +const validTypes = ['CHECK_MESHES'] +const validOutType = ['CHECKED_MESH'] + +const checkMeshes = (action) => { + + /* filtering now done on the angular level */ + const baseUrl = action.baseUrl + fetch(`${baseUrl}/info`) + .then(res => res.json()) + .then(json => json.mesh + ? json.mesh + : new Error(`mesh does not exist on fetched info file: ${JSON.stringify(json)}`)) + .then(meshPath => action.indices.forEach(index => { + fetch(`${baseUrl}/${meshPath}/${index}:0`) + .then(() => { + postMessage({ + type: 'CHECKED_MESH', + parcellationId: action.parcellationId, + checkedIndex: index, + baseUrl + }) + }) + .catch(error => { + /* no cors error is also caught here, but also printed to the console */ + }) + })) + .catch(error => { + console.error('parsing info json error', error) + }) +} + +onmessage = (message) => { + + if(validTypes.findIndex(type => type === message.data.type)>=0){ + switch(message.data.type){ + case 'CHECK_MESHES': + checkMeshes(message.data) + return; + default: + console.warn('unhandled worker action') + } + } +} diff --git a/tsconfig.json b/tsconfig.json index 1e1c481748c91961ea899dbb2d1bfb1261c8cf64..13633294135515b7caea1c6550552b07cea72232 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "moduleResolution": "node", - "skipLibCheck": true, + "lib":["webworker","es2015", "dom"], "target": "es2015", "sourceMap": true, "baseUrl": ".", diff --git a/webpack.aot.js b/webpack.aot.js index 45c6c9278d350a6b429c5ebfa62ca8fddddd4151..aaeea3e40899e9c0c146363e89eb591b4af3470f 100644 --- a/webpack.aot.js +++ b/webpack.aot.js @@ -6,12 +6,16 @@ const AngularCompilerPlugin = ngtools.AngularCompilerPlugin const ClosureCompilerPlugin = require('webpack-closure-compiler') const merge = require('webpack-merge') const staticAssets = require('./webpack.staticassets') +const worker = require('./webpack.worker') -module.exports = merge(staticAssets, { +module.exports = merge(worker, staticAssets, { - entry : './src/main-aot.ts', + entry : { + main : './src/main-aot.ts', + worker : './src/util/worker.ts' + }, output : { - filename : 'main.js', + filename : '[name].js', path : path.resolve(__dirname,'dist/aot') }, module: { @@ -27,9 +31,8 @@ module.exports = merge(staticAssets, { }, { test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/, - // test : /\.ts$/, loader: '@ngtools/webpack', - exclude : /export_nehuba|plugin_example/ + exclude : /worker|export_nehuba|plugin_example/ }, { test : /\.(html|css)$/, diff --git a/webpack.common.js b/webpack.common.js index 64d93df42a3ecf189938a3c3022a3e5c009ed2cc..f84ff7357eef32339f70f2d1221e3dfac9935852 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -7,7 +7,7 @@ module.exports = { { test : /\.ts$/, loaders : ['ts-loader','angular2-template-loader?keepUrl=true'], - exclude : /node_modules|[Ss]pec\.ts$/ + exclude : /worker|node_modules|[Ss]pec\.ts$/ }, { test : /.*?worker.*?\.js$/, diff --git a/webpack.dev.js b/webpack.dev.js index 5e812129eee3095b6ec70738a27c24f8fabae71a..05babdb5cee93ccc91c2e54d859bc44813b831f4 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -4,13 +4,16 @@ const path = require('path') const ngAssets = require('./webpack.ngassets') const staticAssets = require('./webpack.staticassets') const HtmlWebpackPlugin = require('html-webpack-plugin') +const worker = require('./webpack.worker') - -module.exports = merge(common,ngAssets,staticAssets,{ - entry : './src/main.ts', +module.exports = merge(common,worker,ngAssets,staticAssets,{ + entry : { + main : './src/main.ts', + worker: './src/util/worker.ts' + }, mode : 'development', output : { - filename : 'main.js', + filename : '[name].js', path : path.resolve(__dirname,'dist/dev') }, devtool:'source-map', diff --git a/webpack.worker.js b/webpack.worker.js new file mode 100644 index 0000000000000000000000000000000000000000..02f442e04c93d2b2b38b6f52af0c1e68307be122 --- /dev/null +++ b/webpack.worker.js @@ -0,0 +1,9 @@ +module.exports = { + module : { + rules : [{ + test : /worker.*?\.ts/, + loaders : ['ts-loader'], + exclude : /node_modules/ + }], + } +} \ No newline at end of file