Newer
Older
import { BehaviorSubject, combineLatest, merge, Observable, Subject, Subscription } from "rxjs";
import { debounceTime, distinctUntilChanged, filter, map, shareReplay, switchMap, tap, withLatestFrom } from "rxjs/operators";
import { IColorMap, INgLayerCtrl, TNgLayerCtrl } from "./layerCtrl.util";
import { IVolumeTypeDetail } from "src/util/siibraApiConstants/types";
import { SAPI, SapiParcellationModel } from "src/atlasComponents/sapi";
import { SAPISpace, SAPIRegion } from "src/atlasComponents/sapi/core";
import { annotation, atlasAppearance, atlasSelection } from "src/state";
import { LayerCtrlEffects } from "./layerCtrl.effects";
export const BACKUP_COLOR = {
red: 255,
green: 255,
blue: 255
}
export class NehubaLayerControlService implements OnDestroy{
static PMAP_LAYER_NAME = 'regional-pmap'
private selectedRegion$ = this.store$.pipe(
private defaultNgLayers$ = this.layerEffects.onATPDebounceNgLayers$
public selectedATPR$ = this.selectedATP$.pipe(
switchMap(({ atlas, template, parcellation }) =>
this.store$.pipe(
select(atlasSelection.selectors.selectedParcAllRegions),
map(regions => ({
atlas, template, parcellation, regions
})),
shareReplay(1)
)
)
)
private activeColorMap$ = combineLatest([
this.selectedATPR$.pipe(
map(({ atlas, parcellation, regions, template }) => {
for (const r of regions) {
if (!r.hasAnnotation) continue
if (!r.hasAnnotation.visualizedIn) continue
const ngId = getParcNgId(atlas, template, parcellation, r)
const [ red, green, blue ] = SAPIRegion.GetDisplayColor(r)
const labelIndex = getRegionLabelIndex(atlas, template, parcellation, r)
if (!labelIndex) continue
if (!returnVal[ngId]) {
returnVal[ngId] = {}
this.defaultNgLayers$.pipe(
map(({ tmplAuxNgLayers }) => {
const returnVal: IColorMap = {}
for (const ngId in tmplAuxNgLayers) {
returnVal[ngId] = {}
const { auxMeshes } = tmplAuxNgLayers[ngId]
for (const auxMesh of auxMeshes) {
const { labelIndicies } = auxMesh
for (const lblIdx of labelIndicies) {
returnVal[ngId][lblIdx] = BACKUP_COLOR
map(([cmParc, cmAux]) => ({
...cmParc,
...cmAux
}))
)
private auxMeshes$: Observable<IAuxMesh[]> = this.selectedATP$.pipe(
map(({ template }) => template),
switchMap(tmpl => this.sapiSvc.registry.get<SAPISpace>(tmpl["@id"]).getVolumes().pipe(
map(
tmplVolumes => {
const auxMeshArr: IAuxMesh[] = []
for (const vol of tmplVolumes) {
if (vol.data.detail["neuroglancer/precompmesh"]) {
const detail = vol.data.detail as IVolumeTypeDetail["neuroglancer/precompmesh"]
for (const auxMesh of detail["neuroglancer/precompmesh"].auxMeshes) {
auxMeshArr.push({
"@id": `auxmesh-${tmpl["@id"]}-${auxMesh.name}`,
labelIndicies: auxMesh.labelIndicies,
name: auxMesh.name,
ngId: '',
rgb: [255, 255, 255],
visible: auxMesh.name !== "Sulci"
})
}
}
}
return auxMeshArr
private sub: Subscription[] = []
ngOnDestroy(){
while (this.sub.length > 0) this.sub.pop().unsubscribe()
}
constructor(
private store$: Store<any>,
/**
* on store showdelin
* toggle parcnglayers visibility
*/
this.store$.pipe(
select(atlasAppearance.selectors.showDelineation),
withLatestFrom(this.defaultNgLayers$)
).subscribe(([flag, { parcNgLayers }]) => {
const layerObj = {}
for (const key in parcNgLayers) {
layerObj[key] = {
visible: flag
}
}
this.manualNgLayersControl$.next({
type: 'update',
payload: layerObj
})
}),
this.ngLayers$.subscribe(({ customLayers }) => {
this.ngLayersRegister = customLayers
})
)
this.sub.push(
this.store$.pipe(
select(atlasAppearance.selectors.customLayers),
map(cl => cl.filter(l => l.clType === "customlayer/colormap").length > 0),
distinctUntilChanged()
).subscribe(flag => {
const pmapLayer = this.ngLayersRegister.find(l => l.id === NehubaLayerControlService.PMAP_LAYER_NAME)
if (!pmapLayer) return
const payload = {
type: 'update',
payload: {
[NehubaLayerControlService.PMAP_LAYER_NAME]: {
visible: !flag
}
}
} as TNgLayerCtrl<'update'>
this.manualNgLayersControl$.next(payload)
})
)
/**
* on custom landmarks loaded, set mesh transparency
*/
this.sub.push(
this.store$.pipe(
withLatestFrom(this.auxMeshes$)
).subscribe(([landmarks, auxMeshes]) => {
const payload: {
[key: string]: number
} = {}
const alpha = landmarks.length > 0
? 0.2
: 1.0
for (const auxMesh of auxMeshes) {
payload[auxMesh.ngId] = alpha
}
this.manualNgLayersControl$.next({
type: 'setLayerTransparency',
payload
})
})
)
}
public activeColorMap: IColorMap
public overwriteColorMap$ = new BehaviorSubject<IColorMap>(null)
public setColorMap$: Observable<IColorMap> = merge(
this.activeColorMap$.pipe(
// TODO this is a dirty fix
// it seems, sometimes, overwritecolormap and activecolormap can emit at the same time
// (e.g. when reg selection changes)
// this ensures that the activecolormap emits later, and thus take effect over overwrite colormap
debounceTime(16),
),
public expectedLayerNames$ = this.defaultNgLayers$.pipe(
map(({ parcNgLayers, tmplAuxNgLayers, tmplNgLayers }) => {
return [
...Object.keys(parcNgLayers),
...Object.keys(tmplAuxNgLayers),
...Object.keys(tmplNgLayers),
]
public visibleLayer$: Observable<string[]> = this.expectedLayerNames$.pipe(
map(expectedLayerNames => {
const ngIdSet = new Set<string>([...expectedLayerNames])
/**
* define when shown segments should be updated
*/
public _segmentVis$: Observable<string[]> = combineLatest([
this.selectedATP$,
this.selectedRegion$
]).pipe(
map(() => [''])
)
public segmentVis$: Observable<string[]> = combineLatest([
/**
* selectedRegions
*/
/**
* if layer contains non mixable layer
*/
this.store$.pipe(
select(atlasAppearance.selectors.customLayers),
map(layers => layers.filter(l => l.clType === "customlayer/nglayer").length > 0),
map(([[ regions, nonmixableLayerExists ], { atlas, parcellation, template }]) => {
if (nonmixableLayerExists) {
return null
}
/* selectedregionindexset needs to be updated regardless of forceshowsegment */
const selectedRegionIndexSet = new Set<string>(
regions.map(r => {
const ngId = getParcNgId(atlas, template, parcellation, r)
const label = getRegionLabelIndex(atlas, template, parcellation, r)
return serializeSegment(ngId, label)
})
)
return [...selectedRegionIndexSet]
} else {
return []
}
})
)
/**
* ngLayers controller
*/
private ngLayersRegister: atlasAppearance.NgLayerCustomLayer[] = []
private ngLayers$ = this.store$.pipe(
select(atlasAppearance.selectors.customLayers),
map(customLayers => customLayers.filter(l => l.clType === "customlayer/nglayer") as atlasAppearance.NgLayerCustomLayer[]),
map(customLayers => {
const newLayers = customLayers.filter(l => {
const registeredLayerNames = this.ngLayersRegister.map(l => l.id)
return !registeredLayerNames.includes(l.id)
const removeLayers = this.ngLayersRegister.filter(l => {
const stateLayerNames = customLayers.map(l => l.id)
return !stateLayerNames.includes(l.id)
return { newLayers, removeLayers, customLayers }
}),
shareReplay(1)
)
private manualNgLayersControl$ = new Subject<TNgLayerCtrl<keyof INgLayerCtrl>>()
ngLayersController$: Observable<TNgLayerCtrl<keyof INgLayerCtrl>> = merge(
this.ngLayers$.pipe(
map(({ newLayers }) => newLayers),
filter(layers => layers.length > 0),
map(newLayers => {
const newLayersObj: any = {}
newLayers.forEach(({ id, source, ...rest }) => newLayersObj[id] = {
...rest,
source,
})
return {
type: 'add',
payload: newLayersObj
} as TNgLayerCtrl<'add'>
})
),
this.ngLayers$.pipe(
map(({ removeLayers }) => removeLayers),
filter(layers => layers.length > 0),
map(removeLayers => {
const removeLayerNames = removeLayers.map(v => v.id)
return {
type: 'remove',
payload: { names: removeLayerNames }
} as TNgLayerCtrl<'remove'>
})
),
this.manualNgLayersControl$,
).pipe(
)