diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/constants.ts b/src/atlasComponents/regionalFeatures/bsFeatures/constants.ts index 4bbc8df0f28b6f595f8e6e2c83c29fe2d2daa5b9..1d2f42a5cae77b2a96e8ac993975edbec3458d6f 100644 --- a/src/atlasComponents/regionalFeatures/bsFeatures/constants.ts +++ b/src/atlasComponents/regionalFeatures/bsFeatures/constants.ts @@ -2,8 +2,7 @@ import { InjectionToken } from "@angular/core"; import { Observable } from "rxjs"; import { IHasId } from "src/util/interfaces"; import { TBSDetail, TBSSummary } from "./receptor/type"; - -export const BS_ENDPOINT = new InjectionToken<string>('BS_ENDPOINT') +export { BS_ENDPOINT } from 'src/util/constants' export type TRegion = { name: string diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/module.ts b/src/atlasComponents/regionalFeatures/bsFeatures/module.ts index f4f16b8fa2c84a6c607dd8e25506bb9812c9bd47..185a3387ceb4748baba29d1de4cf4491e027d6a2 100644 --- a/src/atlasComponents/regionalFeatures/bsFeatures/module.ts +++ b/src/atlasComponents/regionalFeatures/bsFeatures/module.ts @@ -1,6 +1,5 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { BS_ENDPOINT } from "./constants"; import { BSFeatureReceptorModule } from "./receptor"; import { BsFeatureService } from "./service"; @@ -10,10 +9,6 @@ import { BsFeatureService } from "./service"; BSFeatureReceptorModule, ], providers: [ - { - provide: BS_ENDPOINT, - useValue: BS_REST_URL || `https://brainscapes.apps-dev.hbp.eu` - }, BsFeatureService ], exports: [ diff --git a/src/atlasComponents/splashScreen/splashScreen/splashScreen.component.ts b/src/atlasComponents/splashScreen/splashScreen/splashScreen.component.ts index d70b2ba321c9ab90d0a50ea28c2190d318f433c6..b734cf054cedd9a57ae9b0aa48280607ce20c15a 100644 --- a/src/atlasComponents/splashScreen/splashScreen/splashScreen.component.ts +++ b/src/atlasComponents/splashScreen/splashScreen/splashScreen.component.ts @@ -30,7 +30,6 @@ export class SplashScreen implements AfterViewInit { constructor( private store: Store<IavRootStoreInterface>, - private constanceService: AtlasViewerConstantsServices, private pureConstantService: PureContantService ) { this.loadedTemplate$ = this.store.pipe( @@ -91,10 +90,6 @@ export class SplashScreen implements AfterViewInit { }) ) } - - get totalTemplates() { - return this.constanceService.templateUrls.length - } } @Pipe({ diff --git a/src/atlasViewer/atlasViewer.constantService.service.ts b/src/atlasViewer/atlasViewer.constantService.service.ts index b9484fd0f400b2d18208595d2ca25db0be74bcd3..bf9b870c4f6a6b5d4001dec3ed341b849f2b18a3 100644 --- a/src/atlasViewer/atlasViewer.constantService.service.ts +++ b/src/atlasViewer/atlasViewer.constantService.service.ts @@ -26,12 +26,6 @@ export class AtlasViewerConstantsServices implements OnDestroy { // instead of using window.location.href, which includes query param etc public backendUrl = (BACKEND_URL && `${BACKEND_URL}/`.replace(/\/\/$/, '/')) || `${window.location.origin}${window.location.pathname}` - public totalTemplates = null - - public getTemplateEndpoint$ = this.http.get(`${this.backendUrl}templates`, { responseType: 'json' }).pipe( - shareReplay(1) - ) - public templateUrls = Array(100) /* to be provided by KG in future */ @@ -217,10 +211,7 @@ Raise/track issues at github repo: <a target = "_blank" href = "${this.repoUrl}" this.dissmissUserLayerSnackbarMessage = this.dissmissUserLayerSnackbarMessageDesktop } }), - ), - this.pureConstantService.getTemplateEndpoint$.subscribe(arr => { - this.totalTemplates = arr.length - }) + ) } private subscriptions: Subscription[] = [] diff --git a/src/main.module.ts b/src/main.module.ts index f4a8fcaa08008d4069692958c6c32adf8735037d..887448d74a2d356a03804a08b9a98e55743a4a6a 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -62,6 +62,7 @@ import { KgTosModule } from './ui/kgtos/module'; import { MouseoverModule } from './mouseoverModule/mouseover.module'; import { AtlasViewerRouterModule } from './routerModule'; import { MessagingGlue } from './messagingGlue'; +import { BS_ENDPOINT } from './util/constants'; export function debug(reducer: ActionReducer<any>): ActionReducer<any> { return function(state, action) { @@ -254,7 +255,11 @@ export function debug(reducer: ActionReducer<any>): ActionReducer<any> { { provide: WINDOW_MESSAGING_HANDLER_TOKEN, useClass: MessagingGlue - } + }, + { + provide: BS_ENDPOINT, + useValue: (BS_REST_URL || `https://brainscapes.apps-dev.hbp.eu`).replace(/\/$/, '') + }, ], bootstrap : [ AtlasViewer, diff --git a/src/util/constants.ts b/src/util/constants.ts index 0236f4e41a896222c77208711a82b836d7f18aae..773b99b8bd254b0305697777f5e700bd06bb1584 100644 --- a/src/util/constants.ts +++ b/src/util/constants.ts @@ -115,3 +115,4 @@ export const compareLandmarksChanged: (prevLandmarks: any[], newLandmarks: any[] } export const CYCLE_PANEL_MESSAGE = `[spacebar] to cycle through views` +export const BS_ENDPOINT = new InjectionToken<string>('BS_ENDPOINT') diff --git a/src/util/fn.ts b/src/util/fn.ts index 4f673a08e5b2ace779f13d8994d68965687688af..96369e7dd63f67c9060161b122014dd8866d7be4 100644 --- a/src/util/fn.ts +++ b/src/util/fn.ts @@ -83,3 +83,40 @@ export function switchMapWaitFor(opts: ISwitchMapWaitFor){ mapTo(arg) ) } + +export class MultiDimMap{ + + private map = new Map() + + static GetKey(...arg: any[]){ + let mapKey = `` + for (let i = 0; i < arg.length; i++) { + mapKey += arg[i] + } + return mapKey + } + + set(...arg: any[]) { + const mapKey = MultiDimMap.GetKey(...(arg.slice(0, -1))) + this.map.set(mapKey, arg[arg.length - 1]) + } + get(...arg: any[]) { + const mapKey = MultiDimMap.GetKey(...arg) + return this.map.get(mapKey) + } + delete(...arg: any[]) { + const mapKey = MultiDimMap.GetKey(...arg) + return this.map.delete(mapKey) + } +} + +export function recursiveMutate<T>(arr: T[], getChildren: (obj: T) => T[], mutateFn: (obj: T) => void){ + for (const obj of arr) { + mutateFn(obj) + recursiveMutate( + getChildren(obj), + getChildren, + mutateFn + ) + } +} diff --git a/src/util/pureConstant.service.ts b/src/util/pureConstant.service.ts index 6bd40e68d982defb8905191cc554c84659028b96..7224fd7f95bdf4246afd8873ff85d3015ae7c67b 100644 --- a/src/util/pureConstant.service.ts +++ b/src/util/pureConstant.service.ts @@ -1,15 +1,142 @@ -import { Injectable, OnDestroy } from "@angular/core"; +import { Inject, Injectable, OnDestroy } from "@angular/core"; import { Store, select } from "@ngrx/store"; import { Observable, merge, Subscription, of, forkJoin, fromEvent, combineLatest, timer } from "rxjs"; import { viewerConfigSelectorUseMobileUi } from "src/services/state/viewerConfig.store.helper"; -import { shareReplay, tap, scan, catchError, filter, switchMap, map, take, distinctUntilChanged } from "rxjs/operators"; +import { shareReplay, tap, scan, catchError, filter, switchMap, map, take, distinctUntilChanged, mapTo } from "rxjs/operators"; import { HttpClient } from "@angular/common/http"; import { viewerStateFetchedTemplatesSelector, viewerStateSetFetchedAtlases } from "src/services/state/viewerState.store.helper"; import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service"; import { LoggingService } from "src/logging"; import { viewerStateFetchedAtlasesSelector } from "src/services/state/viewerState/selectors"; +import { BS_ENDPOINT } from "src/util/constants"; +import { flattenReducer } from 'common/util' +import { TAtlas, TId, TParc, TRegion, TSpaceFull, TSpaceSummary } from "./siibraApiConstants/types"; +import { MultiDimMap, recursiveMutate } from "./fn"; -const getUniqueId = () => Math.round(Math.random() * 1e16).toString(16) +function parseId(id: TId){ + if (typeof id === 'string') return id + return `${id.kg.kgSchema}/${id.kg.kgId}` +} + +type THasId = { + ['@id']: string + name: string +} + +type TIAVAtlas = { + templateSpaces: ({ availableIn: THasId[] } & THasId)[] + parcellations: ({ + availableIn: THasId[] + baseLayer: boolean + '@version': { + name: string + '@next': string + '@previous': string + '@this': string + } + } & THasId)[] +} & THasId + +function getNehubaConfig(darkTheme: boolean) { + const backgrd = darkTheme + ? [0,0,0,1] + : [1,1,1,1] + + const rmPsp = darkTheme + ? {"mode":"<","color":[0.1,0.1,0.1,1]} + :{"color":[1,1,1,1],"mode":"=="} + const drawSubstrates = darkTheme + ? {"color":[0.5,0.5,1,0.2]} + : {"color":[0,0,0.5,0.15]} + const drawZoomLevels = darkTheme + ? {"cutOff":150000} + : {"cutOff":200000,"color":[0.5,0,0,0.15] } + + return { + "configName": "", + "globals": { + "hideNullImageValues": true, + "useNehubaLayout": { + "keepDefaultLayouts": false + }, + "useNehubaMeshLayer": true, + "rightClickWithCtrlGlobal": false, + "zoomWithoutCtrlGlobal": false, + "useCustomSegmentColors": true + }, + "zoomWithoutCtrl": true, + "hideNeuroglancerUI": true, + "rightClickWithCtrl": true, + "rotateAtViewCentre": true, + "enableMeshLoadingControl": true, + "zoomAtViewCentre": true, + "restrictUserNavigation": true, + "disableSegmentSelection": false, + "dataset": { + "imageBackground": backgrd, + "initialNgState": { + "showDefaultAnnotations": false, + "layers": {}, + // "navigation": { + // "pose": { + // "position": { + // "voxelSize": [ + // 21166.666015625, + // 20000, + // 21166.666015625 + // ], + // "voxelCoordinates": [ + // -21.8844051361084, + // 16.288618087768555, + // 28.418994903564453 + // ] + // } + // }, + // "zoomFactor": 350000 + // }, + "perspectiveOrientation": [ + 0.3140767216682434, + -0.7418519854545593, + 0.4988985061645508, + -0.3195493221282959 + ], + "perspectiveZoom": 1922235.5293810747 + } + }, + "layout": { + "views": "hbp-neuro", + "planarSlicesBackground": backgrd, + "useNehubaPerspective": { + "enableShiftDrag": false, + "doNotRestrictUserNavigation": false, + "perspectiveSlicesBackground": backgrd, + "removePerspectiveSlicesBackground": rmPsp, + "perspectiveBackground": backgrd, + "fixedZoomPerspectiveSlices": { + "sliceViewportWidth": 300, + "sliceViewportHeight": 300, + "sliceZoom": 563818.3562426177, + "sliceViewportSizeMultiplier": 2 + }, + "mesh": { + "backFaceColor": backgrd, + "removeBasedOnNavigation": true, + "flipRemovedOctant": true + }, + "centerToOrigin": true, + "drawSubstrates": drawSubstrates, + "drawZoomLevels": drawZoomLevels, + "hideImages": false, + "waitForMesh": true, + "restrictZoomLevel": { + "minZoom": 1200000, + "maxZoom": 3500000 + } + } + } + } + +} @Injectable({ providedIn: 'root' @@ -17,96 +144,103 @@ const getUniqueId = () => Math.round(Math.random() * 1e16).toString(16) export class PureContantService implements OnDestroy{ + private subscriptions: Subscription[] = [] public useTouchUI$: Observable<boolean> - public fetchedAtlases$: Observable<any> public darktheme$: Observable<boolean> public totalAtlasesLength: number public allFetchingReady$: Observable<boolean> - public backendUrl = (BACKEND_URL && `${BACKEND_URL}/`.replace(/\/\/$/, '/')) || `${window.location.origin}${window.location.pathname}` + private atlasParcSpcRegionMap = new MultiDimMap() + + private _backendUrl = (BACKEND_URL && `${BACKEND_URL}/`.replace(/\/\/$/, '/')) || `${window.location.origin}${window.location.pathname}` + get backendUrl() { + console.warn(`something is using backendUrl`) + return this._backendUrl + } + /** + * TODO remove + * when removing, also remove relevant worker code + */ private workerUpdateParcellation$ = fromEvent(this.workerService.worker, 'message').pipe( filter((message: MessageEvent) => message && message.data && message.data.type === 'UPDATE_PARCELLATION_REGIONS'), map(({ data }) => data) ) - private fetchTemplate = (templateUrl) => this.http.get(`${this.backendUrl}${templateUrl}`, { responseType: 'json' }).pipe( - switchMap((template: any) => { - if (template.nehubaConfig) { - return of(template) - } - if (template.nehubaConfigURL) { - return this.http.get(`${this.backendUrl}${template.nehubaConfigURL}`, { responseType: 'json' }).pipe( - map(nehubaConfig => { - return { - ...template, - nehubaConfig, - } - }), - ) + private getRegions(atlasId: string, parcId: string, spaceId: string){ + return this.http.get<TRegion[]>( + `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/parcellations/${encodeURIComponent(parcId)}/regions`, + { + params: { + space_id: spaceId + }, + responseType: 'json' } - return of(template) - }), - ) + ) + } - private processTemplate = template => forkJoin( - template.parcellations.map(parcellation => { + private getParcs(atlasId: string){ + return this.http.get<TParc[]>( + `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/parcellations`, + { responseType: 'json' } + ) + } - const id = getUniqueId() + private getParcDetail(atlasId: string, parcId: string) { + return this.http.get<TParc>( + `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/parcellations/${encodeURIComponent(parcId)}`, + { responseType: 'json' } + ) + } - this.workerService.worker.postMessage({ - type: 'PROPAGATE_PARC_REGION_ATTR', - parcellation, - inheritAttrsOpts: { - ngId: (parcellation as any ).ngId, - relatedAreas: [], - fullId: null - }, - id - }) + private getSpaces(atlasId: string){ + return this.http.get<TSpaceSummary[]>( + `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/spaces`, + { responseType: 'json' } + ) + } - return this.workerUpdateParcellation$.pipe( - filter(({ id: returnedId }) => id === returnedId), - take(1), - map(({ parcellation }) => parcellation) - ) - }) - ) + private getSpaceDetail(atlasId: string, spaceId: string) { + return this.http.get<TSpaceFull>( + `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/spaces/${encodeURIComponent(spaceId)}`, + { responseType: 'json' } + ) + } - public getTemplateEndpoint$ = this.http.get<any[]>(`${this.backendUrl}templates`, { responseType: 'json' }).pipe( - catchError(() => { - this.log.warn(`fetching root /tempaltes error`) - return of([]) - }), - shareReplay(), - ) + private getSpacesAndParc(atlasId: string) { + const spaces$ = this.getSpaces(atlasId).pipe( + switchMap(spaces => forkJoin( + spaces.map(space => this.getSpaceDetail(atlasId, parseId(space.id))) + )) + ) + const parcs$ = this.getParcs(atlasId).pipe( + // need not to get full parc data. first level gets all data + // switchMap(parcs => forkJoin( + // parcs.map(parc => this.getParcDetail(atlasId, parseId(parc.id))) + // )) + ) + return forkJoin([ + spaces$, + parcs$, + ]).pipe( + map(([ templateSpaces, parcellations ]) => { + return { templateSpaces, parcellations } + }) + ) + } - public initFetchTemplate$ = this.getTemplateEndpoint$.pipe( - switchMap((templates: string[]) => merge( - ...templates.map(templateName => this.fetchTemplate(templateName).pipe( - switchMap(template => this.processTemplate(template).pipe( - map(parcellations => { - return { - ...template, - parcellations - } - }) - )) - )), - )), - catchError((err) => { - this.log.warn(`fetching templates error`, err) - return of(null) - }), - ) + private getFullRegions(atlasId: string, parcId: string, spaceId: string) { + + } constructor( private store: Store<any>, private http: HttpClient, private log: LoggingService, private workerService: AtlasWorkerService, + @Inject(BS_ENDPOINT) private bsEndpoint: string, ){ this.darktheme$ = this.store.pipe( select(state => state?.viewerState?.templateSelected?.useTheme === 'dark') @@ -117,21 +251,6 @@ export class PureContantService implements OnDestroy{ shareReplay(1) ) - this.fetchedAtlases$ = this.http.get(`${this.backendUrl.replace(/\/$/, '')}/atlases/`, { responseType: 'json' }).pipe( - catchError((err, obs) => of(null)), - filter(v => !!v), - tap((arr: any[]) => this.totalAtlasesLength = arr.length), - switchMap(atlases => merge( - ...atlases.map(({ url }) => this.http.get( - /^http/.test(url) - ? url - : `${this.backendUrl.replace(/\/$/, '')}/${url}`, - { responseType: 'json' })) - )), - scan((acc, curr) => acc.concat(curr).sort((a, b) => (a.order || 1000) - (b.order || 1001)), []), - shareReplay(1) - ) - this.subscriptions.push( this.fetchedAtlases$.subscribe(fetchedAtlases => this.store.dispatch( @@ -141,7 +260,8 @@ export class PureContantService implements OnDestroy{ ) this.allFetchingReady$ = combineLatest([ - this.getTemplateEndpoint$.pipe( + this.initFetchTemplate$.pipe( + filter(v => !!v), map(arr => arr.length), ), this.store.pipe( @@ -161,7 +281,247 @@ export class PureContantService implements OnDestroy{ ) } - private subscriptions: Subscription[] = [] + private getAtlases$ = this.http.get<TAtlas[]>( + `${this.bsEndpoint}/atlases`, + { + responseType: 'json' + } + ).pipe( + shareReplay(1) + ) + + public fetchedAtlases$: Observable<TIAVAtlas[]> = this.getAtlases$.pipe( + switchMap(atlases => { + return forkJoin( + atlases.map( + atlas => this.getSpacesAndParc(atlas.id).pipe( + map(({ templateSpaces, parcellations }) => { + return { + '@id': atlas.id, + name: atlas.name, + templateSpaces: templateSpaces.map(tmpl => { + return { + '@id': tmpl.id, + name: tmpl.name, + availableIn: tmpl.availableParcellations.map(parc => { + return { + '@id': parc.id, + name: parc.name + } + }) + } + }), + parcellations: parcellations.map(parc => { + return { + '@id': parseId(parc.id), + name: parc.name, + baseLayer: parc.modality === 'cytoarchitecture', + '@version': { + '@next': parc.version?.next, + '@previous': parc.version?.prev, + 'name': parc.version?.name, + '@this': parseId(parc.id) + }, + availableIn: parc.availableSpaces.map(space => { + return { + '@id': space.id, + name: space.name, + /** + * TODO need original data format + */ + // originalDatasetFormats: [{ + // name: "probability map" + // }] + } + }) + } + }) + } + }) + ) + ) + ) + }), + catchError((err, obs) => of([])), + tap((arr: any[]) => this.totalAtlasesLength = arr.length), + scan((acc, curr) => acc.concat(curr).sort((a, b) => (a.order || 1000) - (b.order || 1001)), []), + shareReplay(1) + ) + + public initFetchTemplate$ = this.fetchedAtlases$.pipe( + switchMap(atlases => { + return forkJoin( + atlases.map(atlas => this.getSpacesAndParc(atlas['@id']).pipe( + switchMap(({ templateSpaces, parcellations }) => { + const ngLayerObj = {} + return forkJoin( + templateSpaces.map( + tmpl => { + ngLayerObj[tmpl.id] = {} + return tmpl.availableParcellations.map( + parc => this.getRegions(atlas['@id'], parc.id, tmpl.id).pipe( + tap(regions => { + recursiveMutate( + regions, + region => region.children, + region => { + /** + * individual map(s) + * this should work for both fully mapped and interpolated + * in the case of interpolated, it sucks that the ngLayerObj will be set multiple times + */ + if ( + tmpl.id in region.volumeSrc + && 'collect' in region.volumeSrc[tmpl.id] + ) { + const dedicatedMap = region.volumeSrc[tmpl.id]['collect'].filter(v => v.volume_type === 'neuroglancer/precomputed') + if (dedicatedMap.length === 1) { + const ngId = MultiDimMap.GetKey(atlas['@id'], tmpl.id, parc.id, dedicatedMap[0].id) + region['ngId'] = ngId + region['labelIndex'] = dedicatedMap[0].detail['neuroglancer/precomputed'].labelIndex + ngLayerObj[tmpl.id][ngId] = { + source: `precomputed://${dedicatedMap[0].url}`, + type: "segmentation", + transform: dedicatedMap[0].detail['neuroglancer/precomputed'].transform + } + } + } + + /** + * if label index is defined + */ + if (!!region.labelIndex) { + const hemisphereKey = /left hemisphere/.test(region.name) + // these two keys are, unfortunately, more or less hardcoded + // which is less than ideal + ? 'left hemisphere' + : /right hemisphere/.test(region.name) + ? 'right hemisphere' + : 'whole brain' + + /** + * TODO fix in siibra-api + */ + if ( + tmpl.id !== 'minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588' + && parc.id === 'minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-25' + && hemisphereKey === 'whole brain' + ) { + region.labelIndex = null + return + } + + const hemispheredNgId = MultiDimMap.GetKey(atlas['@id'], tmpl.id, parc.id, hemisphereKey) + region['ngId'] = hemispheredNgId + } + } + ) + this.atlasParcSpcRegionMap.set( + atlas['@id'], tmpl.id, parc.id, regions + ) + + /** + * populate maps for parc + */ + for (const parc of parcellations) { + if (tmpl.id in (parc.volumeSrc || {})) { + // key: 'left hemisphere' | 'right hemisphere' | 'whole brain' + for (const key in parc.volumeSrc[tmpl.id]) { + for (const vol of parc.volumeSrc[tmpl.id][key]) { + if (vol.volume_type === 'neuroglancer/precomputed') { + const ngIdKey = MultiDimMap.GetKey(atlas['@id'], tmpl.id, parseId(parc.id), key) + ngLayerObj[tmpl.id][ngIdKey] = { + source: `precomputed://${vol.url}`, + type: "segmentation", + transform: vol.detail['neuroglancer/precomputed'].transform + } + } + } + } + } + } + }) + ) + ) + } + ).reduce(flattenReducer, []) + ).pipe( + mapTo({ templateSpaces, parcellations, ngLayerObj }) + ) + }), + map(({ templateSpaces, ngLayerObj }) => { + return templateSpaces.map(tmpl => { + const darkTheme = tmpl.src_volume_type === 'mri' + const nehubaConfig = getNehubaConfig(darkTheme) + const initialLayers = nehubaConfig.dataset.initialNgState.layers + + const tmplNgId = tmpl.name + const tmplAuxMesh = `${tmpl.name} auxmesh` + + const precomputed = tmpl.volume_src.find(src => src.volume_type === 'neuroglancer/precomputed') + if (precomputed) { + initialLayers[tmplNgId] = { + type: "image", + source: `precomputed://${precomputed.url}`, + transform: precomputed.detail['neuroglancer/precomputed'].transform + } + } + + const precompmesh = tmpl.volume_src.find(src => src.volume_type === 'neuroglancer/precompmesh') + const auxMeshes = [] + if (precompmesh){ + initialLayers[tmplAuxMesh] = { + source: `precompmesh://${precompmesh.url}`, + type: "segmentation", + transform: precompmesh.detail['neuroglancer/precompmesh'].transform + } + for (const auxMesh of precompmesh.detail['neuroglancer/precompmesh'].auxMeshes) { + + auxMeshes.push({ + ...auxMesh, + ngId: tmplAuxMesh, + '@id': `${tmplAuxMesh} ${auxMesh.name}`, + visible: true + }) + } + } + + for (const key in (ngLayerObj[tmpl.id] || {})) { + initialLayers[key] = ngLayerObj[tmpl.id][key] + } + + return { + name: tmpl.name, + '@id': tmpl.id, + fullId: tmpl.id, + useTheme: darkTheme ? 'dark' : 'light', + ngId: tmplNgId, + nehubaConfig, + auxMeshes, + parcellations: tmpl.availableParcellations.map(parc => { + const regions = this.atlasParcSpcRegionMap.get(atlas['@id'], tmpl.id, parc.id) || [] + return { + fullId: parc.id, + '@id': parc.id, + name: parc.name, + regions + } + }) + } + }) + }) + )) + ) + }), + map(arr => { + return arr.reduce(flattenReducer, []) + }), + catchError((err) => { + this.log.warn(`fetching templates error`, err) + return of(null) + }), + shareReplay(1), + ) ngOnDestroy(){ while(this.subscriptions.length > 0) this.subscriptions.pop().unsubscribe() diff --git a/src/util/siibraApiConstants/types.ts b/src/util/siibraApiConstants/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..4510e27ee359c50fa82e81c29e801b449933fe85 --- /dev/null +++ b/src/util/siibraApiConstants/types.ts @@ -0,0 +1,140 @@ +type THref = { + href: string +} + +type TSpaceType = 'mri' | 'histology' + +type TNgTransform = number[][] + +type TVolumeType = 'nii' | 'neuroglancer/precomputed' | 'neuroglancer/precompmesh' | 'detailed maps' +type TParcModality = 'cytoarchitecture' + +type TAuxMesh = { + name: string + labelIndicies: number[] +} + +interface IVolumeTypeDetail { + 'nii': null + 'neuroglancer/precomputed': { + 'neuroglancer/precomputed': { + 'transform': TNgTransform + } + } + 'neuroglancer/precompmesh': { + 'neuroglancer/precompmesh': { + 'auxMeshes': TAuxMesh[] + 'transform': TNgTransform + } + } + 'detailed maps': null +} + +type TVolumeSrc<VolumeType extends keyof IVolumeTypeDetail> = { + id: string + name: string + url: string + volume_type: TVolumeType + detail: IVolumeTypeDetail[VolumeType] +} + +type TKgIdentifier = { + kgSchema: string + kgId: string +} + +type TVersion = { + name: string + prev: string | null + next: string | null +} + +export type TId = string | { kg: TKgIdentifier } + +export type TAtlas = { + id: string + name: string + links: { + parcellations: THref + spaces: THref + } +} + +export type TSpaceSummary = { + id: { + kg: TKgIdentifier + }, + name: string + links: { + self: THref + } +} + +export type TParcSummary = { + id: string + name: string +} + +export type TSpaceFull = { + id: string + name: string + key: string //??? + type: string //??? + url: string //??? + ziptarget: string //??? + src_volume_type: TSpaceType + volume_src: TVolumeSrc<keyof IVolumeTypeDetail>[] + availableParcellations: TParcSummary[] + links: { + templates: THref + parcellation_maps: THref + } +} + +export type TParc = { + id: { + kg: TKgIdentifier + } + name: string + availableSpaces: { + id: string + name: string + }[] + links: { + self: THref + } + regions: THref + modality: TParcModality + version: TVersion + volumeSrc: { + [key: string]: { + [key: string]: TVolumeSrc<keyof IVolumeTypeDetail>[] + } + } +} + +export type TRegion = { + name: string + children: TRegion[] + volumeSrc: { + [key: string]: { + [key: string]: TVolumeSrc<keyof IVolumeTypeDetail>[] + } + } + + labelIndex?: number + rgb?: number[] + id?: { + kg: TKgIdentifier + } + + /** + * missing + */ + + originDatasets?: ({ + filename: string + } & TKgIdentifier) [] + + position?: number[] +} diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts index 7a338c72e24a09592fdc718d5064687e7d67a6db..194681b3645ab4d6383d7f8d3bddb629eca683ea 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts @@ -10,7 +10,7 @@ import { IAuxMesh } from '../store' export function getAuxMeshesAndReturnIColor(auxMeshes: IAuxMesh[]): IColorMap{ const returnVal: IColorMap = {} for (const auxMesh of auxMeshes as IAuxMesh[]) { - const { ngId, labelIndicies, rgb } = auxMesh + const { ngId, labelIndicies, rgb = [255, 255, 255] } = auxMesh const auxMeshColorMap = returnVal[ngId] || {} for (const lblIdx of labelIndicies) { auxMeshColorMap[lblIdx as number] = {