Skip to content
Snippets Groups Projects
threeSurfer.component.ts 5.62 KiB
Newer Older
Xiao Gui's avatar
Xiao Gui committed
import { Component, Input, Output, EventEmitter, ElementRef, OnChanges, OnDestroy, AfterViewInit } from "@angular/core";
import { IViewer, TViewerEvent } from "src/viewerModule/viewer.interface";
import { TThreeSurferConfig, TThreeSurferMode } from "../types";
Xiao Gui's avatar
Xiao Gui committed
import { parseContext } from "../util";
import { retry } from 'common/util'

@Component({
  selector: 'three-surfer-glue-cmp',
  templateUrl: './threeSurfer.template.html',
  styleUrls: [
    './threeSurfer.style.css'
  ]
})

export class ThreeSurferGlueCmp implements IViewer, OnChanges, AfterViewInit, OnDestroy {

  @Input()
  selectedTemplate: any

  @Input()
  selectedParcellation: any
  
  @Output()
  viewerEvent = new EventEmitter<TViewerEvent>()

  private domEl: HTMLElement
  private config: TThreeSurferConfig
  public modes: TThreeSurferMode[] = []
  public selectedMode: string
Xiao Gui's avatar
Xiao Gui committed
  private colormap: Map<string, Map<number, [number, number, number]>> = new Map()

  constructor(
Xiao Gui's avatar
Xiao Gui committed
    private el: ElementRef,
  ){
    this.domEl = this.el.nativeElement
  }

  tsRef: any
Xiao Gui's avatar
Xiao Gui committed
  loadedMeshes: {
Xiao Gui's avatar
Xiao Gui committed
    threeSurfer: any
    mesh: string
    colormap: string
    hemisphere: string
Xiao Gui's avatar
Xiao Gui committed
    vIdxArr: number[]
  }[] = []

  private unloadAllMeshes() {
    while(this.loadedMeshes.length > 0) {
      const m = this.loadedMeshes.pop()
Xiao Gui's avatar
Xiao Gui committed
      this.tsRef.unloadMesh(m.threeSurfer)
    }
  }

  public async loadMode(mode: TThreeSurferMode) {
    
    this.unloadAllMeshes()

    this.selectedMode = mode.name
    const { meshes } = mode
    for (const singleMesh of meshes) {
Xiao Gui's avatar
Xiao Gui committed
      const { mesh, colormap, hemisphere } = singleMesh
      
      const tsM = await this.tsRef.loadMesh(
        parseContext(mesh, [this.config['@context']])
      )
Xiao Gui's avatar
Xiao Gui committed
      const applyCM = this.colormap.get(hemisphere)

      const tsC = await this.tsRef.loadColormap(
        parseContext(colormap, [this.config['@context']])
      )
Xiao Gui's avatar
Xiao Gui committed
      
      let colorIdx = tsC[0].getData()
      if (tsC[0].attributes.DataType === 'NIFTI_TYPE_INT16') {
        colorIdx = (window as any).ThreeSurfer.GiftiBase.castF32UInt16(colorIdx)
      }
Xiao Gui's avatar
Xiao Gui committed

      this.loadedMeshes.push({
        threeSurfer: tsM,
        colormap,
        mesh,
        hemisphere,
        vIdxArr: colorIdx
      })
Xiao Gui's avatar
Xiao Gui committed
      this.tsRef.applyColorMap(tsM, colorIdx, 
        {
          custom: applyCM
        }
      )
Xiao Gui's avatar
Xiao Gui committed
  async ngOnChanges(){
    if (this.tsRef) this.ngOnDestroy()
    if (this.selectedTemplate) {
Xiao Gui's avatar
Xiao Gui committed

      /**
       * wait until threesurfer is defined in window
       */
      await retry(async () => {
        if (typeof (window as any).ThreeSurfer === 'undefined') throw new Error('ThreeSurfer not yet defined')
      }, {
        timeout: 160,
        retries: 10,
      })
      
      this.config = this.selectedTemplate['three-surfer']
      // somehow curv ... cannot be parsed properly by gifti parser... something about points missing
      this.modes = this.config.modes.filter(m => !/curv/.test(m.name))
      if (!this.tsRef) {
        this.tsRef = new (window as any).ThreeSurfer(this.domEl, {highlightHovered: true})
        this.onDestroyCb.push(
          () => {
            this.tsRef.dispose()
            this.tsRef = null
          }
        )
      }
Xiao Gui's avatar
Xiao Gui committed

      for (const region of this.selectedParcellation.regions) {
        const map = new Map<number, [number, number, number]>()
        for (const child of region.children) {
          const color = (child.iav?.rgb as [number, number, number] ) || [200, 200, 200]
          map.set(Number(child.grayvalue), color.map(v => v/255) as [number, number, number])
        }
        this.colormap.set(region.name, map)
      }
      
      // load mode0 by default
      this.loadMode(this.config.modes[0])

      this.viewerEvent.emit({
        type: 'VIEWERLOADED',
        data: true
      })
    }
  }
  ngAfterViewInit(){
    const customEvHandler = (ev: CustomEvent) => {
Xiao Gui's avatar
Xiao Gui committed
      
      if (!ev.detail.mesh) {
        return this.handleMouseoverEvent([])
      }

      const evGeom = ev.detail.mesh.geometry
      const evVertIdx = ev.detail.mesh.verticesIdicies
      const found = this.loadedMeshes.find(({ threeSurfer }) => threeSurfer === evGeom)
      
      if (!found) return this.handleMouseoverEvent([])

      const { hemisphere: key, vIdxArr } = found

      if (!key || !evVertIdx) {
        return this.handleMouseoverEvent([])
      }

Xiao Gui's avatar
Xiao Gui committed
      const labelIdxSet = new Set<number>()
Xiao Gui's avatar
Xiao Gui committed
      
      for (const vIdx of evVertIdx) {
        labelIdxSet.add(
          vIdxArr[vIdx]
        )
      }
      if (labelIdxSet.size === 0) {
        return this.handleMouseoverEvent([])
      }

      const foundRegion = this.selectedParcellation.regions.find(({ name }) => name === key)

      if (!foundRegion) {
        return this.handleMouseoverEvent(
          Array.from(labelIdxSet).map(v => {
            return `unknown#${v}`
          })
        )
      }

      return this.handleMouseoverEvent(
        Array.from(labelIdxSet)
          .map(lblIdx => {
            const ontoR = foundRegion.children.find(ontR => Number(ontR.grayvalue) === lblIdx)
            if (ontoR) {
              return ontoR.name
            } else {
              return `unkonwn#${lblIdx}`
            }
          })
      )

      
Xiao Gui's avatar
Xiao Gui committed
    this.domEl.addEventListener((window as any).ThreeSurfer.CUSTOM_EVENTNAME, customEvHandler)
    this.onDestroyCb.push(
Xiao Gui's avatar
Xiao Gui committed
      () => this.domEl.removeEventListener((window as any).ThreeSurfer.CUSTOM_EVENTNAME, customEvHandler)
Xiao Gui's avatar
Xiao Gui committed
  public mouseoverText: string
  private handleMouseoverEvent(mouseover: any[]){
    this.mouseoverText = mouseover.length === 0 ? null : mouseover.join(' / ')
  }

  private onDestroyCb: (() => void) [] = []

  ngOnDestroy() {
    while (this.onDestroyCb.length > 0) this.onDestroyCb.pop()()
  }
}