Skip to content
Snippets Groups Projects
layerCtrl.effects.ts 6.62 KiB
Newer Older
import { Injectable } from "@angular/core";
import { createEffect } from "@ngrx/effects";
import { select, Store } from "@ngrx/store";
Xiao Gui's avatar
Xiao Gui committed
import { forkJoin, from, of } from "rxjs";
import { switchMap, withLatestFrom, catchError, map, debounceTime, shareReplay, distinctUntilChanged, tap } from "rxjs/operators";
import { NgLayerSpec, NgPrecompMeshSpec, NgSegLayerSpec, SxplrAtlas, SxplrParcellation, SxplrTemplate, VoiFeature } from "src/atlasComponents/sapi/sxplrTypes";
Xiao Gui's avatar
Xiao Gui committed
import { SAPI } from "src/atlasComponents/sapi"
import { atlasAppearance, atlasSelection } from "src/state";
Xiao Gui's avatar
Xiao Gui committed
import { arrayEqual } from "src/util/array";
import { EnumColorMapName } from "src/util/colorMaps";
Xiao Gui's avatar
Xiao Gui committed
import { getShader } from "src/util/fn";
import { PMAP_LAYER_NAME } from "../constants";
Xiao Gui's avatar
Xiao Gui committed
import { QuickHash } from "src/util/fn";
import { getParcNgId } from "../config.service";

@Injectable()
export class LayerCtrlEffects {
  static TransformVolumeModel(volumeModel: VoiFeature['ngVolume']): atlasAppearance.const.NgLayerCustomLayer[] {    
    return [{
      clType: "customlayer/nglayer",
Xiao Gui's avatar
Xiao Gui committed
      legacySpecFlag: "old",
      id: volumeModel.url,
      source: `precomputed://${volumeModel.url}`,
      transform: volumeModel.transform,
    }]
Xiao Gui's avatar
Xiao Gui committed
  }

Xiao Gui's avatar
Xiao Gui committed
  #onATP$ = this.store.pipe(
    atlasSelection.fromRootStore.distinctATP(),
    map(val => val as { atlas: SxplrAtlas, parcellation: SxplrParcellation, template: SxplrTemplate }),
  )

  #pmapUrl: string
Xiao Gui's avatar
Xiao Gui committed
  #cleanupUrl(){
    if (!!this.#pmapUrl) {
      URL.revokeObjectURL(this.#pmapUrl)
      this.#pmapUrl = null
    }
  }

  onRegionSelect = createEffect(() => this.store.pipe(
    select(atlasAppearance.selectors.useViewer),
    switchMap(viewer => {
      const rmPmapAction = atlasAppearance.actions.removeCustomLayer({
        id: PMAP_LAYER_NAME
      })
      if (viewer !== "NEHUBA") {
        this.#cleanupUrl()
        return of(rmPmapAction)
      }
      return this.store.pipe(
        select(atlasSelection.selectors.selectedRegions),
        distinctUntilChanged(
          arrayEqual((o, n) => o.name === n.name)
        ),
        withLatestFrom(this.#onATP$),
        // since region selection changed, pmap will definitely be removed. revoke the url resource.
        tap(() => this.#cleanupUrl()),
        switchMap(([ regions, { parcellation, template } ]) => {
          if (regions.length !== 1) {
            return of(rmPmapAction)
Xiao Gui's avatar
Xiao Gui committed
          }
Xiao Gui's avatar
Xiao Gui committed
          return this.sapi.getStatisticalMap(parcellation, template, regions[0]).pipe(
            switchMap(({ buffer, meta }) => {
              this.#pmapUrl = URL.createObjectURL(new Blob([buffer], {type: "application/octet-stream"}))
              return of(
                rmPmapAction,
                atlasAppearance.actions.addCustomLayer({
                  customLayer: {
Xiao Gui's avatar
Xiao Gui committed
                    legacySpecFlag: "old",
Xiao Gui's avatar
Xiao Gui committed
                    clType: "customlayer/nglayer",
                    id: PMAP_LAYER_NAME,
                    source: `nifti://${this.#pmapUrl}`,
                    shader: getShader({
                      colormap: EnumColorMapName.VIRIDIS,
                      highThreshold: meta.max,
                      lowThreshold: meta.min,
                      removeBg: true,
Xiao Gui's avatar
Xiao Gui committed
                    }),
Xiao Gui's avatar
Xiao Gui committed
                    type: 'image',
                    opacity: 0.5
Xiao Gui's avatar
Xiao Gui committed
                  }
                })
              )
            }),
            catchError(() => of(rmPmapAction)),
          )
Xiao Gui's avatar
Xiao Gui committed
        })
Xiao Gui's avatar
Xiao Gui committed
    })
Xiao Gui's avatar
Xiao Gui committed
  onATPClearBaseLayers = createEffect(() => this.#onATP$.pipe(
Xiao Gui's avatar
Xiao Gui committed
    withLatestFrom(
      this.store.pipe(
        select(atlasAppearance.selectors.customLayers),
        map(
          cl => cl.filter(layer =>
            layer.clType === "baselayer/nglayer"
            || layer.clType === "customlayer/nglayer"
          )
Xiao Gui's avatar
Xiao Gui committed
        )
      )
    ),
    switchMap(([_, layers]) => 
      of(
        ...layers.map(layer => 
          atlasAppearance.actions.removeCustomLayer({
            id: layer.id
          })  
        )
      )
    )
  ))

Xiao Gui's avatar
Xiao Gui committed
  onATPDebounceNgLayers$ = this.#onATP$.pipe(
Xiao Gui's avatar
Xiao Gui committed
    debounceTime(16),
    switchMap(({ atlas, template, parcellation }) => 
      forkJoin({
        tmplNgLayers: this.sapi.getVoxelTemplateImage(template).pipe(
          map(templateImages => {
Xiao Gui's avatar
Xiao Gui committed
            const returnObj: Record<string, NgLayerSpec> = {}
Xiao Gui's avatar
Xiao Gui committed
            for (const img of templateImages) {
Xiao Gui's avatar
Xiao Gui committed
              const url = typeof img.source === "string"
              ? img.source
              : img.source.url
              returnObj[QuickHash.GetHash(url)] = img
Xiao Gui's avatar
Xiao Gui committed
            return returnObj
Xiao Gui's avatar
Xiao Gui committed
        tmplAuxNgLayers: this.sapi.getVoxelAuxMesh(template).pipe(
          map(auxMeshes => {
Xiao Gui's avatar
Xiao Gui committed
            const returnObj: Record<string, NgPrecompMeshSpec> = {}
Xiao Gui's avatar
Xiao Gui committed
            for (const img of auxMeshes) {
              returnObj[QuickHash.GetHash(`${img.source}_auxMesh`)] = img
Xiao Gui's avatar
Xiao Gui committed
            }
Xiao Gui's avatar
Xiao Gui committed
            return returnObj
          })
Xiao Gui's avatar
Xiao Gui committed
        parcNgLayers: from(this.sapi.getTranslatedLabelledNgMap(parcellation, template)).pipe(
          map(val => {
            const returnVal: Record<string, NgSegLayerSpec> = {}
Xiao Gui's avatar
Xiao Gui committed
            for (const [ /** url */, { layer, region } ] of Object.entries(val)) {
Xiao Gui's avatar
Xiao Gui committed
              const { name } = region[0]
              const ngId = getParcNgId(atlas, template, parcellation, {
                id: '',
                name,
                parentIds: [],
                type: "SxplrRegion"
Xiao Gui's avatar
Xiao Gui committed
              })
              returnVal[ngId] = layer
              continue
            }
            return returnVal
          })
        )
      })
    ),
    shareReplay(1),
Xiao Gui's avatar
Xiao Gui committed
  )

  onATPDebounceAddBaseLayers$ = createEffect(() => this.onATPDebounceNgLayers$.pipe(
    switchMap(ngLayers => {
      const { parcNgLayers, tmplAuxNgLayers, tmplNgLayers } = ngLayers
      
Xiao Gui's avatar
Xiao Gui committed
      const customBaseLayers: atlasAppearance.const.NgLayerCustomLayer[] = []
Xiao Gui's avatar
Xiao Gui committed
      for (const layers of [parcNgLayers, tmplAuxNgLayers, tmplNgLayers]) {
        for (const key in layers) {
Xiao Gui's avatar
Xiao Gui committed
          const v = layers[key]

          if (v.legacySpecFlag === "old") {
            customBaseLayers.push({
              clType: "baselayer/nglayer",
              legacySpecFlag: "old",
              id: key,
              ...v
            })
          }

          if (v.legacySpecFlag === "new") {
            customBaseLayers.push({
              legacySpecFlag: "new",
              clType: "baselayer/nglayer",
              id: key,
              ...v
            })
          }

Xiao Gui's avatar
Xiao Gui committed
        }
      }
      return of(
        ...customBaseLayers.map(layer => 
          atlasAppearance.actions.addCustomLayer({
            customLayer: layer
          })  
        )
      )
    })
  ))

Xiao Gui's avatar
Xiao Gui committed
  constructor(
    private store: Store<any>,
    private sapi: SAPI,
  ){}