import { Injectable } from "@angular/core"; import { createEffect } from "@ngrx/effects"; import { select, Store } from "@ngrx/store"; import { forkJoin, from, of } from "rxjs"; import { mapTo, switchMap, withLatestFrom, filter, catchError, map, debounceTime, shareReplay, distinctUntilChanged, startWith, pairwise, tap } from "rxjs/operators"; import { NgSegLayerSpec, SxplrAtlas, SxplrParcellation, SxplrTemplate } from "src/atlasComponents/sapi/type_sxplr"; import { SAPI } from "src/atlasComponents/sapi" import { SapiFeatureModel, SapiSpatialFeatureModel, } from "src/atlasComponents/sapi/type_v3"; import { translateV3Entities } from "src/atlasComponents/sapi/translate_v3" import { atlasAppearance, atlasSelection, userInteraction } from "src/state"; import { arrayEqual } from "src/util/array"; import { EnumColorMapName } from "src/util/colorMaps"; import { getShader } from "src/util/constants"; import { PMAP_LAYER_NAME } from "../constants"; import { QuickHash } from "src/util/fn"; import { BaseService } from "../base.service/base.service"; import { getParcNgId } from "../config.service"; @Injectable() export class LayerCtrlEffects { static TransformVolumeModel(volumeModel: SapiSpatialFeatureModel['volume']): atlasAppearance.NgLayerCustomLayer[] { /** * TODO implement */ throw new Error(`IMPLEMENT ME`) for (const volumeFormat in volumeModel.providedVolumes) { } return [] } onRegionSelectClearPmapLayer = createEffect(() => this.store.pipe( select(atlasSelection.selectors.selectedRegions), distinctUntilChanged( arrayEqual((o, n) => o["@id"] === n["@id"]) ), mapTo( atlasAppearance.actions.removeCustomLayer({ id: PMAP_LAYER_NAME }) ) )) onRegionSelectShowNewPmapLayer = createEffect(() => this.store.pipe( select(atlasSelection.selectors.selectedRegions), filter(regions => regions.length > 0), withLatestFrom( this.store.pipe( atlasSelection.fromRootStore.distinctATP() ) ), switchMap(([ regions, { atlas, parcellation, template } ]) => { const sapiRegion = this.sapi.getRegion(atlas["@id"], parcellation["@id"], regions[0].name) return forkJoin([ sapiRegion.getMapInfo(template["@id"]), sapiRegion.getMapUrl(template["@id"]) ]).pipe( map(([mapInfo, mapUrl]) => atlasAppearance.actions.addCustomLayer({ customLayer: { clType: "customlayer/nglayer", id: PMAP_LAYER_NAME, source: `nifti://${mapUrl}`, shader: getShader({ colormap: EnumColorMapName.VIRIDIS, highThreshold: mapInfo.max, lowThreshold: mapInfo.min, removeBg: true, }) } }) ), catchError(() => of( atlasAppearance.actions.removeCustomLayer({ id: PMAP_LAYER_NAME }) )) ) }), )) onATP$ = this.store.pipe( atlasSelection.fromRootStore.distinctATP(), map(val => val as { atlas: SxplrAtlas, parcellation: SxplrParcellation, template: SxplrTemplate }), ) onShownFeature = createEffect(() => this.store.pipe( select(userInteraction.selectors.selectedFeature), startWith(null as SapiFeatureModel), pairwise<SapiFeatureModel>(), map(([ prev, curr ]) => { const removeLayers: atlasAppearance.NgLayerCustomLayer[] = [] const addLayers: atlasAppearance.NgLayerCustomLayer[] = [] if (prev?.["@type"].includes("feature/volume_of_interest")) { const prevVoi = prev as SapiSpatialFeatureModel removeLayers.push( ...LayerCtrlEffects.TransformVolumeModel(prevVoi.volume) ) } if (curr?.["@type"].includes("feature/volume_of_interest")) { const currVoi = curr as SapiSpatialFeatureModel addLayers.push( ...LayerCtrlEffects.TransformVolumeModel(currVoi.volume) ) } return { removeLayers, addLayers } }), filter(({ removeLayers, addLayers }) => removeLayers.length !== 0 || addLayers.length !== 0), switchMap(({ removeLayers, addLayers }) => of(...[ ...removeLayers.map( l => atlasAppearance.actions.removeCustomLayer({ id: l.id }) ), ...addLayers.map( l => atlasAppearance.actions.addCustomLayer({ customLayer: l }) ) ])) )) onATPClearBaseLayers = createEffect(() => this.onATP$.pipe( withLatestFrom( this.store.pipe( select(atlasAppearance.selectors.customLayers), map( cl => cl.filter(layer => layer.clType === "baselayer/nglayer" || layer.clType === "customlayer/nglayer" ) ) ) ), switchMap(([_, layers]) => of( ...layers.map(layer => atlasAppearance.actions.removeCustomLayer({ id: layer.id }) ) ) ) )) onATPDebounceNgLayers$ = this.onATP$.pipe( debounceTime(16), switchMap(({ atlas, template, parcellation }) => forkJoin({ tmplNgLayers: this.sapi.getVoxelTemplateImage(template).pipe( map(templateImages => { const returnObj = {} for (const img of templateImages) { returnObj[QuickHash.GetHash(img.source)] = img } return returnObj }) ), tmplAuxNgLayers: this.sapi.getVoxelAuxMesh(template).pipe( map(auxMeshes => { const returnObj = {} for (const img of auxMeshes) { returnObj[QuickHash.GetHash(`${img.source}_auxMesh`)] = img } return returnObj }) ), parcNgLayers: from(this.sapi.getTranslatedLabelledNgMap(parcellation, template)).pipe( map(val => { const returnVal: Record<string, NgSegLayerSpec> = {} for (const [ url, { layer, region } ] of Object.entries(val)) { const { name } = region[0] const ngId = getParcNgId(atlas, template, parcellation, { id: '', name, parentIds: [] }) returnVal[ngId] = layer continue } return returnVal }) ) }) ), shareReplay(1), ) onATPDebounceAddBaseLayers$ = createEffect(() => this.onATPDebounceNgLayers$.pipe( switchMap(ngLayers => { const { parcNgLayers, tmplAuxNgLayers, tmplNgLayers } = ngLayers const customBaseLayers: atlasAppearance.NgLayerCustomLayer[] = [] for (const layers of [parcNgLayers, tmplAuxNgLayers, tmplNgLayers]) { for (const key in layers) { const { source, transform, opacity, visible } = layers[key] customBaseLayers.push({ clType: "baselayer/nglayer", id: key, source, transform, opacity, visible, }) } } return of( ...customBaseLayers.map(layer => atlasAppearance.actions.addCustomLayer({ customLayer: layer }) ) ) }) )) constructor( private store: Store<any>, private sapi: SAPI, private baseService: BaseService, ){} }