Skip to content
Snippets Groups Projects
nehubaViewer.component.ts 30.5 KiB
Newer Older
Xiao Gui's avatar
Xiao Gui committed
import { Component, ElementRef, EventEmitter, NgZone, OnDestroy, OnInit, Output, Renderer2 } from "@angular/core";
import { fromEvent, Subscription, ReplaySubject } from 'rxjs'
Xiao Gui's avatar
Xiao Gui committed
import { pipeFromArray } from "rxjs/internal/util/pipe";
import { debounceTime, filter, map, scan } from "rxjs/operators";
import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service";
Xiao Gui's avatar
Xiao Gui committed
import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service";
import { StateInterface as ViewerConfiguration } from "src/services/state/viewerConfig.store";
import { getNgIdLabelIndexFromId } from "src/services/stateStore.service";
Xiao Gui's avatar
Xiao Gui committed
import { takeOnePipe } from "../nehubaContainer.component";
Xiao Gui's avatar
Xiao Gui committed

Xiao Gui's avatar
Xiao Gui committed
import { LoggingService } from "src/services/logging.service";
import { getExportNehuba, getViewer, setNehubaViewer } from "src/util/fn";
import 'third_party/export_nehuba/chunk_worker.bundle.js'
Xiao Gui's avatar
Xiao Gui committed
import 'third_party/export_nehuba/main.bundle.js'
interface LayerLabelIndex {
Xiao Gui's avatar
Xiao Gui committed
  layer: {
Xiao Gui's avatar
Xiao Gui committed
    name: string
  }

  labelIndicies: number[]
}

Xiao Gui's avatar
Xiao Gui committed
const scanFn: (acc: LayerLabelIndex[], curr: LayerLabelIndex) => LayerLabelIndex[] = (acc: LayerLabelIndex[], curr: LayerLabelIndex) => {
  const { layer } = curr
  const { name } = layer
  const foundIndex = acc.findIndex(({ layer }) => layer.name === name)
Xiao Gui's avatar
Xiao Gui committed
  if (foundIndex < 0) { return acc.concat(curr) } else { return acc.map((item, idx) => idx === foundIndex
Xiao Gui's avatar
Xiao Gui committed
      ...item,
      labelIndicies: [...new Set([...item.labelIndicies, ...curr.labelIndicies])],
    }
Xiao Gui's avatar
Xiao Gui committed
  }
/**
 * no selector is needed, as currently, nehubaviewer is created dynamically
 */
Xiao Gui's avatar
Xiao Gui committed
@Component({
  templateUrl : './nehubaViewer.template.html',
  styleUrls : [
Xiao Gui's avatar
Xiao Gui committed
    './nehubaViewer.style.css',
  ],
Xiao Gui's avatar
Xiao Gui committed
})

Xiao Gui's avatar
Xiao Gui committed
export class NehubaViewerUnit implements OnInit, OnDestroy {

  private exportNehuba: any
  private viewer: any

  private subscriptions: Subscription[] = []
Xiao Gui's avatar
Xiao Gui committed

  @Output() public nehubaReady: EventEmitter<null> = new EventEmitter()
  @Output() public layersChanged: EventEmitter<null> = new EventEmitter()
  private layersChangedHandler: any
Xiao Gui's avatar
Xiao Gui committed
  @Output() public debouncedViewerPositionChange: EventEmitter<any> = new EventEmitter()
  @Output() public mouseoverSegmentEmitter:
    EventEmitter<{
Xiao Gui's avatar
Xiao Gui committed
      segmentId: number | null
      segment: string | null
Xiao Gui's avatar
Xiao Gui committed
      layer: {
Xiao Gui's avatar
Xiao Gui committed
        name?: string
        url?: string
      }
    }> = new EventEmitter()
Xiao Gui's avatar
Xiao Gui committed
  @Output() public mouseoverLandmarkEmitter: EventEmitter<number | null> = new EventEmitter()
  @Output() public mouseoverUserlandmarkEmitter: EventEmitter<number | null> = new EventEmitter()
  @Output() public regionSelectionEmitter: EventEmitter<{segment: number, layer: {name?: string, url?: string}}> = new EventEmitter()
  @Output() public errorEmitter: EventEmitter<any> = new EventEmitter()
  public auxilaryMeshIndices: number[] = []

  /* only used to set initial navigation state */
Xiao Gui's avatar
Xiao Gui committed
  public initNav: any
  public initRegions: any[]
  public initNiftiLayers: any[] = []
Xiao Gui's avatar
Xiao Gui committed
  public config: any
  public nehubaViewer: any
  private _dim: [number, number, number]
Xiao Gui's avatar
Xiao Gui committed
  get dim() {
    return this._dim
      ? this._dim
      : [1.5e9, 1.5e9, 1.5e9]
  }
Xiao Gui's avatar
Xiao Gui committed
  public _s1$: any
  public _s2$: any
  public _s3$: any
  public _s4$: any
  public _s5$: any
  public _s6$: any
  public _s7$: any
  public _s8$: any
  public _s9$: any
Xiao Gui's avatar
Xiao Gui committed
  public _s$: any[] = [
    this._s7$,
Xiao Gui's avatar
Xiao Gui committed
    this._s9$,
Xiao Gui's avatar
Xiao Gui committed
  public ondestroySubscriptions: Subscription[] = []
  private createNehubaPromiseRs: Function
  private createNehubaPromise = new Promise(rs => {
    this.createNehubaPromiseRs = rs
  })

    private rd: Renderer2,
Xiao Gui's avatar
Xiao Gui committed
    public elementRef: ElementRef,
    private workerService: AtlasWorkerService,
    private zone: NgZone,
    public constantService: AtlasViewerConstantsServices,
    private log: LoggingService,
  ) {
Xiao Gui's avatar
Xiao Gui committed
    if (!this.constantService.loadExportNehubaPromise) {
      this.constantService.loadExportNehubaPromise = new Promise((resolve, reject) => {
        const scriptEl = this.rd.createElement('script')
        scriptEl.src = 'main.bundle.js'
        scriptEl.onload = () => resolve(true)
        scriptEl.onerror = (e) => reject(e)
        this.rd.appendChild(window.document.head, scriptEl)
      })
    }

    this.constantService.loadExportNehubaPromise
      .then(() => {
Xiao Gui's avatar
Xiao Gui committed
        this.exportNehuba = getExportNehuba()
Xiao Gui's avatar
Xiao Gui committed
        const fixedZoomPerspectiveSlices = this.config && this.config.layout && this.config.layout.useNehubaPerspective && this.config.layout.useNehubaPerspective.fixedZoomPerspectiveSlices
        if (fixedZoomPerspectiveSlices) {
          const { sliceZoom, sliceViewportWidth, sliceViewportHeight } = fixedZoomPerspectiveSlices
          const dim = Math.min(sliceZoom * sliceViewportWidth, sliceZoom * sliceViewportHeight)
          this._dim = [dim, dim, dim]
        }
        this.patchNG()
        this.loadNehuba()

        this.layersChangedHandler = this.nehubaViewer.ngviewer.layerManager.layersChanged.add(() => this.layersChanged.emit(null))
        this.nehubaViewer.ngviewer.registerDisposer(this.layersChangedHandler)
      .then(() => {
        // all mutation to this.nehubaViewer should await createNehubaPromise
        this.createNehubaPromiseRs()
      })
      .catch(e => this.errorEmitter.emit(e))
    this.ondestroySubscriptions.push(
      fromEvent(this.workerService.worker, 'message').pipe(
Xiao Gui's avatar
Xiao Gui committed
        filter((message: any) => {
Xiao Gui's avatar
Xiao Gui committed
          if (!message) {
            // this.log.error('worker response message is undefined', message)
Xiao Gui's avatar
Xiao Gui committed
          if (!message.data) {
            // this.log.error('worker response message.data is undefined', message.data)
Xiao Gui's avatar
Xiao Gui committed
          if (message.data.type !== 'ASSEMBLED_LANDMARKS_VTK') {
            /* worker responded with not assembled landmark, no need to act */
            return false
          }
Xiao Gui's avatar
Xiao Gui committed
          if (!message.data.url) {
            /* file url needs to be defined */
            return false
          }
          return true
        }),
        debounceTime(100),
        filter(e => this.templateId === e.data.template),
Xiao Gui's avatar
Xiao Gui committed
        map(e => e.data.url),
      ).subscribe(url => {
        this.removeSpatialSearch3DLandmarks()
        const _ = {}
        _[this.constantService.ngLandmarkLayerName] = {
Xiao Gui's avatar
Xiao Gui committed
          type : 'mesh',
Xiao Gui's avatar
Xiao Gui committed
          shader : FRAGMENT_MAIN_WHITE,
Xiao Gui's avatar
Xiao Gui committed
      }),
    this.ondestroySubscriptions.push(
      fromEvent(this.workerService.worker, 'message').pipe(
Xiao Gui's avatar
Xiao Gui committed
        filter((message: any) => {
Xiao Gui's avatar
Xiao Gui committed
          if (!message) {
            // this.log.error('worker response message is undefined', message)
Xiao Gui's avatar
Xiao Gui committed
          if (!message.data) {
            // this.log.error('worker response message.data is undefined', message.data)
Xiao Gui's avatar
Xiao Gui committed
          if (message.data.type !== 'ASSEMBLED_USERLANDMARKS_VTK') {
            /* worker responded with not assembled landmark, no need to act */
            return false
          }
          /**
           * nb url may be undefined
           * if undefined, user have removed all user landmarks, and all that needs to be done
           * is remove the user landmark layer
Xiao Gui's avatar
Xiao Gui committed
           *
           * message.data.url
           */

          return true
        }),
        debounceTime(100),
Xiao Gui's avatar
Xiao Gui committed
        map(e => e.data.url),
      ).subscribe(url => {
        this.removeuserLandmarks()

        /**
         * url may be null if user removes all landmarks
         */
Xiao Gui's avatar
Xiao Gui committed
        if (!url) { return }
        const _ = {}
        _[this.constantService.ngUserLandmarkLayerName] = {
Xiao Gui's avatar
Xiao Gui committed
          type : 'mesh',
          source : `vtk://${url}`,
Xiao Gui's avatar
Xiao Gui committed
          shader : FRAGMENT_MAIN_WHITE,
        }
        this.loadLayer(_)
Xiao Gui's avatar
Xiao Gui committed
      }),
Xiao Gui's avatar
Xiao Gui committed
  private _baseUrlToParcellationIdMap: Map<string, string> = new Map()
  private _baseUrls: string[] = []

  public numMeshesToBeLoaded: number = 0
Xiao Gui's avatar
Xiao Gui committed
  public applyPerformanceConfig({ gpuLimit }: Partial<ViewerConfiguration>) {
    if (gpuLimit && this.nehubaViewer) {
      const limit = this.nehubaViewer.ngviewer.state.children.get('gpuMemoryLimit')
      if (limit && limit.restoreState) {
        limit.restoreState(gpuLimit)
      }
    }
  }

  /* required to check if correct landmarks are loaded */
Xiao Gui's avatar
Xiao Gui committed
  private _templateId: string
  get templateId() {
Xiao Gui's avatar
Xiao Gui committed
  set templateId(id: string) {
  /** compatible with multiple parcellation id selection */
  private _ngIds: string[] = []
Xiao Gui's avatar
Xiao Gui committed
  get ngIds() {
    return this._ngIds
Xiao Gui's avatar
Xiao Gui committed
  set ngIds(val: string[]) {
    this.createNehubaPromise
      .then(() => {
        this._ngIds.forEach(id => {
          const oldlayer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(id)
          if (oldlayer) {oldlayer.setVisible(false) } else { this.log.warn('could not find old layer', id) }
        })
        this._ngIds = val
        this.loadNewParcellation()
        this.showAllSeg()
Xiao Gui's avatar
Xiao Gui committed
  public spatialLandmarkSelectionChanged(labels: number[]) {
    const getCondition = (label: number) => `if(label > ${label - 0.1} && label < ${label + 0.1} ){${FRAGMENT_EMIT_RED}}`
    const newShader = `void main(){ ${labels.map(getCondition).join('else ')}else {${FRAGMENT_EMIT_WHITE}} }`
Xiao Gui's avatar
Xiao Gui committed
    if (!this.nehubaViewer) {
      if (!PRODUCTION) { this.log.warn('setting special landmark selection changed failed ... nehubaViewer is not yet defined') }
      return
    }
    const landmarkLayer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(this.constantService.ngLandmarkLayerName)
Xiao Gui's avatar
Xiao Gui committed
    if (!landmarkLayer) {
      if (!PRODUCTION) { this.log.warn('landmark layer could not be found ... will not update colour map') }
Xiao Gui's avatar
Xiao Gui committed
    if (labels.length === 0) {
      landmarkLayer.layer.displayState.fragmentMain.restoreState(FRAGMENT_MAIN_WHITE)
    } else {
      landmarkLayer.layer.displayState.fragmentMain.restoreState(newShader)
    }
  }

Xiao Gui's avatar
Xiao Gui committed
  public multiNgIdsLabelIndexMap: Map<string, Map<number, any>>
Xiao Gui's avatar
Xiao Gui committed
  public navPosReal: [number, number, number] = [0, 0, 0]
  public navPosVoxel: [number, number, number] = [0, 0, 0]
Xiao Gui's avatar
Xiao Gui committed
  public mousePosReal: [number, number, number] = [0, 0, 0]
  public mousePosVoxel: [number, number, number] = [0, 0, 0]
Xiao Gui's avatar
Xiao Gui committed
  public viewerState: ViewerState
Xiao Gui's avatar
Xiao Gui committed
  private _multiNgIdColorMap: Map<string, Map<number, {red: number, green: number, blue: number}>>
  get multiNgIdColorMap() {
    return this._multiNgIdColorMap
  }

  set multiNgIdColorMap(val) {
    this._multiNgIdColorMap = val
  }

  private loadMeshes$: ReplaySubject<{labelIndicies: number[], layer: { name: string }}> = new ReplaySubject()
Xiao Gui's avatar
Xiao Gui committed
  private loadMeshes(labelIndicies: number[], { name }) {
    this.loadMeshes$.next({
      labelIndicies,
Xiao Gui's avatar
Xiao Gui committed
      layer: { name },
  public mouseOverSegment: number | null
Xiao Gui's avatar
Xiao Gui committed
  public mouseOverLayer: {name: string, url: string}| null
Xiao Gui's avatar
Xiao Gui committed
  public viewportToDatas: [any, any, any] = [null, null, null]
Xiao Gui's avatar
Xiao Gui committed
  public getNgHash: () => string = () => this.exportNehuba
    ? this.exportNehuba.getNgHash()
Xiao Gui's avatar
Xiao Gui committed
  public redraw() {
    this.nehubaViewer.redraw()
  }

Xiao Gui's avatar
Xiao Gui committed
  public loadNehuba() {
    this.nehubaViewer = this.exportNehuba.createNehubaViewer(this.config, (err) => {
      /* print in debug mode */
Xiao Gui's avatar
Xiao Gui committed
      this.log.log(err)
    /**
     * Hide all layers except the base layer (template)
     * Then show the layers referenced in multiNgIdLabelIndexMap
     */
    const managedLayers = this.nehubaViewer.ngviewer.layerManager.managedLayers
Xiao Gui's avatar
Xiao Gui committed
    managedLayers.slice(1).forEach(layer => layer.setVisible(false))
    Array.from(this.multiNgIdsLabelIndexMap.keys()).forEach(ngId => {
      const layer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(ngId)
Xiao Gui's avatar
Xiao Gui committed
      if (layer) { layer.setVisible(true) } else { this.log.log('layer unavailable', ngId) }
    this.redraw()
Xiao Gui's avatar
Xiao Gui committed
    /* creation of the layout is done on next frame, hence the settimeout */
    setTimeout(() => {
Xiao Gui's avatar
Xiao Gui committed
      getViewer().display.panels.forEach(patchSliceViewPanel)
Xiao Gui's avatar
Xiao Gui committed
    })
Xiao Gui's avatar
Xiao Gui committed

    this.newViewerInit()
    this.loadNewParcellation()

Xiao Gui's avatar
Xiao Gui committed
    setNehubaViewer(this.nehubaViewer)
Xiao Gui's avatar
Xiao Gui committed
    this.onDestroyCb.push(() => setNehubaViewer(null))
    /**
     * TODO
     * move this to nehubaContainer
     * do NOT use position logic to determine idx
     */
Xiao Gui's avatar
Xiao Gui committed
    this.ondestroySubscriptions.push(
fsdavid's avatar
fsdavid committed
      // fromEvent(this.elementRef.nativeElement, 'viewportToData').pipe(
      //   ...takeOnePipe
      // ).subscribe((events:CustomEvent[]) => {
      //   [0,1,2].forEach(idx => this.viewportToDatas[idx] = events[idx].detail.viewportToData)
      // })
      pipeFromArray([...takeOnePipe])(fromEvent(this.elementRef.nativeElement, 'viewportToData'))
Xiao Gui's avatar
Xiao Gui committed
        .subscribe((events: CustomEvent[]) => {
          [0, 1, 2].forEach(idx => this.viewportToDatas[idx] = events[idx].detail.viewportToData)
        }),
Xiao Gui's avatar
Xiao Gui committed
    )
Xiao Gui's avatar
Xiao Gui committed
  public ngOnInit() {
    this.subscriptions.push(
      this.loadMeshes$.pipe(
Xiao Gui's avatar
Xiao Gui committed
        scan(scanFn, []),
      ).subscribe(layersLabelIndex => {
        let totalMeshes = 0
Xiao Gui's avatar
Xiao Gui committed
        for (const layerLayerIndex of layersLabelIndex) {
          const { layer, labelIndicies } = layerLayerIndex
          totalMeshes += labelIndicies.length
          this.nehubaViewer.setMeshesToLoad(labelIndicies, layer)
        }
        // TODO implement total mesh to be loaded and mesh loading UI
        // this.numMeshesToBeLoaded = totalMeshes
Xiao Gui's avatar
Xiao Gui committed
      }),
Xiao Gui's avatar
Xiao Gui committed
  public ngOnDestroy() {
    while (this.subscriptions.length > 0) {
      this.subscriptions.pop().unsubscribe()
    }

Xiao Gui's avatar
Xiao Gui committed
    this._s$.forEach(_s$ => {
      if (_s$) { _s$.unsubscribe() }
    this.ondestroySubscriptions.forEach(s => s.unsubscribe())
Xiao Gui's avatar
Xiao Gui committed
    while (this.onDestroyCb.length > 0) {
      this.onDestroyCb.pop()()
    }
Xiao Gui's avatar
Xiao Gui committed
    this.nehubaViewer.dispose()
Xiao Gui's avatar
Xiao Gui committed
  private onDestroyCb: Array<() => void> = []

  private patchNG() {
Xiao Gui's avatar
Xiao Gui committed
    const { LayerManager, UrlHashBinding } = this.exportNehuba.getNgPatchableObj()
xgui3783's avatar
xgui3783 committed

    UrlHashBinding.prototype.setUrlHash = () => {
Xiao Gui's avatar
Xiao Gui committed
      // this.log.log('seturl hash')
      // this.log.log('setting url hash')
xgui3783's avatar
xgui3783 committed
    }

    UrlHashBinding.prototype.updateFromUrlHash = () => {
Xiao Gui's avatar
Xiao Gui committed
      // this.log.log('update hash binding')
xgui3783's avatar
xgui3783 committed
    }
Xiao Gui's avatar
Xiao Gui committed

xgui3783's avatar
xgui3783 committed
    /* TODO find a more permanent fix to disable double click */
    LayerManager.prototype.invokeAction = (arg) => {

      /**
       * The emitted value does not affect the region selection
       * the region selection is taken care of in nehubaContainer
       */
      const map = this.multiNgIdsLabelIndexMap.get(this.mouseOverLayer.name)
      const region = map && map.get(this.mouseOverSegment)
      if (arg === 'select') {
        this.regionSelectionEmitter.emit({ segment: region, layer: this.mouseOverLayer })
xgui3783's avatar
xgui3783 committed
      }
    }
Xiao Gui's avatar
Xiao Gui committed
    /* eslint-disable-next-line @typescript-eslint/no-empty-function */
    this.onDestroyCb.push(() => LayerManager.prototype.invokeAction = (_arg) => { /** in default neuroglancer, this function is invoked when selection occurs */ })
xgui3783's avatar
xgui3783 committed
  }

Xiao Gui's avatar
Xiao Gui committed
  private filterLayers(l: any, layerObj: any): boolean {
    /**
     * if selector is an empty object, select all layers
     */
Xiao Gui's avatar
Xiao Gui committed
    return layerObj instanceof Object && Object.keys(layerObj).every(key =>
      /**
       * the property described by the selector must exist and ...
       */
Xiao Gui's avatar
Xiao Gui committed
      !!l[key] &&
        /**
         * if the selector is regex, test layer property
         */
        ( layerObj[key] instanceof RegExp
          ? layerObj[key].test(l[key])
          /**
           * if selector is string, test for strict equality
           */
          : typeof layerObj[key] === 'string'
            ? layerObj[key] === l[key]
            /**
             * otherwise do not filter
             */
Xiao Gui's avatar
Xiao Gui committed
            : false
        ),
Xiao Gui's avatar
Xiao Gui committed
    )
Xiao Gui's avatar
Xiao Gui committed
  // TODO single landmark for user landmark
Xiao Gui's avatar
Xiao Gui committed
  public updateUserLandmarks(landmarks: any[]) {
    if (!this.nehubaViewer) {
Xiao Gui's avatar
Xiao Gui committed
    }
    this.workerService.worker.postMessage({
      type : 'GET_USERLANDMARKS_VTK',
      scale: Math.min(...this.dim.map(v => v * this.constantService.nehubaLandmarkConstant)),
Xiao Gui's avatar
Xiao Gui committed
      landmarks : landmarks.map(lm => lm.position.map(coord => coord * 1e6)),
Xiao Gui's avatar
Xiao Gui committed
  public removeSpatialSearch3DLandmarks() {
Xiao Gui's avatar
Xiao Gui committed
      name : this.constantService.ngLandmarkLayerName,
Xiao Gui's avatar
Xiao Gui committed
  public removeuserLandmarks() {
Xiao Gui's avatar
Xiao Gui committed
      name : this.constantService.ngUserLandmarkLayerName,
Xiao Gui's avatar
Xiao Gui committed
  // pos in mm
  public addSpatialSearch3DLandmarks(geometries: any[], scale?: number, type?: 'icosahedron') {
    this.workerService.worker.postMessage({
      scale: Math.min(...this.dim.map(v => v * this.constantService.nehubaLandmarkConstant)),
Xiao Gui's avatar
Xiao Gui committed
      landmarks : geometries.map(geometry =>
Xiao Gui's avatar
Xiao Gui committed
          // gemoetry : [number,number,number] | [ [number,number,number][], [number,number,number][] ]
            ? [geometry[0].map(triplets => triplets.map(coord => coord * 1e6)), geometry[1]]
Xiao Gui's avatar
Xiao Gui committed
            : geometry.map(coord => coord * 1e6),
      ),
Xiao Gui's avatar
Xiao Gui committed
  public setLayerVisibility(condition: {name: string|RegExp}, visible: boolean) {
    if (!this.nehubaViewer) {
Xiao Gui's avatar
Xiao Gui committed
    }
Xiao Gui's avatar
Xiao Gui committed
    const viewer = this.nehubaViewer.ngviewer
    viewer.layerManager.managedLayers
Xiao Gui's avatar
Xiao Gui committed
      .filter(l => this.filterLayers(l, condition))
      .map(layer => layer.setVisible(visible))
Xiao Gui's avatar
Xiao Gui committed
  public removeLayer(layerObj: any) {
    if (!this.nehubaViewer) {
Xiao Gui's avatar
Xiao Gui committed
    }
    const viewer = this.nehubaViewer.ngviewer
Xiao Gui's avatar
Xiao Gui committed
    const removeLayer = (i) => (viewer.layerManager.removeManagedLayer(i), i.name)

    return viewer.layerManager.managedLayers
Xiao Gui's avatar
Xiao Gui committed
      .filter(l => this.filterLayers(l, layerObj))
      .map(removeLayer)
  }

Xiao Gui's avatar
Xiao Gui committed
  public loadLayer(layerObj: any) {
    const viewer = this.nehubaViewer.ngviewer
    return Object.keys(layerObj)
Xiao Gui's avatar
Xiao Gui committed
      .filter(key =>
        /* if the layer exists, it will not be loaded */
        !viewer.layerManager.getLayerByName(key))
Xiao Gui's avatar
Xiao Gui committed
      .map(key => {
        viewer.layerManager.addManagedLayer(
Xiao Gui's avatar
Xiao Gui committed
          viewer.layerSpecification.getLayer(key, layerObj[key]))

        return layerObj[key]
      })
  }

Xiao Gui's avatar
Xiao Gui committed
  public hideAllSeg() {
    if (!this.nehubaViewer) { return }
    Array.from(this.multiNgIdsLabelIndexMap.keys()).forEach(ngId => {
Xiao Gui's avatar
Xiao Gui committed

      Array.from(this.multiNgIdsLabelIndexMap.get(ngId).keys()).forEach(idx => {
        this.nehubaViewer.hideSegment(idx, {
Xiao Gui's avatar
Xiao Gui committed
          name: ngId,
        })
      })
      this.nehubaViewer.showSegment(0, {
Xiao Gui's avatar
Xiao Gui committed
        name: ngId,
Xiao Gui's avatar
Xiao Gui committed
  public showAllSeg() {
    if (!this.nehubaViewer) { return }
    Array.from(this.multiNgIdsLabelIndexMap.keys()).forEach(ngId => {
Xiao Gui's avatar
Xiao Gui committed
      this.nehubaViewer.hideSegment(0, {
        name: ngId,
Xiao Gui's avatar
Xiao Gui committed
  public showSegs(array: (number|string)[]) {
Xiao Gui's avatar
Xiao Gui committed
    if (!this.nehubaViewer) { return }
Xiao Gui's avatar
Xiao Gui committed
    if (array.length === 0) { return }
    /**
     * TODO tobe deprecated
     */

    if (typeof array[0] === 'number') {
Xiao Gui's avatar
Xiao Gui committed
      this.log.warn(`show seg with number indices has been deprecated`)
Xiao Gui's avatar
Xiao Gui committed
    }
Xiao Gui's avatar
Xiao Gui committed
    const reduceFn: (acc: Map<string, number[]>, curr: string) => Map<string, number[]> = (acc, curr) => {

      const newMap = new Map(acc)
      const { ngId, labelIndex } = getNgIdLabelIndexFromId({ labelIndexId: curr })
      const exist = newMap.get(ngId)
Xiao Gui's avatar
Xiao Gui committed
      if (!exist) { newMap.set(ngId, [Number(labelIndex)]) } else { newMap.set(ngId, [...exist, Number(labelIndex)]) }
Xiao Gui's avatar
Xiao Gui committed

    const newMap: Map<string, number[]> = array.reduce(reduceFn, new Map())

    /**
     * TODO
     * ugh, ugly code. cleanify
     */
Xiao Gui's avatar
Xiao Gui committed
     * TODO
     * sometimes, ngId still happends to be undefined
     */
    newMap.forEach((segs, ngId) => {
      this.nehubaViewer.hideSegment(0, {
Xiao Gui's avatar
Xiao Gui committed
        name: ngId,
      })
      segs.forEach(seg => {
        this.nehubaViewer.showSegment(seg, {
Xiao Gui's avatar
Xiao Gui committed
          name: ngId,
Xiao Gui's avatar
Xiao Gui committed
  private vec3(pos: [number, number, number]) {
    return this.exportNehuba.vec3.fromValues(...pos)
Xiao Gui's avatar
Xiao Gui committed
  public setNavigationState(newViewerState: Partial<ViewerState>) {
Xiao Gui's avatar
Xiao Gui committed
    if (!this.nehubaViewer) {
      if (!PRODUCTION) {
        this.log.warn('setNavigationState > this.nehubaViewer is not yet defined')
      }
    const {
      orientation,
      perspectiveOrientation,
      perspectiveZoom,
      position,
      positionReal,
Xiao Gui's avatar
Xiao Gui committed
      zoom,
Xiao Gui's avatar
Xiao Gui committed
    if ( perspectiveZoom ) {
      this.nehubaViewer.ngviewer.perspectiveNavigationState.zoomFactor.restoreState(perspectiveZoom)
Xiao Gui's avatar
Xiao Gui committed
    }
    if ( zoom ) {
      this.nehubaViewer.ngviewer.navigationState.zoomFactor.restoreState(zoom)
Xiao Gui's avatar
Xiao Gui committed
    }
    if ( perspectiveOrientation ) {
      this.nehubaViewer.ngviewer.perspectiveNavigationState.pose.orientation.restoreState( perspectiveOrientation )
Xiao Gui's avatar
Xiao Gui committed
    }
    if ( orientation ) {
      this.nehubaViewer.ngviewer.navigationState.pose.orientation.restoreState( orientation )
Xiao Gui's avatar
Xiao Gui committed
    }
    if ( position ) {
      this.nehubaViewer.setPosition( this.vec3(position) , positionReal ? true : false )
Xiao Gui's avatar
Xiao Gui committed
    }
Xiao Gui's avatar
Xiao Gui committed
  public obliqueRotateX(amount: number) {
Xiao Gui's avatar
Xiao Gui committed
    this.nehubaViewer.ngviewer.navigationState.pose.rotateRelative(this.vec3([0, 1, 0]), -amount / 4.0 * Math.PI / 180.0)
  }

Xiao Gui's avatar
Xiao Gui committed
  public obliqueRotateY(amount: number) {
Xiao Gui's avatar
Xiao Gui committed
    this.nehubaViewer.ngviewer.navigationState.pose.rotateRelative(this.vec3([1, 0, 0]), amount / 4.0 * Math.PI / 180.0)
  }

Xiao Gui's avatar
Xiao Gui committed
  public obliqueRotateZ(amount: number) {
Xiao Gui's avatar
Xiao Gui committed
    this.nehubaViewer.ngviewer.navigationState.pose.rotateRelative(this.vec3([0, 0, 1]), amount / 4.0 * Math.PI / 180.0)
  }

Xiao Gui's avatar
Xiao Gui committed
   *
   * @param arrayIdx label indices of the shown segment(s)
   * @param ngId segmentation layer name
   */
Xiao Gui's avatar
Xiao Gui committed
  private updateColorMap(arrayIdx: number[], ngId: string) {
    const set = new Set(arrayIdx)
    const newColorMap = new Map(
      Array.from(this.multiNgIdColorMap.get(ngId).entries())
Xiao Gui's avatar
Xiao Gui committed
        .map(v => set.has(v[0]) || set.size === 0 ?
Xiao Gui's avatar
Xiao Gui committed
          [v[0], {red: 255, green: 255, blue: 255}]) as any,
Xiao Gui's avatar
Xiao Gui committed
    this.nehubaViewer.batchAddAndUpdateSegmentColors(newColorMap, {
      name: ngId,
Xiao Gui's avatar
Xiao Gui committed
  private newViewerInit() {
    /* 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
Xiao Gui's avatar
Xiao Gui committed
      .subscribe(({ segment, layer }) => {
        this.mouseOverSegment = segment
        this.mouseOverLayer = { ...layer }
      })
Xiao Gui's avatar
Xiao Gui committed
    if (this.initNav) {
      this.setNavigationState(this.initNav)
    }
    if (this.initRegions && this.initRegions.length > 0) {
      this.hideAllSeg()
      this.showSegs(this.initRegions)
    }

Xiao Gui's avatar
Xiao Gui committed
    if (this.initNiftiLayers.length > 0) {
      this.initNiftiLayers.forEach(layer => this.loadLayer(layer))
      this.hideAllSeg()
    }

Xiao Gui's avatar
Xiao Gui committed
    this._s8$ = this.nehubaViewer.mouseOver.segment.subscribe(({segment: segmentId, layer }) => {
Xiao Gui's avatar
Xiao Gui committed

      const {name = 'unnamed'} = layer
      const map = this.multiNgIdsLabelIndexMap.get(name)
      const region = map && map.get(segmentId)
      this.mouseoverSegmentEmitter.emit({
        layer,
        segment: region,
Xiao Gui's avatar
Xiao Gui committed
        segmentId,
    // nehubaViewer.navigationState.all emits every time a new layer is added or removed from the viewer
    this._s3$ = this.nehubaViewer.navigationState.all
      .debounceTime(300)
      .distinctUntilChanged((a, b) => {
        const {
          orientation: o1,
          perspectiveOrientation: po1,
          perspectiveZoom: pz1,
          position: p1,
Xiao Gui's avatar
Xiao Gui committed
          zoom: z1,
        } = a
        const {
          orientation: o2,
          perspectiveOrientation: po2,
          perspectiveZoom: pz2,
          position: p2,
Xiao Gui's avatar
Xiao Gui committed
          zoom: z2,
Xiao Gui's avatar
Xiao Gui committed
        return [0, 1, 2, 3].every(idx => o1[idx] === o2[idx]) &&
          [0, 1, 2, 3].every(idx => po1[idx] === po2[idx]) &&
Xiao Gui's avatar
Xiao Gui committed
          [0, 1, 2].every(idx => p1[idx] === p2[idx]) &&
Xiao Gui's avatar
Xiao Gui committed
      .subscribe(({ orientation, perspectiveOrientation, perspectiveZoom, position, zoom }) => {
        this.viewerState = {
          orientation,
          perspectiveOrientation,
          perspectiveZoom,
          zoom,
          position,
Xiao Gui's avatar
Xiao Gui committed
          positionReal : false,
Xiao Gui's avatar
Xiao Gui committed

        this.debouncedViewerPositionChange.emit({
          orientation : Array.from(orientation),
          perspectiveOrientation : Array.from(perspectiveOrientation),
          perspectiveZoom,
          zoom,
          position,
Xiao Gui's avatar
Xiao Gui committed
          positionReal : true,
Xiao Gui's avatar
Xiao Gui committed
    // TODO bug: mouseoverlandmarkemitter does not emit empty for VTK layer when user mouse click
    this.ondestroySubscriptions.push(
      this.nehubaViewer.mouseOver.layer
        .filter(obj => obj.layer.name === this.constantService.ngLandmarkLayerName)
Xiao Gui's avatar
Xiao Gui committed
        .subscribe(obj => this.mouseoverLandmarkEmitter.emit(obj.value)),
    this.ondestroySubscriptions.push(
      this.nehubaViewer.mouseOver.layer
        .filter(obj => obj.layer.name === this.constantService.ngUserLandmarkLayerName)
Xiao Gui's avatar
Xiao Gui committed
        .subscribe(obj => this.mouseoverUserlandmarkEmitter.emit(obj.value)),
    this._s4$ = this.nehubaViewer.navigationState.position.inRealSpace
Xiao Gui's avatar
Xiao Gui committed
      .filter(v => typeof v !== 'undefined' && v !== null)
      .subscribe(v => this.navPosReal = v)
    this._s5$ = this.nehubaViewer.navigationState.position.inVoxels
Xiao Gui's avatar
Xiao Gui committed
      .filter(v => typeof v !== 'undefined' && v !== null)
      .subscribe(v => this.navPosVoxel = v)
    this._s6$ = this.nehubaViewer.mousePosition.inRealSpace
Xiao Gui's avatar
Xiao Gui committed
      .filter(v => typeof v !== 'undefined' && v !== null)
      .subscribe(v => (this.mousePosReal = v))
    this._s7$ = this.nehubaViewer.mousePosition.inVoxels
Xiao Gui's avatar
Xiao Gui committed
      .filter(v => typeof v !== 'undefined' && v !== null)
      .subscribe(v => (this.mousePosVoxel = v))
Xiao Gui's avatar
Xiao Gui committed

Xiao Gui's avatar
Xiao Gui committed
  private loadNewParcellation() {

    /* show correct segmentation layer */
    this._baseUrls = []

    this.ngIds.map(id => {
      const newlayer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(id)
Xiao Gui's avatar
Xiao Gui committed
      if (newlayer) {newlayer.setVisible(true) } else { this.log.warn('could not find new layer', id) }
Xiao Gui's avatar
Xiao Gui committed
      const regex = /^(\S.*?):\/\/(.*?)$/.exec(newlayer.sourceUrl)
Xiao Gui's avatar
Xiao Gui committed

      if (!regex || !regex[2]) {
        this.log.error('could not parse baseUrl')
Xiao Gui's avatar
Xiao Gui committed
      if (regex[1] !== 'precomputed') {
        this.log.error('sourceUrl is not precomputed')
Xiao Gui's avatar
Xiao Gui committed

      const baseUrl = regex[2]
      this._baseUrls.push(baseUrl)
      this._baseUrlToParcellationIdMap.set(baseUrl, id)

      const indicies = [
        ...Array.from(this.multiNgIdsLabelIndexMap.get(id).keys()),
Xiao Gui's avatar
Xiao Gui committed
        ...this.auxilaryMeshIndices,
      ]

      this.loadMeshes(indicies, { name: id })
    const obj = Array.from(this.multiNgIdsLabelIndexMap.keys()).map(ngId => {
      return [
Xiao Gui's avatar
Xiao Gui committed
        ngId,
        new Map(Array.from(
          [
            ...this.multiNgIdsLabelIndexMap.get(ngId).entries(),
            ...this.auxilaryMeshIndices.map(val => {
              return [val, {}]
Xiao Gui's avatar
Xiao Gui committed
            }),
          ],
        ).map((val: [number, any]) => ([val[0], this.getRgb(val[0], val[1].rgb)])) as any),
Xiao Gui's avatar
Xiao Gui committed
    }) as Array<[string, Map<number, {red: number, green: number, blue: number}>]>
    const multiNgIdColorMap = new Map(obj)

    /* load colour maps */
Xiao Gui's avatar
Xiao Gui committed

    this.setColorMap(multiNgIdColorMap)
Xiao Gui's avatar
Xiao Gui committed
    this._s$.forEach(_s$ => {
      if (_s$) { _s$.unsubscribe() }
Xiao Gui's avatar
Xiao Gui committed
    if (this._s1$) {this._s1$.unsubscribe() }
    if (this._s9$) {this._s9$.unsubscribe() }

    const arr = Array.from(this.multiNgIdsLabelIndexMap.keys()).map(ngId => {
      return this.nehubaViewer.getShownSegmentsObservable({
Xiao Gui's avatar
Xiao Gui committed
        name: ngId,
      }).subscribe(arrayIdx => this.updateColorMap(arrayIdx, ngId))
    })

    this._s9$ = {
      unsubscribe: () => {
Xiao Gui's avatar
Xiao Gui committed
        while (arr.length > 0) {
          arr.pop().unsubscribe()
        }
Xiao Gui's avatar
Xiao Gui committed
      },
Xiao Gui's avatar
Xiao Gui committed
  public setColorMap(map: Map<string, Map<number, {red: number, green: number, blue: number}>>) {
    this.multiNgIdColorMap = map
Xiao Gui's avatar
Xiao Gui committed

    Array.from(map.entries()).forEach(([ngId, map]) => {

      this.nehubaViewer.batchAddAndUpdateSegmentColors(
        map,
        { name : ngId })
    })
  }

Xiao Gui's avatar
Xiao Gui committed
  private getRgb(labelIndex: number, rgb?: number[]): {red: number, green: number, blue: number} {
    if (typeof rgb === 'undefined' || rgb === null) {
      const arr = intToColour(Number(labelIndex))
        red : arr[0],
        green: arr[1],
Xiao Gui's avatar
Xiao Gui committed
        blue : arr[2],
    return {
      red : rgb[0],
      green: rgb[1],
Xiao Gui's avatar
Xiao Gui committed
      blue : rgb[2],
Xiao Gui's avatar
Xiao Gui committed
  }
Xiao Gui's avatar
Xiao Gui committed
const patchSliceViewPanel = (sliceViewPanel: any) => {
  const originalDraw = sliceViewPanel.draw
Xiao Gui's avatar
Xiao Gui committed
  sliceViewPanel.draw = function(this) {

    if (this.sliceView) {
Xiao Gui's avatar
Xiao Gui committed
      const viewportToDataEv = new CustomEvent('viewportToData', {
        bubbles: true,
        detail: {
Xiao Gui's avatar
Xiao Gui committed
          viewportToData : this.sliceView.viewportToData,
        },
Xiao Gui's avatar
Xiao Gui committed
      })
      this.element.dispatchEvent(viewportToDataEv)
    }

    originalDraw.call(this)
  }
}

Xiao Gui's avatar
Xiao Gui committed
 *
 * https://stackoverflow.com/a/16348977/6059235
 */
const intToColour = function(int) {
Xiao Gui's avatar
Xiao Gui committed
  if (int >= 65500) {
    return [255, 255, 255]
Xiao Gui's avatar
Xiao Gui committed
  const str = String(int * 65535)
  let hash = 0
  for (let i = 0; i < str.length; i++) {
    hash = str.charCodeAt(i) + ((hash << 5) - hash);
  }
  const returnV = []
  for (let i = 0; i < 3; i++) {
    const value = (hash >> (i * 8)) & 0xFF;
    returnV.push(value)
  }
  return returnV
}

Xiao Gui's avatar
Xiao Gui committed
export interface ViewerState {
  orientation: [number, number, number, number]
  perspectiveOrientation: [number, number, number, number]
  perspectiveZoom: number
  position: [number, number, number]
  positionReal: boolean
  zoom: number

export const ICOSAHEDRON = `# vtk DataFile Version 2.0
Converted using https://github.com/HumanBrainProject/neuroglancer-scripts
ASCII
DATASET POLYDATA
POINTS 12 float
-525731.0 0.0 850651.0
525731.0 0.0 850651.0
-525731.0 0.0 -850651.0
525731.0 0.0 -850651.0
0.0 850651.0 525731.0
0.0 850651.0 -525731.0
0.0 -850651.0 525731.0
0.0 -850651.0 -525731.0
850651.0 525731.0 0.0
-850651.0 525731.0 0.0
850651.0 -525731.0 0.0
-850651.0 -525731.0 0.0
POLYGONS 20 80
3 1 4 0
3 4 9 0
3 4 5 9
3 8 5 4
3 1 8 4
3 1 10 8
3 10 3 8
3 8 3 5
3 3 2 5
3 3 7 2
3 3 10 7
3 10 6 7
3 6 11 7
3 6 0 11
3 6 1 0
3 10 1 6
3 11 0 9
3 2 11 9
3 5 2 9
3 11 2 7`

declare const TextEncoder