diff --git a/angular.json b/angular.json index 381d8126cb58142d0ab49b4dabda5cd8ad15124d..0cd29585665857860e19b5f64ad44a737dbc86c2 100644 --- a/angular.json +++ b/angular.json @@ -1,5 +1,8 @@ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "cli": { + "analytics": false + }, "version": 1, "newProjectRoot": "projects", "projects": { diff --git a/e2e/checklist.md b/e2e/checklist.md index 7577819cb3cf150a1fc8588a165c9224b57290c0..a42223294dd298cae6f7cbe080466def3f807be7 100644 --- a/e2e/checklist.md +++ b/e2e/checklist.md @@ -48,4 +48,11 @@ - [ ] high res hoc1, hoc2, hoc3, lam1-6 are visible - [ ] Waxholm - [ ] v4 are visible - - [ ] on hover, show correct region name(s) \ No newline at end of file + - [ ] on hover, show correct region name(s) + +## saneURL +- [ ] [saneUrl](https://siibra-explorer.apps.hbp.eu/staging/saneUrl/bigbrainGreyWhite) redirects to big brain +- [ ] [saneUrl](https://siibra-explorer.apps.hbp.eu/staging/saneUrl/julichbrain) redirects to julich brain (colin 27) +- [ ] [saneUrl](https://siibra-explorer.apps.hbp.eu/staging/saneUrl/whs4) redirects to waxholm v4 +- [ ] [saneUrl](https://siibra-explorer.apps.hbp.eu/staging/saneUrl/allen2017) redirects to allen 2017 +- [ ] [saneUrl](https://siibra-explorer.apps.hbp.eu/staging/saneUrl/mebrains) redirects to monkey diff --git a/src/util/pureConstant.service.ts b/src/util/pureConstant.service.ts index f0c01ff0755e005bc28eb68eb8ff2b9ef5c0df16..cc8e857a378d6b1c2a000a041f8055e8be66f0f8 100644 --- a/src/util/pureConstant.service.ts +++ b/src/util/pureConstant.service.ts @@ -56,6 +56,20 @@ type TIAVAtlas = { } & THasId)[] } & THasId +type TNehubaConfig = Record<string, { + source: string + transform: number[][] + type: 'segmentation' | 'image' +}> + +type TViewerConfig = TNehubaConfig + +/** + * key value pair of + * atlasId -> templateId -> viewerConfig + */ +type TAtlasTmplViewerConfig = Record<string, Record<string, TViewerConfig>> + export const spaceMiscInfoMap = new Map([ ['minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588', { name: 'bigbrain', @@ -515,12 +529,20 @@ Raise/track issues at github repo: <a target = "_blank" href = "${this.repoUrl}" shareReplay(1) ) + private atlasTmplConfig: TAtlasTmplViewerConfig = {} + + async getViewerConfig(atlasId: string, templateId: string, parcId: string) { + const atlasLayers = this.atlasTmplConfig[atlasId] + const templateLayers = atlasLayers && atlasLayers[templateId] + return templateLayers || {} + } + public initFetchTemplate$ = this.fetchedAtlases$.pipe( switchMap(atlases => { return forkJoin( atlases.map(atlas => this.getSpacesAndParc(atlas['@id']).pipe( switchMap(({ templateSpaces, parcellations }) => { - const ngLayerObj = {} + this.atlasTmplConfig[atlas["@id"]] = {} return forkJoin( templateSpaces.map( tmpl => { @@ -535,7 +557,7 @@ Raise/track issues at github repo: <a target = "_blank" href = "${this.repoUrl}" name: 'Julich-Brain Probabilistic Cytoarchitectonic Maps (v2.9)' }) } - ngLayerObj[tmpl.id] = {} + this.atlasTmplConfig[atlas["@id"]][tmpl.id] = {} return tmpl.availableParcellations.map( parc => this.getRegions(atlas['@id'], parc.id, tmpl.id).pipe( tap(regions => { @@ -558,7 +580,7 @@ Raise/track issues at github repo: <a target = "_blank" href = "${this.repoUrl}" const ngId = getNgId(atlas['@id'], tmpl.id, parc.id, dedicatedMap[0]['@id']) region['ngId'] = ngId region['labelIndex'] = dedicatedMap[0].detail['neuroglancer/precomputed'].labelIndex - ngLayerObj[tmpl.id][ngId] = { + this.atlasTmplConfig[atlas["@id"]][tmpl.id][ngId] = { source: `precomputed://${dedicatedMap[0].url}`, type: "segmentation", transform: dedicatedMap[0].detail['neuroglancer/precomputed'].transform @@ -622,7 +644,7 @@ Raise/track issues at github repo: <a target = "_blank" href = "${this.repoUrl}" const key = 'whole brain' const ngIdKey = getNgId(atlas['@id'], tmpl.id, parseId(parc.id), key) - ngLayerObj[tmpl.id][ngIdKey] = { + this.atlasTmplConfig[atlas["@id"]][tmpl.id][ngIdKey] = { source: `precomputed://${vol.url}`, type: "segmentation", transform: vol.detail['neuroglancer/precomputed'].transform @@ -639,7 +661,7 @@ Raise/track issues at github repo: <a target = "_blank" href = "${this.repoUrl}" }] for (const { key, mapIndex } of mapIndexKey) { const ngIdKey = getNgId(atlas['@id'], tmpl.id, parseId(parc.id), key) - ngLayerObj[tmpl.id][ngIdKey] = { + this.atlasTmplConfig[atlas["@id"]][tmpl.id][ngIdKey] = { source: `precomputed://${precomputedVols[mapIndex].url}`, type: "segmentation", transform: precomputedVols[mapIndex].detail['neuroglancer/precomputed'].transform @@ -660,7 +682,7 @@ Raise/track issues at github repo: <a target = "_blank" href = "${this.repoUrl}" } ).reduce(flattenReducer, []) ).pipe( - mapTo({ templateSpaces, parcellations, ngLayerObj }) + mapTo({ templateSpaces, parcellations, ngLayerObj: this.atlasTmplConfig }) ) }), map(({ templateSpaces, parcellations, ngLayerObj }) => { @@ -796,8 +818,8 @@ Raise/track issues at github repo: <a target = "_blank" href = "${this.repoUrl}" } } - for (const key in (ngLayerObj[tmpl.id] || {})) { - initialLayers[key] = ngLayerObj[tmpl.id][key] + for (const key in (ngLayerObj[atlas["@id"]][tmpl.id] || {})) { + initialLayers[key] = ngLayerObj[atlas["@id"]][tmpl.id][key] } return { diff --git a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts index 44a1fb490ccdfeb69020a48fa9a25154ef92a9dd..54b6e08fa3d70dea75042b7372251e8be51e2de7 100644 --- a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts +++ b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts @@ -7,7 +7,7 @@ import { ComponentsModule } from "src/components" import { ngViewerSelectorOctantRemoval } from "src/services/state/ngViewerState.store.helper" import { viewerStateCustomLandmarkSelector, viewerStateSelectedTemplatePureSelector } from "src/services/state/viewerState/selectors" import { AngularMaterialModule } from "src/sharedModules" -import { UtilModule } from "src/util" +import {PureContantService, UtilModule} from "src/util" import { actionSetAuxMeshes, selectorAuxMeshes } from "../../store" import { NEHUBA_INSTANCE_INJTKN } from "../../util" import { ViewerCtrlCmp } from "./viewerCtrlCmp.component" @@ -20,12 +20,31 @@ describe('> viewerCtrlCmp.component.ts', () => { let fixture: ComponentFixture<ViewerCtrlCmp> let loader: HarnessLoader let mockStore: MockStore + let mockNehubaViewer = { - updateUserLandmarks: jasmine.createSpy() + updateUserLandmarks: jasmine.createSpy(), + nehubaViewer: { + ngviewer: { + layerManager: { + getLayerByName: jasmine.createSpy('getLayerByName'), + get managedLayers() { + return [] + }, + set managedLayers(val) { + return + } + }, + display: { + scheduleRedraw: jasmine.createSpy('scheduleRedraw') + } + } + } } afterEach(() => { mockNehubaViewer.updateUserLandmarks.calls.reset() + mockNehubaViewer.nehubaViewer.ngviewer.layerManager.getLayerByName.calls.reset() + mockNehubaViewer.nehubaViewer.ngviewer.display.scheduleRedraw.calls.reset() }) beforeEach( async () => { @@ -43,7 +62,17 @@ describe('> viewerCtrlCmp.component.ts', () => { provideMockStore(), { provide: NEHUBA_INSTANCE_INJTKN, - useValue: new BehaviorSubject(mockNehubaViewer) + useFactory: () => { + return new BehaviorSubject(mockNehubaViewer).asObservable() + } + }, + { + provide: PureContantService, + useFactory: () => { + return { + getViewerConfig: jasmine.createSpy('getViewerConfig') + } + } } ] }).compileComponents() @@ -54,12 +83,12 @@ describe('> viewerCtrlCmp.component.ts', () => { mockStore.overrideSelector(viewerStateSelectedTemplatePureSelector, {}) mockStore.overrideSelector(ngViewerSelectorOctantRemoval, true) mockStore.overrideSelector(viewerStateCustomLandmarkSelector, []) + mockStore.overrideSelector(selectorAuxMeshes, []) }) describe('> can be init', () => { beforeEach(() => { - mockStore.overrideSelector(selectorAuxMeshes, []) fixture = TestBed.createComponent(ViewerCtrlCmp) fixture.detectChanges() loader = TestbedHarnessEnvironment.loader(fixture) @@ -207,5 +236,120 @@ describe('> viewerCtrlCmp.component.ts', () => { ) }) }) + + describe('> flagDelin', () => { + let toggleParcVsblSpy: jasmine.Spy + beforeEach(() => { + fixture = TestBed.createComponent(ViewerCtrlCmp) + toggleParcVsblSpy = spyOn(fixture.componentInstance as any, 'toggleParcVsbl') + fixture.detectChanges() + }) + it('> calls toggleParcVsbl', () => { + toggleParcVsblSpy.and.callFake(() => {}) + fixture.componentInstance.flagDelin = false + expect(toggleParcVsblSpy).toHaveBeenCalled() + }) + }) + describe('> toggleParcVsbl', () => { + let getViewerConfigSpy: jasmine.Spy + let getLayerByNameSpy: jasmine.Spy + beforeEach(() => { + const pureCstSvc = TestBed.inject(PureContantService) + getLayerByNameSpy = mockNehubaViewer.nehubaViewer.ngviewer.layerManager.getLayerByName + getViewerConfigSpy = pureCstSvc.getViewerConfig as jasmine.Spy + fixture = TestBed.createComponent(ViewerCtrlCmp) + fixture.detectChanges() + }) + + it('> calls pureSvc.getViewerConfig', async () => { + getViewerConfigSpy.and.returnValue({}) + await fixture.componentInstance['toggleParcVsbl']() + expect(getViewerConfigSpy).toHaveBeenCalled() + }) + + describe('> if _flagDelin is true', () => { + beforeEach(() => { + fixture.componentInstance['_flagDelin'] = true + fixture.componentInstance['hiddenLayerNames'] = [ + 'foo', + 'bar', + 'baz' + ] + }) + it('> go through all hideen layer names and set them to true', async () => { + const setVisibleSpy = jasmine.createSpy('setVisible') + getLayerByNameSpy.and.returnValue({ + setVisible: setVisibleSpy + }) + await fixture.componentInstance['toggleParcVsbl']() + expect(getLayerByNameSpy).toHaveBeenCalledTimes(3) + for (const arg of ['foo', 'bar', 'baz']) { + expect(getLayerByNameSpy).toHaveBeenCalledWith(arg) + } + expect(setVisibleSpy).toHaveBeenCalledTimes(3) + expect(setVisibleSpy).toHaveBeenCalledWith(true) + expect(setVisibleSpy).not.toHaveBeenCalledWith(false) + }) + it('> hiddenLayerNames resets', async () => { + await fixture.componentInstance['toggleParcVsbl']() + expect(fixture.componentInstance['hiddenLayerNames']).toEqual([]) + }) + }) + + describe('> if _flagDelin is false', () => { + let managedLayerSpyProp: jasmine.Spy + let setVisibleSpy: jasmine.Spy + beforeEach(() => { + fixture.componentInstance['_flagDelin'] = false + setVisibleSpy = jasmine.createSpy('setVisible') + getLayerByNameSpy.and.returnValue({ + setVisible: setVisibleSpy + }) + getViewerConfigSpy.and.resolveTo({ + 'foo': {}, + 'bar': {}, + 'baz': {} + }) + managedLayerSpyProp = spyOnProperty(mockNehubaViewer.nehubaViewer.ngviewer.layerManager, 'managedLayers') + managedLayerSpyProp.and.returnValue([{ + visible: true, + name: 'foo' + }, { + visible: false, + name: 'bar' + }, { + visible: true, + name: 'baz' + }]) + }) + + afterEach(() => { + managedLayerSpyProp.calls.reset() + }) + + it('> calls schedulRedraw', async () => { + await fixture.componentInstance['toggleParcVsbl']() + await new Promise(rs => requestAnimationFrame(rs)) + expect(mockNehubaViewer.nehubaViewer.ngviewer.display.scheduleRedraw).toHaveBeenCalled() + }) + + it('> only calls setVisible false on visible layers', async () => { + await fixture.componentInstance['toggleParcVsbl']() + expect(getLayerByNameSpy).toHaveBeenCalledTimes(2) + + for (const arg of ['foo', 'baz']) { + expect(getLayerByNameSpy).toHaveBeenCalledWith(arg) + } + expect(setVisibleSpy).toHaveBeenCalledTimes(2) + expect(setVisibleSpy).toHaveBeenCalledWith(false) + expect(setVisibleSpy).not.toHaveBeenCalledWith(true) + }) + + it('> sets hiddenLayerNames correctly', async () => { + await fixture.componentInstance['toggleParcVsbl']() + expect(fixture.componentInstance['hiddenLayerNames']).toEqual(['foo', 'baz']) + }) + }) + }) }) }) diff --git a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.ts b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.ts index cbe0380a6c5a06350b7ec29287072094343a25c2..dad94866475668c3e8677a37d9e7fd5e2b36e87a 100644 --- a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.ts +++ b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.ts @@ -1,15 +1,16 @@ import { Component, HostBinding, Inject, Optional } from "@angular/core"; import { select, Store } from "@ngrx/store"; import { combineLatest, merge, Observable, of, Subscription } from "rxjs"; -import { filter, map, pairwise, withLatestFrom } from "rxjs/operators"; +import {filter, map, pairwise, withLatestFrom} from "rxjs/operators"; import { ngViewerActionSetPerspOctantRemoval } from "src/services/state/ngViewerState/actions"; import { ngViewerSelectorOctantRemoval } from "src/services/state/ngViewerState/selectors"; -import { viewerStateCustomLandmarkSelector, viewerStateSelectedTemplatePureSelector } from "src/services/state/viewerState/selectors"; +import { viewerStateCustomLandmarkSelector, viewerStateGetSelectedAtlas, viewerStateSelectedTemplatePureSelector } from "src/services/state/viewerState/selectors"; import { NehubaViewerUnit } from "src/viewerModule/nehuba"; import { NEHUBA_INSTANCE_INJTKN } from "src/viewerModule/nehuba/util"; import { ARIA_LABELS } from 'common/constants' import { actionSetAuxMeshes, selectorAuxMeshes } from "../../store"; import { FormBuilder, FormControl, FormGroup } from "@angular/forms"; +import {PureContantService} from "src/util"; @Component({ selector: 'viewer-ctrl-component', @@ -27,6 +28,9 @@ export class ViewerCtrlCmp{ @HostBinding('attr.darktheme') darktheme = false + private selectedAtlasId: string + private selectedTemplateId: string + private _flagDelin = true get flagDelin(){ return this._flagDelin @@ -68,9 +72,16 @@ export class ViewerCtrlCmp{ select(selectorAuxMeshes), ) + private nehubaInst: NehubaViewerUnit + + get ngViewer() { + return this.nehubaInst?.nehubaViewer.ngviewer || (window as any).viewer + } + constructor( private store$: Store<any>, formBuilder: FormBuilder, + private pureConstantService: PureContantService, @Optional() @Inject(NEHUBA_INSTANCE_INJTKN) private nehubaInst$: Observable<NehubaViewerUnit>, ){ @@ -83,20 +94,25 @@ export class ViewerCtrlCmp{ this.customLandmarks$, this.nehubaInst$, ]).pipe( - filter(([_, neubaInst]) => !!neubaInst), + filter(([_, nehubaInst]) => !!nehubaInst), ).subscribe(([landmarks, nehubainst]) => { this.setOctantRemoval(landmarks.length === 0) nehubainst.updateUserLandmarks(landmarks) - }) + }), + this.nehubaInst$.subscribe(nehubaInst => this.nehubaInst = nehubaInst) ) } else { console.warn(`NEHUBA_INSTANCE_INJTKN not provided`) } this.sub.push( + this.store$.select(viewerStateGetSelectedAtlas) + .pipe(filter(a => !!a)) + .subscribe(sa => this.selectedAtlasId = sa['@id']), this.store$.pipe( select(viewerStateSelectedTemplatePureSelector) ).subscribe(tmpl => { + this.selectedTemplateId = tmpl['@id'] const { useTheme } = tmpl || {} this.darktheme = useTheme === 'dark' }), @@ -152,29 +168,32 @@ export class ViewerCtrlCmp{ ) } - private toggleParcVsbl(){ - const visibleParcLayers = ((window as any).viewer.layerManager.managedLayers) - .slice(1) - .filter(({ visible }) => visible) - .filter(layer => !this.auxMeshesNamesSet.has(layer.name)) + private async toggleParcVsbl(){ + const viewerConfig = await this.pureConstantService.getViewerConfig(this.selectedAtlasId, this.selectedTemplateId, null) if (this.flagDelin) { for (const name of this.hiddenLayerNames) { - const l = (window as any).viewer.layerManager.getLayerByName(name) + const l = this.ngViewer.layerManager.getLayerByName(name) l && l.setVisible(true) } this.hiddenLayerNames = [] } else { this.hiddenLayerNames = [] - for (const { name } of visibleParcLayers) { - const l = (window as any).viewer.layerManager.getLayerByName(name) + const segLayerNames: string[] = [] + for (const layer of this.ngViewer.layerManager.managedLayers) { + if (layer.visible && layer.name in viewerConfig) { + segLayerNames.push(layer.name) + } + } + for (const name of segLayerNames) { + const l = this.ngViewer.layerManager.getLayerByName(name) l && l.setVisible(false) this.hiddenLayerNames.push( name ) } } - - setTimeout(() => { - (window as any).viewer.display.scheduleRedraw() + + requestAnimationFrame(() => { + this.ngViewer.display.scheduleRedraw() }) }