From e6deccf25892257ce08b3be51f3624af86c348cb Mon Sep 17 00:00:00 2001 From: Xiao Gui <xgui3783@gmail.com> Date: Tue, 30 Oct 2018 11:41:31 +0100 Subject: [PATCH] feat: rendering user landmarks improvement now uses worker to parse, and render on a single layer, should improve performance when rendering lots of landmarks --- .../atlasViewer.constantService.service.ts | 1 + .../nehubaContainer.component.ts | 19 +++++- .../nehubaViewer/nehubaViewer.component.ts | 65 ++++++++++++------- src/util/worker.js | 49 +++++++++++--- 4 files changed, 100 insertions(+), 34 deletions(-) diff --git a/src/atlasViewer/atlasViewer.constantService.service.ts b/src/atlasViewer/atlasViewer.constantService.service.ts index edb6dcfc2..d3e7a63ed 100644 --- a/src/atlasViewer/atlasViewer.constantService.service.ts +++ b/src/atlasViewer/atlasViewer.constantService.service.ts @@ -12,6 +12,7 @@ export class AtlasViewerConstantsServices{ public mobile: boolean public ngLandmarkLayerName = 'spatial landmark layer' + public ngUserLandmarkLayerName = 'user landmark layer' /* TODO to be replaced by @id: Landmark/UNIQUE_ID in KG in the future */ public testLandmarksChanged : (prevLandmarks : any[], newLandmarks : any[]) => boolean = (prevLandmarks:any[], newLandmarks:any[]) => { diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index 133dcf08a..deefad2e2 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -146,7 +146,8 @@ export class NehubaContainer implements OnInit, OnDestroy{ // filter(state => isDefined(state) && isDefined(state.userLandmarks)), map(state => isDefined(state) && isDefined(state.userLandmarks) ? state.userLandmarks - : []) + : []), + distinctUntilChanged(userLmUnchanged) ) const segmentsUnchangedChanged = (s1,s2)=> @@ -327,7 +328,6 @@ export class NehubaContainer implements OnInit, OnDestroy{ ).subscribe(landmarks => { this.userLandmarks = landmarks if(this.nehubaViewer){ - this.nehubaViewer.removeUserLandmarks() this.nehubaViewer.addUserLandmarks(landmarks) } }) @@ -753,6 +753,7 @@ export class NehubaContainer implements OnInit, OnDestroy{ }) }, add3DLandmarks : landmarks => { + // TODO check uniqueness of ID if(!landmarks.every(l => isDefined(l.id))) throw new Error('every landmarks needs to be identified with the id field') if(!landmarks.every(l=> isDefined(l.position))) @@ -1047,4 +1048,16 @@ export const takeOnePipe = [ export const CM_THRESHOLD = `0.05` export const CM_MATLAB_JET = `float r;if( x < 0.7 ){r = 4.0 * x - 1.5;} else {r = -4.0 * x + 4.5;}float g;if (x < 0.5) {g = 4.0 * x - 0.5;} else {g = -4.0 * x + 3.5;}float b;if (x < 0.3) {b = 4.0 * x + 0.5;} else {b = -4.0 * x + 2.5;}float a = 1.0;` -export const getActiveColorMapFragmentMain = ():string=>`void main(){float x = toNormalized(getDataValue());${CM_MATLAB_JET}if(x>${CM_THRESHOLD}){emitRGB(vec3(r,g,b));}else{emitTransparent();}}` \ No newline at end of file +export const getActiveColorMapFragmentMain = ():string=>`void main(){float x = toNormalized(getDataValue());${CM_MATLAB_JET}if(x>${CM_THRESHOLD}){emitRGB(vec3(r,g,b));}else{emitTransparent();}}` + + +export const singleLmUnchanged = (lm:{id:string,position:[number,number,number]}, map: Map<string,[number,number,number]>) => + map.has(lm.id) && map.get(lm.id).every((value,idx) => value === lm.position[idx]) + +export const userLmUnchanged = (oldlms, newlms) => { + const oldmap = new Map(oldlms.map(lm => [lm.id, lm.position])) + const newmap = new Map(newlms.map(lm => [lm.id, lm.position])) + + return oldlms.every(lm => singleLmUnchanged(lm, newmap as Map<string,[number,number,number]>)) + && newlms.every(lm => singleLmUnchanged(lm, oldmap as Map<string, [number,number,number]>)) +} \ No newline at end of file diff --git a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts index 4f2baf2f0..dce915947 100644 --- a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts +++ b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts @@ -100,6 +100,42 @@ export class NehubaViewerUnit implements AfterViewInit,OnDestroy{ }) ) + 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 !== 'ASSEMBLED_USERLANDMARKS_VTK'){ + /* worker responded with not assembled landmark, no need to act */ + return false + } + if(!message.data.url){ + /* file url needs to be defined */ + return false + } + return true + }), + debounceTime(100), + map(e => e.data.url) + ).subscribe(url => { + this.removeSpatialSearch3DLandmarks() + const _ = {} + _[this.constantService.ngUserLandmarkLayerName] = { + type :'mesh', + source : `vtk://${url}`, + shader : FRAGMENT_MAIN_WHITE + } + this.loadLayer(_) + }) + ) + this.ondestroySubscriptions.push( fromEvent(this.workerService.worker,'message').pipe( @@ -390,36 +426,21 @@ export class NehubaViewerUnit implements AfterViewInit,OnDestroy{ public addUserLandmarks(landmarks:any[]){ if(!this.nehubaViewer) return - const _ = {} - landmarks.forEach(lm => { - _[`user-${lm.id}`] = { - type : 'mesh', - source : `vtk://${ICOSAHEDRON_VTK_URL}`, - transform : [ - [2 ,0 ,0 , lm.position[0]*1e6], - [0 ,2 ,0 , lm.position[1]*1e6], - [0 ,0 ,2 , lm.position[2]*1e6], - [0 ,0 ,0 , 1 ], - ], - shader : FRAGMENT_MAIN_WHITE - } + this.workerService.worker.postMessage({ + type : 'GET_USERLANDMARKS_VTK', + landmarks : landmarks.map(lm => lm.position.map(coord => coord * 1e6)) }) - - this.loadLayer(_) } - // TODO single landmark for user landmark - public removeUserLandmarks(){ - if(!this.nehubaViewer) - return + public removeSpatialSearch3DLandmarks(){ this.removeLayer({ - name : /^user\-/ + name : this.constantService.ngLandmarkLayerName }) } - public removeSpatialSearch3DLandmarks(){ + public removeuserLandmarks(){ this.removeLayer({ - name : this.constantService.ngLandmarkLayerName + name : this.constantService.ngUserLandmarkLayerName }) } diff --git a/src/util/worker.js b/src/util/worker.js index b38930c75..8fd3f883e 100644 --- a/src/util/worker.js +++ b/src/util/worker.js @@ -1,5 +1,5 @@ -const validTypes = ['CHECK_MESHES', 'GET_LANDMARKS_VTK'] -const validOutType = ['CHECKED_MESH', 'ASSEMBLED_LANDMARKS_VTK'] +const validTypes = ['CHECK_MESHES', 'GET_LANDMARKS_VTK', 'GET_USERLANDMARKS_VTK'] +const validOutType = ['CHECKED_MESH', 'ASSEMBLED_LANDMARKS_VTK', 'ASSEMBLED_USERLANDMARKS_VTK'] const checkMeshes = (action) => { @@ -105,14 +105,10 @@ const getMeshPoly = (polyIndices, currentIdx) => polyIndices.map(triplet => ).join(' ')) ).join('\n') -let landmarkVtkUrl const encoder = new TextEncoder() -const getLandmarksVtk = (action) => { - // landmarks are array of triples in nm (array of array of numbers) - const landmarks = action.landmarks - const template = action.template +const parseLmToVtk = (landmarks) => { const reduce = landmarks.reduce((acc,curr,idx) => { //curr : null | [number,number,number] | [ [number,number,number], [number,number,number], [number,number,number] ][] @@ -151,9 +147,9 @@ const getLandmarksVtk = (action) => { // if no vertices are been rendered, do not replace old if(reduce.currentVertexIndex === 0) - return + return false - const vtk = vtkHeader + return vtkHeader .concat('\n') .concat(getVertexHeader(reduce.currentVertexIndex)) .concat('\n') @@ -166,7 +162,21 @@ const getLandmarksVtk = (action) => { .concat(getLabelHeader(reduce.currentVertexIndex)) .concat('\n') .concat(reduce.labelString.join('\n')) +} + +let landmarkVtkUrl + +const getLandmarksVtk = (action) => { + + // landmarks are array of triples in nm (array of array of numbers) + const landmarks = action.landmarks + const template = action.template + + const vtk = parseLmToVtk(landmarks) + if(!vtk) + return + // when new set of landmarks are to be displayed, the old landmarks will be discarded if(landmarkVtkUrl) URL.revokeObjectURL(landmarkVtkUrl) @@ -179,6 +189,24 @@ const getLandmarksVtk = (action) => { }) } +let userLandmarkVtkUrl + +const getuserLandmarksVtk = (action) => { + const landmarks = action.landmarks + const vtk = parseLmToVtk(landmarks) + if(!vtk) + return + + if(userLandmarkVtkUrl) + URL.revokeObjectURL(userLandmarkVtkUrl) + + userLandmarkVtkUrl = URL.createObjectURL(new Blob( [encoder.encode(vtk)], {type : 'application/octet-stream'} )) + postMessage({ + type : 'ASSEMBLED_USERLANDMARKS_VTK', + url : userLandmarkVtkUrl + }) +} + onmessage = (message) => { if(validTypes.findIndex(type => type === message.data.type)>=0){ @@ -189,6 +217,9 @@ onmessage = (message) => { case 'GET_LANDMARKS_VTK': getLandmarksVtk(message.data) return + case 'GET_USERLANDMARKS_VTK': + getuserLandmarksVtk(message.data) + return default: console.warn('unhandled worker action', message) } -- GitLab