diff --git a/src/atlasViewer/atlasViewer.apiService.service.ts b/src/atlasViewer/atlasViewer.apiService.service.ts index d4872bb8221e35bd018b7d6e906c54979f788a25..ccc20bb5b87a6de7ab3c3f7cea068fe9489632a6 100644 --- a/src/atlasViewer/atlasViewer.apiService.service.ts +++ b/src/atlasViewer/atlasViewer.apiService.service.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import {Injectable, NgZone, Optional, Inject, OnDestroy, InjectionToken} from "@angular/core"; +import { MatSnackBar } from "@angular/material/snack-bar"; import { select, Store } from "@ngrx/store"; import { Observable, Subject, Subscription, from, race, of, } from "rxjs"; import { distinctUntilChanged, map, filter, startWith, switchMap, catchError, mapTo, take } from "rxjs/operators"; @@ -35,12 +36,21 @@ interface IGetUserSelectRegionPr{ export const CANCELLABLE_DIALOG = 'CANCELLABLE_DIALOG' export const GET_TOAST_HANDLER_TOKEN = 'GET_TOAST_HANDLER_TOKEN' +export interface ILoadMesh { + type: 'VTK', + id: string, + url: string +} +export const LOAD_MESH_TOKEN = new InjectionToken<(loadMeshParam:ILoadMesh)=>void>('LOAD_MESH_TOKEN') + @Injectable({ - providedIn : 'root', + providedIn : 'root' }) export class AtlasViewerAPIServices implements OnDestroy{ + public loadMesh$ = new Subject<ILoadMesh>() + private onDestoryCb: Function[] = [] private loadedTemplates$: Observable<any> private selectParcellation$: Observable<any> @@ -146,6 +156,7 @@ export class AtlasViewerAPIServices implements OnDestroy{ constructor( private store: Store<IavRootStoreInterface>, private dialogService: DialogService, + private snackbar: MatSnackBar, private zone: NgZone, private pluginService: PluginServices, @Optional() @Inject(CANCELLABLE_DIALOG) openCancellableDialog: (message: string, options: any) => () => void, @@ -367,6 +378,20 @@ export class AtlasViewerAPIServices implements OnDestroy{ this.interactiveViewer.metadata.regionsLabelIndexMap = getLabelIndexMap(parcellation.regions) this.interactiveViewer.metadata.layersRegionLabelIndexMap = getMultiNgIdsRegionsLabelIndexMap(parcellation) }) + + this.s.push( + this.loadMesh$.subscribe(({ url, id, type }) => { + if (!this.interactiveViewer.viewerHandle) { + this.snackbar.open('No atlas loaded! Loading mesh failed!', 'Dismiss') + } + this.interactiveViewer.viewerHandle?.loadLayer({ + [id]: { + type: 'mesh', + source: `vtk://${url}` + } + }) + }) + ) } ngOnDestroy(){ diff --git a/src/atlasViewer/atlasViewer.workerService.service.ts b/src/atlasViewer/atlasViewer.workerService.service.ts index c5e222480facb343ec0d02b1ad8bca75ce486abb..17204cb05224dc78887f74cdae74ab9699ba6ec8 100644 --- a/src/atlasViewer/atlasViewer.workerService.service.ts +++ b/src/atlasViewer/atlasViewer.workerService.service.ts @@ -1,4 +1,6 @@ import { Injectable } from "@angular/core"; +import { fromEvent } from "rxjs"; +import { filter, take } from "rxjs/operators"; /* telling webpack to pack the worker file */ import '../util/worker.js' @@ -8,10 +10,32 @@ import '../util/worker.js' */ export const worker = new Worker('worker.js') +interface IWorkerMessage { + method: string + param: any +} + @Injectable({ providedIn: 'root', }) export class AtlasWorkerService { public worker = worker + + async sendMessage(data: IWorkerMessage){ + + const newUuid = crypto.getRandomValues(new Uint32Array(1))[0].toString(16) + this.worker.postMessage({ + id: newUuid, + ...data + }) + const message = await fromEvent(this.worker, 'message').pipe( + filter((message: MessageEvent) => message.data.id && message.data.id === newUuid), + take(1) + ).toPromise() + + const { data: returnData } = message as MessageEvent + const { id, ...rest } = returnData + return rest + } } diff --git a/src/atlasViewer/pluginUnit/atlasViewer.pluginService.service.ts b/src/atlasViewer/pluginUnit/atlasViewer.pluginService.service.ts index ff1264528a849463487777f6445d63b90335e132..dff1231817ccc94cd2e8f59b0f5ed5fcd1765179 100644 --- a/src/atlasViewer/pluginUnit/atlasViewer.pluginService.service.ts +++ b/src/atlasViewer/pluginUnit/atlasViewer.pluginService.service.ts @@ -1,10 +1,10 @@ import { HttpClient } from '@angular/common/http' -import { ComponentFactory, ComponentFactoryResolver, Injectable, ViewContainerRef, Inject } from "@angular/core"; +import { ComponentFactory, ComponentFactoryResolver, Injectable, ViewContainerRef, Inject, InjectionToken } from "@angular/core"; import { PLUGINSTORE_ACTION_TYPES } from "src/services/state/pluginState.store"; import { IavRootStoreInterface, isDefined } from 'src/services/stateStore.service' import { PluginUnit } from "./pluginUnit.component"; import { select, Store } from "@ngrx/store"; -import { BehaviorSubject, merge, Observable, of, zip } from "rxjs"; +import { BehaviorSubject, merge, Observable, of, Subject, zip } from "rxjs"; import { filter, map, shareReplay, switchMap, catchError } from "rxjs/operators"; import { LoggingService } from 'src/logging'; import { PluginHandler } from 'src/util/pluginHandler'; diff --git a/src/main.module.ts b/src/main.module.ts index 3cc29399b3f96f5f9a7784197c66c8f62e436886..a87d7e6d8fa662263b04876717cbcd98a0c480b0 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -1,6 +1,6 @@ import { DragDropModule } from '@angular/cdk/drag-drop' import { CommonModule } from "@angular/common"; -import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from "@angular/core"; +import { CUSTOM_ELEMENTS_SCHEMA, InjectionToken, NgModule } from "@angular/core"; import { FormsModule } from "@angular/forms"; import { StoreModule, ActionReducer } from "@ngrx/store"; import { AngularMaterialModule } from 'src/ui/sharedModules/angularMaterial.module' @@ -14,7 +14,7 @@ import { GetNamesPipe } from "./util/pipes/getNames.pipe"; import { HttpClientModule } from "@angular/common/http"; import { EffectsModule } from "@ngrx/effects"; -import { AtlasViewerAPIServices, CANCELLABLE_DIALOG, GET_TOAST_HANDLER_TOKEN, API_SERVICE_SET_VIEWER_HANDLE_TOKEN, setViewerHandleFactory } from "./atlasViewer/atlasViewer.apiService.service"; +import { AtlasViewerAPIServices, CANCELLABLE_DIALOG, GET_TOAST_HANDLER_TOKEN, API_SERVICE_SET_VIEWER_HANDLE_TOKEN, setViewerHandleFactory, LOAD_MESH_TOKEN, ILoadMesh } from "./atlasViewer/atlasViewer.apiService.service"; import { AtlasWorkerService } from "./atlasViewer/atlasViewer.workerService.service"; import { ModalUnit } from "./atlasViewer/modalUnit/modalUnit.component"; import { TransformOnhoverSegmentPipe } from "./atlasViewer/onhoverSegment.pipe"; @@ -242,6 +242,15 @@ export function debug(reducer: ActionReducer<any>): ActionReducer<any> { deps: [ ClickInterceptorService ] + }, + { + provide: LOAD_MESH_TOKEN, + useFactory: (apiService: AtlasViewerAPIServices) => { + return (loadMeshParam: ILoadMesh) => apiService.loadMesh$.next(loadMeshParam) + }, + deps: [ + AtlasViewerAPIServices + ] } ], bootstrap : [ diff --git a/src/messaging/module.ts b/src/messaging/module.ts index ac8e266a422deb02734662b51bcfb402e99c1971..9e5152f3d6c62c9aa8442aab8362ab1d964f4bb6 100644 --- a/src/messaging/module.ts +++ b/src/messaging/module.ts @@ -1,6 +1,9 @@ -import { NgModule, Optional } from "@angular/core"; +import { Inject, NgModule, Optional } from "@angular/core"; import { MatDialog } from "@angular/material/dialog"; +import { MatSnackBar } from "@angular/material/snack-bar"; import { AtlasViewerAPIServices } from "src/atlasViewer/atlasViewer.apiService.service"; +import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service"; +import { LOAD_MESH_TOKEN, ILoadMesh } from "src/atlasViewer/atlasViewer.apiService.service"; import { ComponentsModule } from "src/components"; import { ConfirmDialogComponent } from "src/components/confirmDialog/confirmDialog.component"; import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module"; @@ -21,7 +24,10 @@ export class MesssagingModule{ constructor( private dialog: MatDialog, - @Optional() private apiService: AtlasViewerAPIServices + private snackbar: MatSnackBar, + private worker: AtlasWorkerService, + @Optional() private apiService: AtlasViewerAPIServices, + @Optional() @Inject(LOAD_MESH_TOKEN) private loadMesh: (loadMeshParam: ILoadMesh) => void ){ window.addEventListener('message', async ({ data, origin, source }) => { @@ -83,7 +89,6 @@ export class MesssagingModule{ } async processMessage({ method, param }){ - console.log({ method, param }) if (method === 'dummyMethod') { return 'OK' @@ -99,6 +104,27 @@ export class MesssagingModule{ return 'OK' } + if (method === '_tmp:plotly') { + const isLoadingSnack = this.snackbar.open(`Loading plotly mesh ...`) + const resp = await this.worker.sendMessage({ + method: `PROCESS_PLOTLY`, + param + }) + isLoadingSnack?.dismiss() + const meshId = 'bobby' + if (this.loadMesh) { + const { objectUrl } = resp.result || {} + this.loadMesh({ + type: 'VTK', + id: meshId, + url: objectUrl + }) + } else { + this.snackbar.open(`Error: loadMesh method not injected.`) + } + return 'OK' + } + throw ({ code: 404, message: 'Method not found' }) } diff --git a/src/util/worker.js b/src/util/worker.js index 2153104bd9142fe1d7a24523a9e2e70ef11999a5..c0b128e52efa6894b45c1b674f7489f1ddca0617 100644 --- a/src/util/worker.js +++ b/src/util/worker.js @@ -5,6 +5,14 @@ const validTypes = [ 'PROPAGATE_PARC_REGION_ATTR' ] +const VALID_METHOD = { + PROCESS_PLOTLY: `PROCESS_PLOTLY` +} + +const VALID_METHODS = [ + VALID_METHOD.PROCESS_PLOTLY +] + const validOutType = [ 'ASSEMBLED_LANDMARKS_VTK', 'ASSEMBLED_USERLANDMARKS_VTK', @@ -284,7 +292,55 @@ const processParcRegionAttr = (payload) => { }) } +let plotyVtkUrl + onmessage = (message) => { + if (message.data.method && VALID_METHODS.indexOf(message.data.method) >= 0) { + const { id } = message.data + if (message.data.method === VALID_METHOD.PROCESS_PLOTLY) { + /** + * units in mm --> convert to nm + */ + const plotyMultiple=1e6 + try { + const { data: plotlyData } = message.data.param + const { x, y, z } = plotlyData.traces[0] + const lm = [] + for (const idx in x) { + if (typeof x !== 'undefined' && x !== null) { + lm.push([x[idx]*plotyMultiple, y[idx]*plotyMultiple, z[idx]*plotyMultiple]) + } + } + if (plotyVtkUrl) URL.revokeObjectURL(plotyVtkUrl) + const vtkString = parseLmToVtk(lm, 1e-1) + plotyVtkUrl = URL.createObjectURL( + new Blob([ encoder.encode(vtkString) ], { type: 'application/octet-stream' }) + ) + postMessage({ + id, + result: { + objectUrl: plotyVtkUrl + } + }) + } catch (e) { + postMessage({ + id, + error: { + code: 401, + message: `malformed plotly param: ${e.toString()}` + } + }) + } + } + postMessage({ + id, + error: { + code: 404, + message: `worker method not found` + } + }) + return + } if(validTypes.findIndex(type => type === message.data.type) >= 0){ switch(message.data.type){