From 7fdbff0f0baf73965bff9df3f075a76b84991b54 Mon Sep 17 00:00:00 2001
From: Xiao Gui <xgui3783@gmail.com>
Date: Fri, 6 May 2022 09:27:37 +0200
Subject: [PATCH] bugfix: jba29 in bigbrain

---
 src/atlasComponents/sapi/constants.ts         |   9 ++
 src/atlasComponents/sapi/core/sapiRegion.ts   |  16 ++-
 src/atlasComponents/sapi/index.ts             |   4 +
 src/atlasComponents/sapi/schema.ts            | 100 +++++++++++---
 src/atlasComponents/sapi/type.ts              |   6 +
 .../features/voi/voiQuery.directive.ts        |   8 +-
 .../util/parcellationSupportedInSpace.pipe.ts |   7 +-
 .../routeStateTransform.service.ts            |  47 +++----
 .../nehuba/config.service/type.ts             |   1 -
 .../nehuba/config.service/util.ts             |  69 ++++++----
 .../layerCtrl.service/layerCtrl.effects.ts    | 128 +++++++++++++-----
 .../nehubaViewerGlue.component.ts             |   9 +-
 src/viewerModule/nehuba/store/util.ts         |   8 +-
 13 files changed, 297 insertions(+), 115 deletions(-)
 create mode 100644 src/atlasComponents/sapi/constants.ts

diff --git a/src/atlasComponents/sapi/constants.ts b/src/atlasComponents/sapi/constants.ts
new file mode 100644
index 000000000..40011a86a
--- /dev/null
+++ b/src/atlasComponents/sapi/constants.ts
@@ -0,0 +1,9 @@
+export const IDS = {
+  TEMPLATES: {
+    BIG_BRAIN: "minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588",
+    MNI152: "minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2"
+  },
+  PARCELLATION: {
+    JBA29: "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-290"
+  }
+}
diff --git a/src/atlasComponents/sapi/core/sapiRegion.ts b/src/atlasComponents/sapi/core/sapiRegion.ts
index afadc4929..1787145e8 100644
--- a/src/atlasComponents/sapi/core/sapiRegion.ts
+++ b/src/atlasComponents/sapi/core/sapiRegion.ts
@@ -1,5 +1,5 @@
 import { SAPI } from "..";
-import { SapiRegionalFeatureModel, SapiRegionMapInfoModel, SapiRegionModel, cleanIeegSessionDatasets, SapiIeegSessionModel, CleanedIeegDataset } from "../type";
+import { SapiRegionalFeatureModel, SapiRegionMapInfoModel, SapiRegionModel, cleanIeegSessionDatasets, SapiIeegSessionModel, CleanedIeegDataset, SapiVolumeModel, PaginatedResponse } from "../type";
 import { strToRgb, hexToRgb } from 'common/util'
 import { forkJoin, Observable, of } from "rxjs";
 import { catchError, map } from "rxjs/operators";
@@ -80,6 +80,20 @@ export class SAPIRegion{
     return `${this.prefix}/regional_map/map?space_id=${encodeURI(spaceId)}`
   }
 
+  getVolumes(): Observable<PaginatedResponse<SapiVolumeModel>>{
+    const url = `${this.prefix}/volumes`
+    return this.sapi.httpGet<PaginatedResponse<SapiVolumeModel>>(
+      url
+    )
+  }
+
+  getVolumeInstance(volumeId: string): Observable<SapiVolumeModel> {
+    const url = `${this.prefix}/volumes/${encodeURIComponent(volumeId)}`
+    return this.sapi.httpGet<SapiVolumeModel>(
+      url
+    )
+  }
+
   getDetail(spaceId: string): Observable<SapiRegionModel> {
     const url = `${this.prefix}/${encodeURIComponent(this.id)}`
     return this.sapi.httpGet<SapiRegionModel>(
diff --git a/src/atlasComponents/sapi/index.ts b/src/atlasComponents/sapi/index.ts
index 2c223648d..e5d8afd20 100644
--- a/src/atlasComponents/sapi/index.ts
+++ b/src/atlasComponents/sapi/index.ts
@@ -24,3 +24,7 @@ export {
   SAPIParcellation,
   SAPIRegion
 } from "./core"
+
+export {
+  IDS
+} from "./constants"
\ No newline at end of file
diff --git a/src/atlasComponents/sapi/schema.ts b/src/atlasComponents/sapi/schema.ts
index 3eb4599d5..a54de84de 100644
--- a/src/atlasComponents/sapi/schema.ts
+++ b/src/atlasComponents/sapi/schema.ts
@@ -24,6 +24,9 @@ export interface paths {
     /** Returns a regional map for given region name. */
     get: operations["get_regional_map_file_atlases__atlas_id__parcellations__parcellation_id__regions__region_id__regional_map_map_get"]
   }
+  "/atlases/{atlas_id}/parcellations/{parcellation_id}/regions/{region_id}/volumes": {
+    get: operations["get_regional_volumes_atlases__atlas_id__parcellations__parcellation_id__regions__region_id__volumes_get"]
+  }
   "/atlases/{atlas_id}/parcellations/{parcellation_id}/regions/{region_id}": {
     get: operations["get_single_region_detail_atlases__atlas_id__parcellations__parcellation_id__regions__region_id__get"]
   }
@@ -104,7 +107,7 @@ export interface paths {
      * :param space_id:
      * :param parcellation_id:
      * :param region_id:
-     * :return: UnionRegionalFeatureModels
+     * :return: FeatureModels
      */
     get: operations["get_feature_details_features__feature_id__get"]
   }
@@ -235,11 +238,24 @@ export interface components {
       /** Urls */
       urls: components["schemas"]["Url"][]
       /** Cells */
-      cells: string
+      cells?: components["schemas"]["CorticalCellModel"][]
       /** Section */
-      section: string
+      section?: string
       /** Patch */
-      patch: string
+      patch?: string
+    }
+    /** CorticalCellModel */
+    CorticalCellModel: {
+      /** X */
+      x: number
+      /** Y */
+      y: number
+      /** Area */
+      area: number
+      /** Layer */
+      layer: number
+      /** Instance Label */
+      "instance label": number
     }
     /** DatasetJsonModel */
     DatasetJsonModel: {
@@ -420,6 +436,29 @@ export interface components {
       /** Content */
       content: string
     }
+    /** Page[Union[siibra.features.connectivity.ConnectivityMatrixDataModel, app.models.SerializationErrorModel]] */
+    "Page_Union_siibra.features.connectivity.ConnectivityMatrixDataModel__app.models.SerializationErrorModel__": {
+      /** Items */
+      items: (Partial<components["schemas"]["ConnectivityMatrixDataModel"]> &
+        Partial<components["schemas"]["SerializationErrorModel"]>)[]
+      /** Total */
+      total: number
+      /** Page */
+      page: number
+      /** Size */
+      size: number
+    }
+    /** Page[VolumeModel] */
+    Page_VolumeModel_: {
+      /** Items */
+      items: components["schemas"]["VolumeModel"][]
+      /** Total */
+      total: number
+      /** Page */
+      page: number
+      /** Size */
+      size: number
+    }
     /** ProfileDataModel */
     ProfileDataModel: {
       density: components["schemas"]["NpArrayDataModel"]
@@ -787,7 +826,7 @@ export interface components {
     /** ValidationError */
     ValidationError: {
       /** Location */
-      loc: string[]
+      loc: (Partial<string> & Partial<number>)[]
       /** Message */
       msg: string
       /** Error Type */
@@ -822,11 +861,8 @@ export interface components {
     VolumeModel: {
       /** @Id */
       "@id": string
-      /**
-       * @Type
-       * @constant
-       */
-      "@type"?: "https://openminds.ebrains.eu/core/DatasetVersion"
+      /** @Type */
+      "@type": string
       metadata: components["schemas"]["siibra__openminds__core__v4__products__datasetVersion__Model"]
       /** Urls */
       urls: components["schemas"]["Url"][]
@@ -1491,6 +1527,35 @@ export interface operations {
       }
     }
   }
+  get_regional_volumes_atlases__atlas_id__parcellations__parcellation_id__regions__region_id__volumes_get: {
+    parameters: {
+      path: {
+        atlas_id: string
+        parcellation_id: string
+        region_id: string
+      }
+      query: {
+        space_id?: string
+        type?: string
+        page?: number
+        size?: number
+      }
+    }
+    responses: {
+      /** Successful Response */
+      200: {
+        content: {
+          "application/json": components["schemas"]["Page_VolumeModel_"]
+        }
+      }
+      /** Validation Error */
+      422: {
+        content: {
+          "application/json": components["schemas"]["HTTPValidationError"]
+        }
+      }
+    }
+  }
   get_single_region_detail_atlases__atlas_id__parcellations__parcellation_id__regions__region_id__get: {
     parameters: {
       path: {
@@ -1575,18 +1640,15 @@ export interface operations {
       }
       query: {
         type?: string
-        per_page?: number
         page?: number
+        size?: number
       }
     }
     responses: {
       /** Successful Response */
       200: {
         content: {
-          "application/json": (Partial<
-            components["schemas"]["ConnectivityMatrixDataModel"]
-          > &
-            Partial<components["schemas"]["SerializationErrorModel"]>)[]
+          "application/json": components["schemas"]["Page_Union_siibra.features.connectivity.ConnectivityMatrixDataModel__app.models.SerializationErrorModel__"]
         }
       }
       /** Validation Error */
@@ -1889,7 +1951,7 @@ export interface operations {
    * :param space_id:
    * :param parcellation_id:
    * :param region_id:
-   * :return: UnionRegionalFeatureModels
+   * :return: FeatureModels
    */
   get_feature_details_features__feature_id__get: {
     parameters: {
@@ -1911,7 +1973,11 @@ export interface operations {
             components["schemas"]["ReceptorDatasetModel"]
           > &
             Partial<components["schemas"]["BaseDatasetJsonModel"]> &
-            Partial<components["schemas"]["CorticalCellDistributionModel"]>
+            Partial<components["schemas"]["CorticalCellDistributionModel"]> &
+            Partial<components["schemas"]["IEEGSessionModel"]> &
+            Partial<components["schemas"]["VOIDataModel"]> &
+            Partial<components["schemas"]["ConnectivityMatrixDataModel"]> &
+            Partial<components["schemas"]["SerializationErrorModel"]>
         }
       }
       /** Validation Error */
diff --git a/src/atlasComponents/sapi/type.ts b/src/atlasComponents/sapi/type.ts
index 1796c54d0..3eb47a670 100644
--- a/src/atlasComponents/sapi/type.ts
+++ b/src/atlasComponents/sapi/type.ts
@@ -34,6 +34,12 @@ export type SapiIeegSessionModel = components["schemas"]["IEEGSessionModel"]
  * utility types
  */
 type PathReturn<T extends keyof paths> = Required<paths[T]["get"]["responses"][200]["content"]["application/json"]>
+export type PaginatedResponse<T> = {
+  items: T[]
+  total: number
+  page: number
+  size: number
+}
 
 /**
  * serialization error type
diff --git a/src/atlasComponents/sapiViews/features/voi/voiQuery.directive.ts b/src/atlasComponents/sapiViews/features/voi/voiQuery.directive.ts
index e70d1ab45..623011d89 100644
--- a/src/atlasComponents/sapiViews/features/voi/voiQuery.directive.ts
+++ b/src/atlasComponents/sapiViews/features/voi/voiQuery.directive.ts
@@ -1,10 +1,11 @@
 import { Directive, EventEmitter, Inject, Input, OnChanges, OnDestroy, Optional, Output, SimpleChanges } from "@angular/core";
 import { interval, merge, Observable, of, Subject, Subscription } from "rxjs";
-import { debounce, debounceTime, filter, pairwise, shareReplay, startWith, switchMap, take, tap } from "rxjs/operators";
+import { debounce, debounceTime, distinctUntilChanged, filter, pairwise, shareReplay, startWith, switchMap, take, tap } from "rxjs/operators";
 import { AnnotationLayer, TNgAnnotationPoint, TNgAnnotationAABBox } from "src/atlasComponents/annotations";
 import { SAPI } from "src/atlasComponents/sapi/sapi.service";
 import { BoundingBoxConcept, SapiAtlasModel, SapiSpaceModel, SapiVOIDataResponse, OpenMINDSCoordinatePoint } from "src/atlasComponents/sapi/type";
 import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util";
+import { arrayEqual } from "src/util/array";
 
 @Directive({
   selector: '[sxplr-sapiviews-features-voi-query]',
@@ -130,14 +131,15 @@ export class SapiViewsFeaturesVoiQuery implements OnChanges, OnDestroy{
     clickInterceptor.register(handle)
     this.subscription.push(
       this.features$.pipe(
+        startWith([] as SapiVOIDataResponse[]),
+        distinctUntilChanged(arrayEqual((o, n) => o["@id"] === n["@id"])),
+        pairwise(),
         debounce(() => 
           interval(16).pipe(
             filter(() => !!this.voiBBoxSvc),
             take(1),
           )
         ),
-        startWith([] as SapiVOIDataResponse[]),
-        pairwise()
       ).subscribe(([ prev, curr ]) => {
         for (const v of prev) {
           const box = this.pointsToAABB(v.location.maxpoint, v.location.minpoint)
diff --git a/src/atlasComponents/sapiViews/util/parcellationSupportedInSpace.pipe.ts b/src/atlasComponents/sapiViews/util/parcellationSupportedInSpace.pipe.ts
index aaafaf90f..f16e6a0a8 100644
--- a/src/atlasComponents/sapiViews/util/parcellationSupportedInSpace.pipe.ts
+++ b/src/atlasComponents/sapiViews/util/parcellationSupportedInSpace.pipe.ts
@@ -1,5 +1,5 @@
 import { Pipe, PipeTransform } from "@angular/core";
-import { Observable } from "rxjs";
+import { Observable, of } from "rxjs";
 import { map } from "rxjs/operators";
 import { SAPIParcellation } from "src/atlasComponents/sapi/core";
 import { SAPI } from "src/atlasComponents/sapi/sapi.service";
@@ -35,6 +35,11 @@ export class ParcellationSupportedInSpacePipe implements PipeTransform{
     const tmplId = typeof tmpl === "string"
       ? tmpl
       : tmpl["@id"]
+    for (const key in knownExceptions.supported) {
+      if (key === parcId && knownExceptions.supported[key].indexOf(tmplId) >= 0) {
+        return of(true)
+      }
+    }
     return this.sapi.registry.get<SAPIParcellation>(parcId).getVolumes().pipe(
       map(volumes => volumes.some(v => v.data.space["@id"] === tmplId))
     )
diff --git a/src/routerModule/routeStateTransform.service.ts b/src/routerModule/routeStateTransform.service.ts
index d7deecf51..10f003638 100644
--- a/src/routerModule/routeStateTransform.service.ts
+++ b/src/routerModule/routeStateTransform.service.ts
@@ -1,7 +1,7 @@
 import { Injectable } from "@angular/core";
 import { UrlSegment, UrlTree } from "@angular/router";
 import { map } from "rxjs/operators";
-import { SAPI } from "src/atlasComponents/sapi";
+import { SAPI, SapiRegionModel } from "src/atlasComponents/sapi";
 import { atlasSelection, defaultState, MainState, plugins, userInteraction } from "src/state";
 import { getParcNgId, getRegionLabelIndex } from "src/viewerModule/nehuba/config.service";
 import { decodeToNumber, encodeNumber, encodeURIFull, separator } from "./cipher";
@@ -43,16 +43,21 @@ export class RouteStateTransformSvc {
       this.sapi.getParcRegions(selectedAtlasId, selectedParcellationId, selectedTemplateId, { priority: 10 }).toPromise(),
     ])
 
-    const latNgIdMap = ["left hemisphere", "right hemisphere", "whole brain"].map(lat => {
-      let regex: RegExp = /./
-      if (lat === "left hemisphere") regex = /left/i
-      if (lat === "right hemisphere") regex = /right/i
-      return {
-        lat,
-        regex,
-        ngId: getParcNgId(selectedAtlas, selectedTemplate, selectedParcellation, lat)
+    const ngIdToRegionMap: Map<string, Map<number, SapiRegionModel[]>> = new Map()
+
+    for (const region of allParcellationRegions) {
+      const ngId = getParcNgId(selectedAtlas, selectedTemplate, selectedParcellation, region)
+      if (!ngIdToRegionMap.has(ngId)) {
+        ngIdToRegionMap.set(ngId, new Map())
       }
-    })
+      const map = ngIdToRegionMap.get(ngId)
+
+      const idx = getRegionLabelIndex(selectedAtlas, selectedTemplate, selectedParcellation, region)
+      if (!map.has(idx)) {
+        map.set(idx, [])
+      }
+      map.get(idx).push(region)
+    }
     
     const selectedRegions = (() => {
       if (!selectedRegionIds) return []
@@ -63,12 +68,12 @@ export class RouteStateTransformSvc {
       const json = { [selectedRegionIds[0]]: selectedRegionIds[1] }
 
       for (const ngId in json) {
-        const matchingMap = latNgIdMap.find(ngIdMap => ngIdMap.ngId === ngId)
-        if (!matchingMap) {
+        if (!ngIdToRegionMap.has(ngId)) {
           console.error(`could not find matching map for ${ngId}`)
           continue
         }
-        const filteredRegions = allParcellationRegions.filter(r => matchingMap.regex.test(r.name))
+
+        const map = ngIdToRegionMap.get(ngId)
         
         const val = json[ngId]
         const labelIndicies = val.split(separator).map(n => {
@@ -81,20 +86,8 @@ export class RouteStateTransformSvc {
             return null
           }
         }).filter(v => !!v)
-  
-        for (const labelIndex of labelIndicies) {
-          /**
-           * currently, only 1 region is expected to be selected at max
-           * this method probably out performs creating a map
-           * but with 2+ regions, creating a map would consistently be faster
-           */
-          for (const r of filteredRegions) {
-            const idx = getRegionLabelIndex(selectedAtlas, selectedTemplate, selectedParcellation, r)
-            if (idx === labelIndex) {
-              return [ r ]
-            }
-          }
-        }
+
+        return labelIndicies.map(idx => map.get(idx) || []).flatMap(v => v)
       }
       return []
     })()
diff --git a/src/viewerModule/nehuba/config.service/type.ts b/src/viewerModule/nehuba/config.service/type.ts
index c0e963ecb..c8154dd44 100644
--- a/src/viewerModule/nehuba/config.service/type.ts
+++ b/src/viewerModule/nehuba/config.service/type.ts
@@ -112,5 +112,4 @@ export type NgPrecompMeshSpec = {
 
 export type NgSegLayerSpec = {
   labelIndicies: number[]
-  laterality: 'left hemisphere' | 'right hemisphere' | 'whole brain'
 } & NgLayerSpec
diff --git a/src/viewerModule/nehuba/config.service/util.ts b/src/viewerModule/nehuba/config.service/util.ts
index a89599f60..6cd2b0c35 100644
--- a/src/viewerModule/nehuba/config.service/util.ts
+++ b/src/viewerModule/nehuba/config.service/util.ts
@@ -1,5 +1,5 @@
 import { SapiParcellationModel, SapiSpaceModel, SapiAtlasModel, SapiRegionModel } from 'src/atlasComponents/sapi'
-import { SapiVolumeModel } from 'src/atlasComponents/sapi/type'
+import { SapiVolumeModel, IDS } from 'src/atlasComponents/sapi'
 import { atlasSelection } from 'src/state'
 import { MultiDimMap } from 'src/util/fn'
 import { ParcVolumeSpec } from "../store/util"
@@ -14,7 +14,7 @@ import {
 
 // fsaverage uses threesurfer, which, whilst do not use ngId, uses 'left' and 'right' as keys 
 const fsAverageKeyVal = {
-  "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-290": {
+  [IDS.PARCELLATION.JBA29]: {
     "left hemisphere": "left",
     "right hemisphere": "right"
   }
@@ -29,7 +29,7 @@ const BACKCOMAP_KEY_DICT = {
   // human multi level
   'juelich/iav/atlas/v1.0.0/1': {
     // icbm152
-    'minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2': {
+    [IDS.TEMPLATES.MNI152]: {
       // julich brain v2.6
       'minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-26': {
         'left hemisphere': 'MNI152_V25_LEFT_NG_SPLIT_HEMISPHERE',
@@ -186,15 +186,23 @@ export function getTmplAuxNgLayer(atlas: SapiAtlasModel, tmpl: SapiSpaceModel, s
   }
 }
 
-export function getParcNgId(atlas: SapiAtlasModel, tmpl: SapiSpaceModel, parc: SapiParcellationModel, _laterality: string | SapiRegionModel): string {
-  let laterality: string
-  if (typeof _laterality === "string") {
-    laterality = _laterality
-  } else {
-    laterality = "whole brain"
-    if (_laterality.name.indexOf("left") >= 0) laterality = "left hemisphere"
-    if (_laterality.name.indexOf("right") >= 0) laterality = "right hemisphere"
+export function getParcNgId(atlas: SapiAtlasModel, tmpl: SapiSpaceModel, parc: SapiParcellationModel, region: SapiRegionModel): string {
+
+  let laterality: string = "whole brain"
+  if (region.name.indexOf("left") >= 0) laterality = "left hemisphere"
+  if (region.name.indexOf("right") >= 0) laterality = "right hemisphere"
+
+  /**
+   * for JBA29 in big brain, there exist several volumes. (e.g. v1, v2, v5, interpolated, etc)
+   */
+  if (tmpl['@id'] === IDS.TEMPLATES.BIG_BRAIN && parc['@id'] === IDS.PARCELLATION.JBA29) {
+    laterality = region.hasAnnotation.visualizedIn?.['@id']
   }
+
+  if (!laterality) {
+    return null
+  }
+
   let ngId = BACKCOMAP_KEY_DICT[atlas["@id"]]?.[tmpl["@id"]]?.[parc["@id"]]?.[laterality]
   if (!ngId) {
     ngId = `_${MultiDimMap.GetKey(atlas["@id"], tmpl["@id"], parc["@id"], laterality)}`
@@ -202,18 +210,37 @@ export function getParcNgId(atlas: SapiAtlasModel, tmpl: SapiSpaceModel, parc: S
   return ngId
 }
 
+const labelIdxRegex = /siibra_python_ng_precomputed_labelindex:\/\/(.*?)#([0-9]+)$/
+
+export function getRegionLabelIndex(_atlas: SapiAtlasModel, _tmpl: SapiSpaceModel, _parc: SapiParcellationModel, region: SapiRegionModel): number {
+  const overwriteLabelIndex = region.hasAnnotation.inspiredBy.map(({ "@id": id }) => labelIdxRegex.exec(id)).filter(v => !!v)
+  if (overwriteLabelIndex.length > 0) {
+    const match = overwriteLabelIndex[0]
+    const volumeId = match[1]
+    const labelIndex = match[2]
+    const _labelIndex = Number(labelIndex)
+    if (!isNaN(_labelIndex)) return _labelIndex
+  }
+  const lblIdx = Number(region?.hasAnnotation?.internalIdentifier)
+  if (isNaN(lblIdx)) return null
+  return lblIdx
+}
+
 export function getParcNgLayers(atlas: SapiAtlasModel, tmpl: SapiSpaceModel, parc: SapiParcellationModel, parcVolumes: { volume: SapiVolumeModel, volumeMetadata: ParcVolumeSpec }[]): Record<string, NgSegLayerSpec>{
   const returnVal: Record<string, NgSegLayerSpec> = {}
   for (const parcVol of parcVolumes) {
     const { volume, volumeMetadata } = parcVol
-    const { laterality, labelIndicies } = volumeMetadata
-    const ngId = getParcNgId(atlas, tmpl, parc, laterality)
+    const { regions } = volumeMetadata
+    if (regions.length === 0) {
+      console.warn(`parc volume with no associated region`)
+      continue
+    }
+    const ngId = getParcNgId(atlas, tmpl, parc, regions[0].region)
 
     returnVal[ngId] = {
       source: `precomputed://${volume.data.url.replace(/^precomputed:\/\//, '')}`,
-      labelIndicies,
-      laterality,
-      transform: (volume.data.detail["neuroglancer/precomputed"] as any).transform
+      transform: (volume.data.detail["neuroglancer/precomputed"] as any).transform,
+      labelIndicies: regions.map(v => v.labelIndex)
     }
   }
   return returnVal
@@ -240,12 +267,6 @@ export const getNgLayersFromVolumesATP = (volumes: CongregatedVolume, ATP: { atl
   }
 }
 
-export function getRegionLabelIndex(_atlas: SapiAtlasModel, _tmpl: SapiSpaceModel, _parc: SapiParcellationModel, region: SapiRegionModel): number {
-  const lblIdx = Number(region?.hasAnnotation?.internalIdentifier)
-  if (isNaN(lblIdx)) return null
-  return lblIdx
-}
-
 export const defaultNehubaConfig: NehubaConfig = {
   "configName": "",
   "globals": {
@@ -318,11 +339,11 @@ export const defaultNehubaConfig: NehubaConfig = {
 }
 
 export const spaceMiscInfoMap = new Map([
-  ['minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588', {
+  [IDS.TEMPLATES.BIG_BRAIN, {
     name: 'bigbrain',
     scale: 1,
   }],
-  ['minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2', {
+  [IDS.TEMPLATES.MNI152, {
     name: 'icbm2009c',
     scale: 1,
   }],
diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts
index 314049276..6f6d70887 100644
--- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts
+++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts
@@ -3,8 +3,8 @@ import { createEffect } from "@ngrx/effects";
 import { select, Store } from "@ngrx/store";
 import { forkJoin, of } from "rxjs";
 import { mapTo, switchMap, withLatestFrom, filter, catchError, map, debounceTime, shareReplay, distinctUntilChanged, startWith, pairwise } from "rxjs/operators";
-import { SAPI, SapiAtlasModel, SapiFeatureModel, SapiParcellationModel, SapiSpaceModel } from "src/atlasComponents/sapi";
-import { SapiVOIDataResponse } from "src/atlasComponents/sapi/type";
+import { SAPI, SapiAtlasModel, SapiFeatureModel, SapiParcellationModel, SapiSpaceModel, SapiRegionModel } from "src/atlasComponents/sapi";
+import { SapiVOIDataResponse, SapiVolumeModel } from "src/atlasComponents/sapi/type";
 import { atlasAppearance, atlasSelection, userInteraction } from "src/state";
 import { arrayEqual } from "src/util/array";
 import { EnumColorMapName } from "src/util/colorMaps";
@@ -146,51 +146,115 @@ export class LayerCtrlEffects {
       throw new Error(`parcellation defined, but template not defined!`)
     }
     
+    /**
+     * some labelled maps (such as julich brain in big brain) do not have the volumes defined on the parcellation level.
+     * While we have the URLs of these volumes (the method we use is also kind of hacky), and in theory, we could construct a volume object directly
+     * It is probably better to fetch the correct volume object to begin with
+     */
     const parcVolumes$ = !parcellation
-      ? of([])
+      ? of([] as {volume: SapiVolumeModel, volumeMetadata: ParcVolumeSpec}[])
       : forkJoin([
         this.sapi.getParcellation(atlas["@id"], parcellation["@id"]).getRegions(template["@id"]).pipe(
           map(regions => {
 
-            const returnArr: ParcVolumeSpec[] = []
+            const volumeIdToRegionMap = new Map<string, {
+              labelIndex: number
+              region: SapiRegionModel
+            }[]>()
+
             for (const r of regions) {
-              const source = r?.hasAnnotation?.visualizedIn?.["@id"]
-              if (!source) continue
-              if (source.indexOf("precomputed://") < 0) continue
+              const volumeId = r?.hasAnnotation?.visualizedIn?.["@id"]
+              if (!volumeId) continue
+
               const labelIndex = getRegionLabelIndex(atlas, template, parcellation, r)
               if (!labelIndex) continue
-              
-              const found = returnArr.find(v => v.volumeSrc === source)
-              if (found) {
-                found.labelIndicies.push(labelIndex)
-                continue
-              }
 
-              let laterality: "left hemisphere" | "right hemisphere" | "whole brain" = "whole brain"
-              if (r.name.indexOf("left") >= 0) laterality = "left hemisphere"
-              if (r.name.indexOf("right") >= 0) laterality = "right hemisphere"
-              returnArr.push({
-                volumeSrc: source,
-                labelIndicies: [labelIndex],
-                parcellation,
-                laterality,
+              if (!volumeIdToRegionMap.has(volumeId)) {
+                volumeIdToRegionMap.set(volumeId, [])
+              }
+              volumeIdToRegionMap.get(volumeId).push({
+                labelIndex,
+                region: r
               })
             }
-            return returnArr
+            return volumeIdToRegionMap
           })
         ),
         this.sapi.getParcellation(atlas["@id"], parcellation["@id"]).getVolumes()
       ]).pipe(
-        map(([ volumeSrcs, volumes ]) => {
-          return volumes.map(
-            v => {
-              const found = volumeSrcs.find(volSrc => volSrc.volumeSrc.indexOf(v.data.url) >= 0)
-              return {
-                volume: v,
-                volumeMetadata: found,
+        switchMap(([ volumeIdToRegionMap, volumes ]) => {
+          const missingVolumeIds = Array.from(volumeIdToRegionMap.keys()).filter(id => volumes.map(v => v["@id"]).indexOf(id) < 0)
+
+          const volumesFromParc: {volume: SapiVolumeModel, volumeMetadata: ParcVolumeSpec}[] = volumes.map(
+            volume => {
+              const found = volumeIdToRegionMap.get(volume["@id"])
+              if (!found) return null
+
+              try {
+
+                const volumeMetadata: ParcVolumeSpec = {
+                  regions: found,
+                  parcellation,
+                  volumeSrc: volume.data.url
+                }
+                return {
+                  volume,
+                  volumeMetadata,
+                }
+              } catch (e) {
+                console.error(e)
+                return null
               }
-            }).filter(
-            v => !!v.volumeMetadata?.labelIndicies
+            }
+          ).filter(v => v?.volumeMetadata?.regions)
+
+          if (missingVolumeIds.length === 0) return of([...volumesFromParc])
+          return forkJoin(
+            missingVolumeIds.map(missingId => {
+              if (!volumeIdToRegionMap.has(missingId)) {
+                console.warn(`volumeIdToRegionMap does not have volume with id ${missingId}`)
+                return of(null as SapiVolumeModel)
+              }
+              const { region } = volumeIdToRegionMap.get(missingId)[0]
+              return this.sapi.getRegion(atlas["@id"], parcellation["@id"], region.name).getVolumeInstance(missingId).pipe(
+                catchError((err, obs) => of(null as SapiVolumeModel))
+              )
+            })
+          ).pipe(
+            map((missingVolumes: SapiVolumeModel[]) => {
+
+              const volumesFromRegion: { volume: SapiVolumeModel, volumeMetadata: ParcVolumeSpec }[] = missingVolumes.map(
+                volume => {
+                  if (!volume || !volumeIdToRegionMap.has(volume['@id'])) {
+                    return null
+                  }
+
+                  try {
+
+                    const found = volumeIdToRegionMap.get(volume['@id'])
+                    const volumeMetadata: ParcVolumeSpec = {
+                      regions: found,
+                      parcellation,
+                      volumeSrc: volume.data.url
+                    }
+                    return {
+                      volume,
+                      volumeMetadata
+                    }
+                  } catch (e) {
+                    console.error(`volume from region error`, e)
+                    return null
+                  }
+                }
+              ).filter(
+                v => !!v
+              )
+
+              return [
+                ...volumesFromParc,
+                ...volumesFromRegion
+              ]
+            })
           )
         })
       )
@@ -199,7 +263,7 @@ export class LayerCtrlEffects {
       ? this.sapi.getSpace(atlas["@id"], template["@id"]).getVolumes().pipe(
         shareReplay(1),
       )
-      : of([])
+      : of([] as SapiVolumeModel[])
     
     return forkJoin({
       tmplVolumes: spaceVols$.pipe(
diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
index c719c98ce..0c7b084eb 100644
--- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
+++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
@@ -187,11 +187,6 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnDestroy, AfterViewIni
     private layerCtrlService: NehubaLayerControlService,
     @Optional() @Inject(CLICK_INTERCEPTOR_INJECTOR) clickInterceptor: ClickInterceptor,
   ){
-    /**
-     * This **massively** improve the performance of the viewer
-     * TODO investigate why, and perhaps eventually remove the cdr.detach()
-     */
-    // this.cdr.detach()
 
     /**
      * define onclick behaviour
@@ -215,10 +210,12 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnDestroy, AfterViewIni
       this.multiNgIdsRegionsLabelIndexMap.clear()
       for (const r of regions) {
         const ngId = getParcNgId(atlas, template, parcellation, r)
-        const labelIndex = getRegionLabelIndex(atlas, template, parcellation, r)
+        if (!ngId) continue
         if (!this.multiNgIdsRegionsLabelIndexMap.has(ngId)) {
           this.multiNgIdsRegionsLabelIndexMap.set(ngId, new Map())
         }
+        const labelIndex = getRegionLabelIndex(atlas, template, parcellation, r)
+        if (!labelIndex) continue
         this.multiNgIdsRegionsLabelIndexMap.get(ngId).set(labelIndex, r)
       }
     })
diff --git a/src/viewerModule/nehuba/store/util.ts b/src/viewerModule/nehuba/store/util.ts
index cc59b9f00..14a59dc1d 100644
--- a/src/viewerModule/nehuba/store/util.ts
+++ b/src/viewerModule/nehuba/store/util.ts
@@ -1,8 +1,10 @@
-import { SapiParcellationModel } from "src/atlasComponents/sapi";
+import { SapiParcellationModel, SapiRegionModel } from "src/atlasComponents/sapi";
 
 export type ParcVolumeSpec = {
   volumeSrc: string
-  labelIndicies: number[]
   parcellation: SapiParcellationModel
-  laterality: 'left hemisphere' | 'right hemisphere' | 'whole brain'
+  regions: {
+    labelIndex: number
+    region: SapiRegionModel
+  }[]
 }
-- 
GitLab