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