diff --git a/src/atlasComponents/sapi/sapi.service.ts b/src/atlasComponents/sapi/sapi.service.ts
index 3648dc0b6f518b7bf147b15fb809e954faff8375..196567eb092c133b22002002e2aa3f1a15fc944f 100644
--- a/src/atlasComponents/sapi/sapi.service.ts
+++ b/src/atlasComponents/sapi/sapi.service.ts
@@ -13,6 +13,7 @@ import { SAPIFeature } from "./features";
 import { environment } from "src/environments/environment"
 import { FeatureType, PathReturn, RouteParam, SapiRoute } from "./typeV3";
 import { BoundingBox, SxplrAtlas, SxplrParcellation, SxplrRegion, SxplrTemplate, VoiFeature, Feature } from "./sxplrTypes";
+import { atlasAppearance } from "src/state";
 
 
 export const SIIBRA_API_VERSION_HEADER_KEY='x-siibra-api-version'
@@ -444,7 +445,7 @@ export class SAPI{
         bbox: JSON.stringify([bbox.minpoint, bbox.maxpoint]),
       }
     }).pipe(
-      switchMap(v => Promise.all(v.items.map(item => translateV3Entities.translateVoi(item))))
+      switchMap(v => Promise.all(v.items.map(item => translateV3Entities.translateVoiFeature(item))))
     )
   }
 
@@ -459,6 +460,29 @@ export class SAPI{
     }).toPromise()
   }
 
+  public useViewer(template: SxplrTemplate) {
+    return forkJoin({
+      voxel: this.getVoxelTemplateImage(template),
+      surface: this.getSurfaceTemplateImage(template)
+    }).pipe(
+      map(vols => {
+        if (!vols) return null
+        const { voxel, surface } = vols
+        if (voxel.length > 0 && surface.length > 0) {
+          console.error(`both voxel and surface length are > 0, this should not happen.`)
+          return atlasAppearance.const.useViewer.NOT_SUPPORTED
+        }
+        if (voxel.length > 0) {
+          return atlasAppearance.const.useViewer.NEHUBA
+        }
+        if (surface.length > 0) {
+          return atlasAppearance.const.useViewer.THREESURFER
+        }
+        return atlasAppearance.const.useViewer.NOT_SUPPORTED
+      })
+    )
+  }
+
   public getVoxelTemplateImage(template: SxplrTemplate) {
     return from(translateV3Entities.translateSpaceToVolumeImage(template))
   }
diff --git a/src/atlasComponents/sapi/sxplrTypes.ts b/src/atlasComponents/sapi/sxplrTypes.ts
index 53320ec5645f56806c0098f88266c3020efed002..c9c2cb1bb2a1225c7b8b780a282e5d281f65441e 100644
--- a/src/atlasComponents/sapi/sxplrTypes.ts
+++ b/src/atlasComponents/sapi/sxplrTypes.ts
@@ -47,7 +47,7 @@ export type AdditionalInfo = {
 }
 
 type Location = {
-  space: SxplrTemplate
+  readonly space: SxplrTemplate
 }
 type LocTuple = [number, number, number]
 
@@ -104,6 +104,11 @@ type DataFrame = {
 
 export type VoiFeature = {
   bbox: BoundingBox
+  ngVolume: {
+    url: string
+    transform: number[][]
+    info: Record<string, any>
+  }
 } & Feature
 
 type CorticalDataType = number
diff --git a/src/atlasComponents/sapi/translateV3.ts b/src/atlasComponents/sapi/translateV3.ts
index 82584e1193b23c56d4066786b2050d8af49c57c7..d0334963b910833bf380b5a7fcd72d5b0c7dc15f 100644
--- a/src/atlasComponents/sapi/translateV3.ts
+++ b/src/atlasComponents/sapi/translateV3.ts
@@ -1,5 +1,5 @@
 import {
-  SxplrAtlas, SxplrParcellation, SxplrTemplate, SxplrRegion, NgLayerSpec, NgPrecompMeshSpec, NgSegLayerSpec, VoiFeature, Point, TemplateDefaultImage, TThreeSurferMesh, TThreeMesh, LabelledMap, CorticalFeature, Feature, TabularFeature, GenericInfo
+  SxplrAtlas, SxplrParcellation, SxplrTemplate, SxplrRegion, NgLayerSpec, NgPrecompMeshSpec, NgSegLayerSpec, VoiFeature, Point, TThreeMesh, LabelledMap, CorticalFeature, Feature, TabularFeature, GenericInfo, BoundingBox
 } from "./sxplrTypes"
 import { PathReturn } from "./typeV3"
 import { hexToRgb } from 'common/util'
@@ -50,16 +50,20 @@ class TranslateV3 {
   }
 
   #templateMap: Map<string, PathReturn<"/spaces/{space_id}">> = new Map()
+  #sxplrTmplMap: Map<string, SxplrTemplate> = new Map()
   retrieveTemplate(template:SxplrTemplate): PathReturn<"/spaces/{space_id}"> {
     return this.#templateMap.get(template.id)
   }
   async translateTemplate(template:PathReturn<"/spaces/{space_id}">): Promise<SxplrTemplate> {
+
     this.#templateMap.set(template["@id"], template)
-    return {
+    const tmpl = {
       id: template["@id"],
       name: template.fullName,
-      type: "SxplrTemplate"
+      type: "SxplrTemplate" as const
     }
+    this.#sxplrTmplMap.set(tmpl.id, tmpl)
+    return tmpl
   }
 
   /**
@@ -93,6 +97,41 @@ class TranslateV3 {
     }
   }
 
+
+  #hasNoFragment(input: Record<string, unknown>): input is Record<string, string> {
+    for (const key in input) {
+      if (typeof input[key] !== 'string') return false
+    }
+    return true
+  }
+  async #extractNgPrecompUnfrag(input: Record<string, unknown>) {
+    if (!this.#hasNoFragment(input)) {
+      throw new Error(`#extractNgPrecompUnfrag can only handle unfragmented volume`)
+    }
+    
+    const returnObj: Record<string, {
+      url: string,
+      transform: number[][],
+      info: Record<string, any>
+    }> = {}
+    for (const key in input) {
+      if (key !== 'neuroglancer/precomputed') {
+        continue
+      }
+      const url = input[key]
+      const [ transform, info ] = await Promise.all([
+        fetch(`${url}/transform.json`).then(res => res.json()) as Promise<number[][]>,
+        fetch(`${url}/info`).then(res => res.json()) as Promise<Record<string, any>>,
+      ])
+      returnObj[key] = {
+        url: input[key],
+        transform: transform,
+        info: info,
+      }
+    }
+    return returnObj
+  }
+
   async translateSpaceToVolumeImage(template: SxplrTemplate): Promise<NgLayerSpec[]> {
     if (!template) return []
     const space = this.retrieveTemplate(template)
@@ -103,42 +142,20 @@ class TranslateV3 {
     for (const defaultImage of validImages) {
       
       const { providedVolumes } = defaultImage
-  
-      const { ['neuroglancer/precomputed']: precomputedVol } = providedVolumes
+      const { "neuroglancer/precomputed": precomputedVol, ...rest } = await this.#extractNgPrecompUnfrag(providedVolumes)
+      
       if (!precomputedVol) {
         console.error(`neuroglancer/precomputed data source has not been found!`)
         continue
       }
-      if (typeof precomputedVol === "object") {
-        console.error(`template default image cannot have fragment`)
-        continue
+      const { transform, info: _info, url } = precomputedVol
+      const { resolution, size } = _info.scales[0]
+      const info = {
+        voxel: size as [number, number, number],
+        real: [0, 1, 2].map(idx => resolution[idx] * size[idx]) as [number, number, number]
       }
-      const [transform, info] = await Promise.all([
-        (async () => {
-          const resp = await fetch(`${precomputedVol}/transform.json`)
-          if (resp.status >= 400) {
-            console.error(`cannot retrieve transform: ${resp.status}`)
-            return null
-          }
-          const transform: number[][] = await resp.json()
-          return transform
-        })(),
-        (async () => {
-          const resp = await fetch(`${precomputedVol}/info`)
-          if (resp.status >= 400) {
-            console.error(`cannot retrieve transform: ${resp.status}`)
-            return null
-          }
-          const info = await resp.json()
-          const { resolution, size } = info.scales[0]
-          return {
-            voxel: info.scales[0].size as [number, number, number],
-            real: [0, 1, 2].map(idx => resolution[idx] * size[idx]) as [number, number, number],
-          }
-        })()
-      ])
       returnObj.push({
-        source: `precomputed://${precomputedVol}`,
+        source: `precomputed://${url}`,
         transform,
         info,
       })
@@ -250,7 +267,7 @@ class TranslateV3 {
       if (url in nglayerSpecMap){
         segLayerSpec = nglayerSpecMap[url]
       } else {
-        const resp = await fetch(`${url}/transform.json`)
+        const resp = await this.cFetch(`${url}/transform.json`)
         const transform = await resp.json()
         segLayerSpec = {
           layer: {
@@ -301,6 +318,36 @@ class TranslateV3 {
     return nglayerSpecMap
   }
 
+  #cFetchCache = new Map<string, string>()
+  /**
+   * Cached fetch
+   * 
+   * Since translate v3 has no dependency on any angular components.
+   * We couldn't cache the response. This is a monkey patch to allow for caching of queries.
+   * @param url: string
+   * @returns { status: number, json: () => Promise<unknown> }
+   */
+  async cFetch(url: string): Promise<{ status: number, json?: () => Promise<any> }> {
+    
+    if (!this.#cFetchCache.has(url)) {
+      const resp = await fetch(url)
+      if (resp.status >= 400) {
+        return {
+          status: resp.status,
+        }
+      }
+      const text = await resp.text()
+      this.#cFetchCache.set(url, text)
+    }
+    const cachedText = this.#cFetchCache.get(url)
+    return {
+      status: 200,
+      json() {
+        return Promise.resolve(JSON.parse(cachedText))
+      }
+    }
+  }
+
   async translateSpaceToAuxMesh(template: SxplrTemplate): Promise<NgPrecompMeshSpec[]>{
     if (!template) return []
     const space = this.retrieveTemplate(template)
@@ -327,7 +374,7 @@ class TranslateV3 {
         console.error(`Expecting exactly two fragments by splitting precompmeshvol, but got ${splitPrecompMeshVol.length}`)
         continue
       }
-      const resp = await fetch(`${splitPrecompMeshVol[0]}/transform.json`)
+      const resp = await this.cFetch(`${splitPrecompMeshVol[0]}/transform.json`)
       if (resp.status >= 400) {
         console.error(`cannot retrieve transform: ${resp.status}`)
         continue
@@ -345,30 +392,15 @@ class TranslateV3 {
     return returnObj
   }
 
-  async translatePoint(point: components["schemas"]["CoordinatePointModel"]): Promise<Point> {
-    const sapiSpace = this.#templateMap.get(point.coordinateSpace['@id'])
-    const space = await this.translateTemplate(sapiSpace)
-    return {
-      space,
-      loc: point.coordinates.map(v => v.value) as [number, number, number]
+  async #translatePoint(point: components["schemas"]["CoordinatePointModel"]): Promise<Point> {
+    const getTmpl = (id: string) => {
+      return this.#sxplrTmplMap.get(id)
     }
-  }
-
-  async translateVoi(voi: PathReturn<"/feature/Image/{feature_id}">): Promise<VoiFeature> {
-    const { boundingbox } = voi
-    const { loc: center, space } = await this.translatePoint(boundingbox.center)
-    const { loc: maxpoint } = await this.translatePoint(boundingbox.maxpoint)
-    const { loc: minpoint } = await this.translatePoint(boundingbox.minpoint)
     return {
-      bbox: {
-        center,
-        maxpoint,
-        minpoint,
-        space
-      },
-      name: voi.name,
-      desc: voi.description,
-      id: voi.id
+      loc: point.coordinates.map(v => v.value) as [number, number, number],
+      get space() {
+        return getTmpl(point.coordinateSpace['@id'])
+      }
     }
   }
 
@@ -376,6 +408,10 @@ class TranslateV3 {
     if (this.#isTabular(feat)) {
       return await this.translateTabularFeature(feat)
     }
+    if (this.#isVoi(feat)) {
+      return await this.translateVoiFeature(feat)
+    }
+    
     return await this.translateBaseFeature(feat)
   }
 
@@ -395,6 +431,35 @@ class TranslateV3 {
     }
   }
 
+  #isVoi(feat: unknown): feat is PathReturn<"/feature/Image/{feature_id}"> {
+    return feat['@type'].includes("feature/volume_of_interest")
+  }
+
+  async translateVoiFeature(feat: PathReturn<"/feature/Image/{feature_id}">): Promise<VoiFeature> {
+    const [superObj, { loc: center }, { loc: maxpoint }, { loc: minpoint }, { "neuroglancer/precomputed": precomputedVol }] = await Promise.all([
+      this.translateBaseFeature(feat),
+      this.#translatePoint(feat.boundingbox.center),
+      this.#translatePoint(feat.boundingbox.maxpoint),
+      this.#translatePoint(feat.boundingbox.minpoint),
+      await this.#extractNgPrecompUnfrag(feat.volume.providedVolumes),
+    ])
+    const { ['@id']: spaceId } = feat.boundingbox.space
+    const getSpace = (id: string) => this.#sxplrTmplMap.get(id)
+    const bbox: BoundingBox = {
+      center,
+      maxpoint,
+      minpoint,
+      get space() {
+        return getSpace(spaceId)
+      }
+    }
+    return {
+      ...superObj,
+      bbox,
+      ngVolume: precomputedVol
+    }
+  }
+
   #isTabular(feat: unknown): feat is PathReturn<"/feature/Tabular/{feature_id}"> {
     return feat["@type"].includes("feature/tabular")
   }
diff --git a/src/features/feature-view/feature-view.component.html b/src/features/feature-view/feature-view.component.html
index b91b00901deb47df55e74bf79da97ee14aaba437..7bbe648781733121949f45fcd7e6d2741f9c9f6f 100644
--- a/src/features/feature-view/feature-view.component.html
+++ b/src/features/feature-view/feature-view.component.html
@@ -66,3 +66,15 @@
     <tr mat-row *matRowDef="let row; columns: columns$ | async;"></tr>
   </table>
 </ng-template>
+
+
+<!-- voi special view -->
+<ng-template [ngIf]="voi$ | async" let-voi>
+  <ng-layer-ctl
+    [ng-layer-ctl-name]="voi.ngVolume.url"
+    [ng-layer-ctl-src]="voi.ngVolume.url"
+    [ng-layer-ctl-transform]="voi.ngVolume.transform"
+    [ng-layer-ctl-info]="voi.ngVolume.info"
+    [ng-layer-ctl-opacity]="1.0">
+  </ng-layer-ctl>
+</ng-template>
diff --git a/src/features/feature-view/feature-view.component.ts b/src/features/feature-view/feature-view.component.ts
index 7c66b0270412086ae7b154581bd9a0d8632a45ec..9cd37f0ac72e1b127d72c03663b222cbaaf9d438 100644
--- a/src/features/feature-view/feature-view.component.ts
+++ b/src/features/feature-view/feature-view.component.ts
@@ -2,12 +2,16 @@ import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit } from '@a
 import { BehaviorSubject, Observable } from 'rxjs';
 import { map } from 'rxjs/operators';
 import { SAPI } from 'src/atlasComponents/sapi/sapi.service';
-import { Feature, TabularFeature } from 'src/atlasComponents/sapi/sxplrTypes';
+import { Feature, TabularFeature, VoiFeature } from 'src/atlasComponents/sapi/sxplrTypes';
 
 function isTabularData(feature: unknown): feature is TabularFeature<number|string|number[]> {
   return !!feature['index'] && !!feature['columns']
 }
 
+function isVoiData(feature: unknown): feature is VoiFeature {
+  return !!feature['bbox']
+}
+
 @Component({
   selector: 'sxplr-feature-view',
   templateUrl: './feature-view.component.html',
@@ -20,8 +24,9 @@ export class FeatureViewComponent implements OnChanges {
   feature: Feature
 
   busy$ = new BehaviorSubject<boolean>(false)
-
-  tabular$: BehaviorSubject<TabularFeature<number|string|number[]>> = new BehaviorSubject(null)
+  
+  tabular$ = new BehaviorSubject<TabularFeature<number|string|number[]>>(null)
+  voi$ = new BehaviorSubject<VoiFeature>(null)
   columns$: Observable<string[]> = this.tabular$.pipe(
     map(data => data
       ? ['index', ...data.columns]
@@ -30,8 +35,11 @@ export class FeatureViewComponent implements OnChanges {
   constructor(private sapi: SAPI) { }
 
   ngOnChanges(): void {
+    
+    this.voi$.next(null)
     this.tabular$.next(null)
     this.busy$.next(true)
+
     this.sapi.getV3FeatureDetailWithId(this.feature.id).subscribe(
       val => {
         this.busy$.next(false)
@@ -39,6 +47,9 @@ export class FeatureViewComponent implements OnChanges {
         if (isTabularData(val)) {
           this.tabular$.next(val)
         }
+        if (isVoiData(val)) {
+          this.voi$.next(val)
+        }
       },
       () => this.busy$.next(false)
     )
diff --git a/src/features/module.ts b/src/features/module.ts
index 1aedc1d8a8695cf13cb2b9830d05438129837f1b..3795cf74814af3283e23d2b3cd22c39af8419d64 100644
--- a/src/features/module.ts
+++ b/src/features/module.ts
@@ -21,6 +21,7 @@ import { MarkdownModule } from "src/components/markdown";
 import { MatTableModule } from "@angular/material/table";
 import { FeatureViewComponent } from "./feature-view/feature-view.component";
 import { TransformPdToDsPipe } from "./transform-pd-to-ds.pipe";
+import { NgLayerCtlModule } from "src/viewerModule/nehuba/ngLayerCtlModule/module";
 
 @NgModule({
   imports: [
@@ -39,6 +40,7 @@ import { TransformPdToDsPipe } from "./transform-pd-to-ds.pipe";
     MatDividerModule,
     MarkdownModule,
     MatTableModule,
+    NgLayerCtlModule,
   ],
   declarations: [
     EntryComponent,
diff --git a/src/routerModule/routeStateTransform.service.ts b/src/routerModule/routeStateTransform.service.ts
index 7f8a71a0611ecd91d4d7640597e924f10dc479af..deab0f8b9733a703c9279a57c49a299c966a8bce 100644
--- a/src/routerModule/routeStateTransform.service.ts
+++ b/src/routerModule/routeStateTransform.service.ts
@@ -57,6 +57,7 @@ export class RouteStateTransformSvc {
       this.sapi.getParcRegions(selectedParcellationId).toPromise(),
     ])
 
+    const userViewer = await this.sapi.useViewer(selectedTemplate).toPromise()
     
     const selectedRegions = await (async () => {
       if (!selectedRegionIds) return []
@@ -127,12 +128,12 @@ export class RouteStateTransformSvc {
       selectedTemplate,
       selectedParcellation,
       selectedRegions,
-      allParcellationRegions
+      allParcellationRegions, 
+      userViewer
     }
   }
 
   async cvtRouteToState(fullPath: UrlTree) {
-
     const returnState: MainState = structuredClone(defaultState)
     const pathFragments: UrlSegment[] = fullPath.root.hasChildren()
       ? fullPath.root.children['primary'].segments
@@ -217,13 +218,15 @@ export class RouteStateTransformSvc {
     }
 
     try {
-      const { selectedAtlas, selectedParcellation, selectedRegions = [], selectedTemplate, allParcellationRegions } = await this.getATPR(returnObj as TUrlPathObj<string[], TUrlAtlas<string[]>>)
+      const { selectedAtlas, selectedParcellation, selectedRegions = [], selectedTemplate, allParcellationRegions, userViewer } = await this.getATPR(returnObj as TUrlPathObj<string[], TUrlAtlas<string[]>>)
       returnState["[state.atlasSelection]"].selectedAtlas = selectedAtlas
       returnState["[state.atlasSelection]"].selectedParcellation = selectedParcellation
       returnState["[state.atlasSelection]"].selectedTemplate = selectedTemplate
+
       returnState["[state.atlasSelection]"].selectedRegions = selectedRegions || []
       returnState["[state.atlasSelection]"].selectedParcellationAllRegions = allParcellationRegions || []
       returnState["[state.atlasSelection]"].navigation = parsedNavObj
+      returnState["[state.atlasAppearance]"].useViewer = userViewer
     } catch (e) {
       // if error, show error on UI?
       console.error(`parse template, parc, region error`, e)
diff --git a/src/routerModule/router.service.ts b/src/routerModule/router.service.ts
index 7a2047489958038b0d4dcd36d2759ec7ffdc7e4f..37d6caec7354d1343031f1760f48f99124ec4409 100644
--- a/src/routerModule/router.service.ts
+++ b/src/routerModule/router.service.ts
@@ -5,7 +5,7 @@ import { NavigationEnd, Router } from '@angular/router'
 import { Store } from "@ngrx/store";
 import { catchError, debounceTime, distinctUntilChanged, filter, map, mapTo, shareReplay, startWith, switchMap, switchMapTo, take, withLatestFrom } from "rxjs/operators";
 import { encodeCustomState, decodeCustomState, verifyCustomState } from "./util";
-import { BehaviorSubject, combineLatest, concat, from, merge, Observable, of, timer } from 'rxjs'
+import { BehaviorSubject, combineLatest, concat, forkJoin, from, merge, Observable, of, timer } from 'rxjs'
 import { scan } from 'rxjs/operators'
 import { RouteStateTransformSvc } from "./routeStateTransform.service";
 import { SAPI } from "src/atlasComponents/sapi";
@@ -133,31 +133,33 @@ export class RouterService {
       switchMap(() => navEnd$),
       map(navEv => navEv.urlAfterRedirects),
       switchMap(url =>
-        routeToStateTransformSvc.cvtRouteToState(
-          router.parseUrl(
-            url
-          )
-        ).then(stateFromRoute => {
-          return {
-            url,
-            stateFromRoute
-          }
-        })
+        forkJoin([
+          routeToStateTransformSvc.cvtRouteToState(
+            router.parseUrl(
+              url
+            )
+          ).then(stateFromRoute => {
+            return {
+              url,
+              stateFromRoute
+            }
+          }),
+          store$.pipe(
+            switchMap(state => 
+              from(routeToStateTransformSvc.cvtStateToRoute(state)).pipe(
+                catchError(() => of(``))
+              )
+            )
+          ),
+        ]),
       ),
       withLatestFrom(
-        store$.pipe(
-          switchMap(state => 
-            from(routeToStateTransformSvc.cvtStateToRoute(state)).pipe(
-              catchError(() => of(``))
-            )
-          )
-        ),
         this.customRoute$.pipe(
           startWith({})
         )
       )
     ).subscribe(arg => {
-      const [{ stateFromRoute, url }, _routeFromState, customRoutes] = arg
+      const [[{ stateFromRoute, url }, _routeFromState ], customRoutes] = arg
       const fullPath = url
       let routeFromState = _routeFromState
       for (const key in customRoutes) {
diff --git a/src/viewerModule/module.ts b/src/viewerModule/module.ts
index 96cdcfe3911b20d5c08f963a3d338dda26d834f6..8d2e4006fcb1d99787f8a9517c6751d6d3980209 100644
--- a/src/viewerModule/module.ts
+++ b/src/viewerModule/module.ts
@@ -31,6 +31,7 @@ import { LeapModule } from "./leap/module";
 import { environment } from "src/environments/environment"
 import { ATPSelectorModule } from "src/atlasComponents/sapiViews/core/rich/ATPSelector";
 import { FeatureModule } from "src/features";
+import { NgLayerCtlModule } from "./nehuba/ngLayerCtlModule/module";
 
 @NgModule({
   imports: [
@@ -54,6 +55,7 @@ import { FeatureModule } from "src/features";
     ShareModule,
     ATPSelectorModule,
     FeatureModule,
+    NgLayerCtlModule,
     ...(environment.ENABLE_LEAP_MOTION ? [LeapModule] : [])
   ],
   declarations: [
diff --git a/src/viewerModule/nehuba/constants.ts b/src/viewerModule/nehuba/constants.ts
index 03e242246e76227e2e0d0687afa6019b611e298f..667c5b7147246a276e3a02f59cd436851db83932 100644
--- a/src/viewerModule/nehuba/constants.ts
+++ b/src/viewerModule/nehuba/constants.ts
@@ -9,52 +9,6 @@ export interface IRegion {
   rgb?: [number, number, number]
 }
 
-export function getMultiNgIdsRegionsLabelIndexMap(parcellation: any = {}, inheritAttrsOpt: any = { ngId: 'root' }): Map<string, Map<number, IRegion>> {
-  const map: Map<string, Map<number, any>> = new Map()
-  
-  const inheritAttrs = Object.keys(inheritAttrsOpt)
-  if (inheritAttrs.indexOf('children') >=0 ) throw new Error(`children attr cannot be inherited`)
-
-  const processRegion = (region: any) => {
-    const { ngId: rNgId } = region
-    const labelIndex = Number(region.labelIndex)
-    if (labelIndex && rNgId) {
-      const existingMap = map.get(rNgId)
-      if (!existingMap) {
-        const newMap = new Map()
-        newMap.set(labelIndex, region)
-        map.set(rNgId, newMap)
-      } else {
-        existingMap.set(labelIndex, region)
-      }
-    }
-
-    if (region.children && Array.isArray(region.children)) {
-      for (const r of region.children) {
-        const copiedRegion = { ...r }
-        for (const attr of inheritAttrs){
-          copiedRegion[attr] = copiedRegion[attr] || region[attr] || parcellation[attr]
-        }
-        processRegion(copiedRegion)
-      }
-    }
-  }
-
-  if (!parcellation) throw new Error(`parcellation needs to be defined`)
-  if (!parcellation.regions) throw new Error(`parcellation.regions needs to be defined`)
-  if (!Array.isArray(parcellation.regions)) throw new Error(`parcellation.regions needs to be an array`)
-
-  for (const region of parcellation.regions){
-    const copiedregion = { ...region }
-    for (const attr of inheritAttrs){
-      copiedregion[attr] = copiedregion[attr] || parcellation[attr]
-    }
-    processRegion(copiedregion)
-  }
-
-  return map
-}
-
 export interface IMeshesToLoad {
   labelIndicies: number[]
   layer: {
@@ -62,6 +16,22 @@ export interface IMeshesToLoad {
   }
 }
 
+export type TVec4 = number[]
+export type TVec3 = number[]
+
+export interface INavObj {
+  position: TVec3
+  orientation: TVec4
+  perspectiveOrientation: TVec4
+  perspectiveZoom: number
+  zoom: number
+}
+
+export type TNehubaViewerUnit = {
+  viewerPositionChange: Observable<INavObj>
+  setNavigationState(nav: Partial<INavObj> & { positionReal?: boolean }): void
+}
+
 export const SET_MESHES_TO_LOAD = new InjectionToken<Observable<IMeshesToLoad>>('SET_MESHES_TO_LOAD')
 
 export const PMAP_LAYER_NAME = 'regional-pmap'
diff --git a/src/viewerModule/nehuba/index.ts b/src/viewerModule/nehuba/index.ts
index 65cb900d6d7293034929898b2515f02082d04b37..7ea67235487ac5d9209ca9bceccc27e9517807e7 100644
--- a/src/viewerModule/nehuba/index.ts
+++ b/src/viewerModule/nehuba/index.ts
@@ -2,3 +2,4 @@ export { NehubaGlueCmp } from "./nehubaViewerGlue/nehubaViewerGlue.component"
 export { NehubaViewerTouchDirective } from "./nehubaViewerInterface/nehubaViewerTouch.directive"
 export { NehubaModule } from "./module"
 export { NehubaViewerUnit } from "./nehubaViewer/nehubaViewer.component"
+export { NEHUBA_INSTANCE_INJTKN } from "./util"
diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts
index 20288e1d0ad406f02646aceba89e26ae530ac226..5c5a85fd3a6b143a805fbf2cf3017a089de3eeed 100644
--- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts
+++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts
@@ -3,12 +3,8 @@ import { createEffect } from "@ngrx/effects";
 import { select, Store } from "@ngrx/store";
 import { forkJoin, from, of } from "rxjs";
 import { switchMap, withLatestFrom, filter, catchError, map, debounceTime, shareReplay, distinctUntilChanged, startWith, pairwise, tap } from "rxjs/operators";
-import { Feature, NgSegLayerSpec, SxplrAtlas, SxplrParcellation, SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes";
+import { Feature, NgSegLayerSpec, SxplrAtlas, SxplrParcellation, SxplrTemplate, VoiFeature } from "src/atlasComponents/sapi/sxplrTypes";
 import { SAPI } from "src/atlasComponents/sapi"
-import { 
-  SapiFeatureModel,
-  SapiSpatialFeatureModel,
-} from "src/atlasComponents/sapi/typeV3";
 import { atlasAppearance, atlasSelection, userInteraction } from "src/state";
 import { arrayEqual } from "src/util/array";
 import { EnumColorMapName } from "src/util/colorMaps";
@@ -19,16 +15,13 @@ import { getParcNgId } from "../config.service";
 
 @Injectable()
 export class LayerCtrlEffects {
-  static TransformVolumeModel(volumeModel: SapiSpatialFeatureModel['volume']): atlasAppearance.const.NgLayerCustomLayer[] {
-    /**
-     * TODO implement
-     */
-    throw new Error(`IMPLEMENT ME`)
-    // for (const volumeFormat in volumeModel.providedVolumes) {
-
-    // }
-    
-    return []
+  static TransformVolumeModel(volumeModel: VoiFeature['ngVolume']): atlasAppearance.const.NgLayerCustomLayer[] {    
+    return [{
+      clType: "customlayer/nglayer",
+      id: volumeModel.url,
+      source: `precomputed://${volumeModel.url}`,
+      transform: volumeModel.transform,
+    }]
   }
 
   #onATP$ = this.store.pipe(
@@ -101,16 +94,21 @@ export class LayerCtrlEffects {
     map(([ prev, curr ]) => {
       const removeLayers: atlasAppearance.const.NgLayerCustomLayer[] = []
       const addLayers: atlasAppearance.const.NgLayerCustomLayer[] = []
-      if (prev?.["@type"]?.includes("feature/volume_of_interest")) {
-        const prevVoi = prev as SapiSpatialFeatureModel
+      
+      /**
+       * TODO: use proper guard functions
+       */
+      if (!!prev?.['bbox']) {
+        const prevVoi = prev as VoiFeature
+        prevVoi.bbox
         removeLayers.push(
-          ...LayerCtrlEffects.TransformVolumeModel(prevVoi.volume)
+          ...LayerCtrlEffects.TransformVolumeModel(prevVoi.ngVolume)
         )
       }
-      if (curr?.["@type"]?.includes("feature/volume_of_interest")) {
-        const currVoi = curr as SapiSpatialFeatureModel
+      if (!!curr?.['bbox']) {
+        const currVoi = curr as VoiFeature
         addLayers.push(
-          ...LayerCtrlEffects.TransformVolumeModel(currVoi.volume)
+          ...LayerCtrlEffects.TransformVolumeModel(currVoi.ngVolume)
         )
       }
       return { removeLayers, addLayers }
diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts
index b1c82b156dfa5888c6b828b944641c66bde8d7b6..971f599b69ece635aab52fc9290c3ae4b954703d 100644
--- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts
+++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts
@@ -378,14 +378,11 @@ export class NehubaLayerControlService implements OnDestroy{
     this.customLayers$.pipe(
       map(cl => {
         const otherColormapExist = cl.filter(l => l.clType === "customlayer/colormap").length > 0
-        const pmapExist = cl.filter(l => l.clType === "customlayer/nglayer").length > 0
-        return pmapExist && !otherColormapExist
+        const otherLayerNames = cl.filter(l => l.clType === "customlayer/nglayer").map(l => l.id)
+        return otherColormapExist
+          ? []
+          : otherLayerNames
       }),
-      distinctUntilChanged(),
-      map(flag => flag
-        ? [ PMAP_LAYER_NAME ]
-        : []
-      )
     )
   ]).pipe(
     map(([ expectedLayerNames, customLayerNames, pmapName ]) => [...expectedLayerNames, ...customLayerNames, ...pmapName, ...AnnotationLayer.Map.keys()])
diff --git a/src/viewerModule/nehuba/module.ts b/src/viewerModule/nehuba/module.ts
index e3cc3aeb2bdead46e359b35d21ad773849411301..8243707dae27438a851e0ccd38cf39fa428519ff 100644
--- a/src/viewerModule/nehuba/module.ts
+++ b/src/viewerModule/nehuba/module.ts
@@ -21,7 +21,6 @@ import { StateModule } from "src/state";
 import { AuthModule } from "src/auth";
 import {QuickTourModule} from "src/ui/quickTour/module";
 import { WindowResizeModule } from "src/util/windowResize";
-import { NgLayerCtrlCmp } from "./ngLayerCtl/ngLayerCtrl.component";
 import { EffectsModule } from "@ngrx/effects";
 import { MeshEffects } from "./mesh.effects/mesh.effects";
 import { NehubaLayoutOverlayModule } from "./layoutOverlay";
@@ -66,7 +65,6 @@ import { NehubaUserLayerModule } from "./userLayers";
     NehubaViewerTouchDirective,
     NehubaGlueCmp,
     StatusCardComponent,
-    NgLayerCtrlCmp,
     NehubaViewerContainer,
   ],
   exports: [
@@ -74,7 +72,6 @@ import { NehubaUserLayerModule } from "./userLayers";
     NehubaViewerTouchDirective,
     NehubaGlueCmp,
     StatusCardComponent,
-    NgLayerCtrlCmp,
   ],
   providers: [
     
@@ -94,9 +91,6 @@ import { NehubaUserLayerModule } from "./userLayers";
       deps: [ NgAnnotationService ]
     },
     NgAnnotationService
-  ],
-  schemas: [
-    CUSTOM_ELEMENTS_SCHEMA
   ]
 })
 
diff --git a/src/viewerModule/nehuba/navigation.service/index.ts b/src/viewerModule/nehuba/navigation.service/index.ts
index ac47740ee69a641d7ef23ed05e27eeb80a945824..8b137891791fe96927ad78e64b0aad7bded08bdc 100644
--- a/src/viewerModule/nehuba/navigation.service/index.ts
+++ b/src/viewerModule/nehuba/navigation.service/index.ts
@@ -1,7 +1 @@
-export {
-  NehubaNavigationService
-} from './navigation.service'
 
-export {
-  INavObj
-} from './navigation.util'
diff --git a/src/viewerModule/nehuba/navigation.service/navigation.base.service.ts b/src/viewerModule/nehuba/navigation.service/navigation.base.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dd82454761ad41d4ccb18f8a37ea3abbabe3c6f5
--- /dev/null
+++ b/src/viewerModule/nehuba/navigation.service/navigation.base.service.ts
@@ -0,0 +1,46 @@
+import { Inject, Injectable, Optional } from "@angular/core";
+import { concat, EMPTY, NEVER, Observable, of } from "rxjs";
+import { delay, exhaustMap, shareReplay, switchMap, take, tap } from "rxjs/operators";
+import { TNehubaViewerUnit } from "../constants";
+import { NEHUBA_INSTANCE_INJTKN } from "../util";
+
+@Injectable({
+  providedIn: 'root'
+})
+export class NavigationBaseSvc{
+  
+  public nehubaViewerUnit$ = this.nehubaInst$
+    ? this.nehubaInst$.pipe(
+      switchMap(val => val ? of(val): EMPTY)
+    )
+    : NEVER
+
+  public viewerNavLock$: Observable<boolean> = this.nehubaViewerUnit$.pipe(
+    switchMap(nvUnit => 
+      nvUnit.viewerPositionChange.pipe(
+        exhaustMap(() => concat(
+          of(true),
+          concat(
+            /**
+             * in the event that viewerPositionChange only emits once (such is the case on init)
+             */
+            of(false),
+            nvUnit.viewerPositionChange,
+          ).pipe(
+            switchMap(() => 
+              of(false).pipe(
+                delay(160)
+              )
+            ),
+            take(1)
+          ),
+        ))
+      )
+    ),
+    shareReplay(1),
+  )
+  constructor(
+    @Optional() @Inject(NEHUBA_INSTANCE_INJTKN) private nehubaInst$: Observable<TNehubaViewerUnit>,
+  ){
+  }
+}
diff --git a/src/viewerModule/nehuba/navigation.service/navigation.effects.ts b/src/viewerModule/nehuba/navigation.service/navigation.effects.ts
index fd615fb7e25ba2141e534a71b836b2d923ff9753..a303c4526490b224cc80c5215b06a1fdaa27ea08 100644
--- a/src/viewerModule/nehuba/navigation.service/navigation.effects.ts
+++ b/src/viewerModule/nehuba/navigation.service/navigation.effects.ts
@@ -1,21 +1,18 @@
-import { Inject, Injectable, OnDestroy } from "@angular/core";
+import { Injectable, OnDestroy } from "@angular/core";
 import { Actions, createEffect, ofType } from "@ngrx/effects";
 import { select, Store } from "@ngrx/store";
-import { combineLatest, Observable, Subscription } from "rxjs";
-import { filter, map, mapTo, tap, withLatestFrom } from "rxjs/operators";
+import { combineLatest, NEVER, of, Subscription } from "rxjs";
+import { debounce, distinctUntilChanged, filter, map, mapTo, skipWhile, switchMap, tap, withLatestFrom } from "rxjs/operators";
 import { atlasSelection, MainState, userInterface, userPreference } from "src/state"
 import { CYCLE_PANEL_MESSAGE } from "src/util/constants";
 import { timedValues } from "src/util/generator";
-import { NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component";
-import { NEHUBA_INSTANCE_INJTKN } from "../util";
-import { navAdd, navMul } from "./navigation.util";
+import { NavigationBaseSvc } from "./navigation.base.service";
+import { navAdd, navMul, navObjEqual } from "./navigation.util";
 
 @Injectable()
 export class NehubaNavigationEffects implements OnDestroy{
 
   private subscription: Subscription[] = []
-  private nehubaInst: NehubaViewerUnit
-  private rafRef: number
 
   /**
    * This is an implementation which reconciles local state with the global navigation state.
@@ -27,64 +24,56 @@ export class NehubaNavigationEffects implements OnDestroy{
    *   and update global state accordingly.
    * - This effect updates the internal navigation state. It should leave reporting any diff to the local viewer's native implementation.
    */
-  onNavigateTo = createEffect(() => this.action.pipe(
-    ofType(atlasSelection.actions.navigateTo),
-    filter(() => !!this.nehubaInst),
-    withLatestFrom(
-      this.store.pipe(
-        select(userPreference.selectors.useAnimation)
+  onNavigateTo = createEffect(() => this.baseSvc.nehubaViewerUnit$.pipe(
+    switchMap(nehubaInst => this.action.pipe(
+      ofType(atlasSelection.actions.navigateTo),
+      withLatestFrom(
+        this.store.pipe(
+          select(userPreference.selectors.useAnimation)
+        ),
+        this.store.pipe(
+          select(atlasSelection.selectors.navigation)
+        )
       ),
-      this.store.pipe(
-        select(atlasSelection.selectors.navigation)
-      )
-    ),
-    tap(([{ navigation, animation, physical }, globalAnimationFlag, currentNavigation]) => {
-      if (!animation || !globalAnimationFlag) {
-        this.nehubaInst.setNavigationState({
-          ...navigation,
-          positionReal: physical
-        })
-        return
-      }
-
-      const gen = timedValues()
-      const src = currentNavigation
-
-      const dest = {
-        ...src,
-        ...navigation
-      }
-
-      const delta = navAdd(dest, navMul(src, -1))
-
-      const animate = () => {
-        
-        /**
-         * if nehubaInst becomes nullish whilst animation is running
-         */  
-        if (!this.nehubaInst) {
-          this.rafRef = null
+      tap(([{ navigation, animation, physical }, globalAnimationFlag, currentNavigation]) => {
+        if (!animation || !globalAnimationFlag) {
+          nehubaInst.setNavigationState({
+            ...navigation,
+            positionReal: physical
+          })
           return
         }
-
-        const next = gen.next()
-        const d =  next.value
-
-        const n = navAdd(src, navMul(delta, d))
-        this.nehubaInst.setNavigationState({
-          ...n,
-          positionReal: physical
-        })
-
-        if ( !next.done ) {
-          this.rafRef = requestAnimationFrame(() => animate())
-        } else {
-          this.rafRef = null
+  
+        const gen = timedValues()
+        const src = currentNavigation
+  
+        const dest = {
+          ...src,
+          ...navigation
         }
-      }
-      this.rafRef = requestAnimationFrame(() => animate())
-
-    })
+  
+        const delta = navAdd(dest, navMul(src, -1))
+  
+        const animate = () => {
+          
+  
+          const next = gen.next()
+          const d =  next.value
+  
+          const n = navAdd(src, navMul(delta, d))
+          nehubaInst.setNavigationState({
+            ...n,
+            positionReal: physical
+          })
+  
+          if ( !next.done ) {
+            requestAnimationFrame(() => animate())
+          }
+        }
+        requestAnimationFrame(() => animate())
+  
+      })
+    )),
   ), { dispatch: false })
 
   onMaximise = createEffect(() => combineLatest([
@@ -104,14 +93,66 @@ export class NehubaNavigationEffects implements OnDestroy{
     )
   ))
 
+  onStoreNavigationUpdate = createEffect(() => this.store.pipe(
+    select(atlasSelection.selectors.navigation),
+    distinctUntilChanged((o, n) => navObjEqual(o, n)),
+    withLatestFrom(
+      this.baseSvc.viewerNavLock$,
+      /**
+       * n.b. if NEHUBA_INSTANCE_INJTKN is not provided, this obs will never emit
+       * which, semantically is the correct behaviour
+       */
+      this.baseSvc.nehubaViewerUnit$,
+      this.baseSvc.nehubaViewerUnit$.pipe(
+        switchMap(nvUnit => nvUnit.viewerPositionChange)
+      )
+    ),
+    skipWhile(([nav, lock, _nvUnit, viewerNav]) => lock || navObjEqual(nav, viewerNav)),
+    tap(([nav, _lock, nvUnit, _viewerNav]) => {
+      nvUnit.setNavigationState(nav)
+    })
+  ), { dispatch: false })
+
+  onViewerNavigationUpdate = createEffect(() => this.baseSvc.nehubaViewerUnit$.pipe(
+    switchMap(nvUnit => 
+      nvUnit.viewerPositionChange.pipe(
+        debounce(() => this.baseSvc.viewerNavLock$.pipe(
+          filter(lock => !lock),
+        )),
+        withLatestFrom(
+          this.store.pipe(
+            select(atlasSelection.selectors.navigation)
+          )
+        ),
+        switchMap(([ val, storedNav ]) => {
+          const { zoom, perspectiveZoom, position } = val
+          const roundedZoom = Math.round(zoom)
+          const roundedPz = Math.round(perspectiveZoom)
+          const roundedPosition = position.map(v => Math.round(v)) as [number, number, number]
+          const roundedNav = {
+            ...val,
+            zoom: roundedZoom,
+            perspectiveZoom: roundedPz,
+            position: roundedPosition,
+          }
+          if (navObjEqual(roundedNav, storedNav)) {
+            return NEVER
+          }
+          return of(
+            atlasSelection.actions.setNavigation({
+              navigation:roundedNav
+            })
+          )
+        })
+      )
+    )
+  ))
+
   constructor(
     private action: Actions,
     private store: Store<MainState>,
-    @Inject(NEHUBA_INSTANCE_INJTKN) nehubaInst$: Observable<NehubaViewerUnit>,
+    private baseSvc: NavigationBaseSvc,
   ){
-    this.subscription.push(
-      nehubaInst$.subscribe(val => this.nehubaInst = val),
-    )
   }
 
   ngOnDestroy(): void {
diff --git a/src/viewerModule/nehuba/navigation.service/navigation.service.spec.ts b/src/viewerModule/nehuba/navigation.service/navigation.service.spec.ts
deleted file mode 100644
index 9180048b03a28473737f81fa49eb4d1800fcb87b..0000000000000000000000000000000000000000
--- a/src/viewerModule/nehuba/navigation.service/navigation.service.spec.ts
+++ /dev/null
@@ -1,170 +0,0 @@
-import { discardPeriodicTasks, fakeAsync, TestBed, tick } from '@angular/core/testing'
-import { MockStore, provideMockStore } from '@ngrx/store/testing'
-import { BehaviorSubject, of, Subject } from 'rxjs'
-import * as NavUtil from './navigation.util'
-import { NehubaViewerUnit } from '../nehubaViewer/nehubaViewer.component'
-import { NEHUBA_INSTANCE_INJTKN } from '../util'
-import { NehubaNavigationService } from './navigation.service'
-import { userPreference, atlasSelection } from "src/state"
-
-const nav1 = {
-  position: [1,2,3],
-  orientation: [0, 0, 0, 1],
-  perspectiveOrientation: [1, 0, 0, 0],
-  perspectiveZoom: 100,
-  zoom: -12
-}
-
-const nav1x2 = {
-  position: [2,4,6],
-  orientation: [0, 0, 0, 2],
-  perspectiveOrientation: [2, 0, 0, 0],
-  perspectiveZoom: 200,
-  zoom: -24
-}
-
-const nav2 = {
-  position: [5, 1, -3],
-  orientation: [0, 0, 1, 0],
-  perspectiveOrientation: [-3, 0, 0, 0],
-  perspectiveZoom: 150,
-  zoom: -60
-}
-
-const nav1p2 = {
-  position: [6, 3, 0],
-  orientation: [0, 0, 1, 1],
-  perspectiveOrientation: [-2, 0, 0, 0],
-  perspectiveZoom: 250,
-  zoom: -72
-}
-describe('> navigation.service.ts', () => {
-
-  describe('> NehubaNavigationService', () => {
-    let nehubaInst$: BehaviorSubject<NehubaViewerUnit>
-    let nehubaInst: Partial<NehubaViewerUnit>
-    let service: NehubaNavigationService
-    beforeEach(() => {
-      nehubaInst$ = new BehaviorSubject(null)
-      TestBed.configureTestingModule({
-        imports: [
-
-        ],
-        providers: [
-          provideMockStore(),
-          {
-            provide: NEHUBA_INSTANCE_INJTKN,
-            useValue: nehubaInst$
-          },
-          NehubaNavigationService
-        ]
-      })
-
-      const mockStore = TestBed.inject(MockStore)
-      mockStore.overrideSelector(
-        atlasSelection.selectors.navigation,
-        nav1
-      )
-      mockStore.overrideSelector(
-        userPreference.selectors.useAnimation,
-        true
-      )
-    })
-  
-    it('> on new emit null on nehubaInst, clearViewSub is called, but setupviewersub is not called', () => {
-
-      service = TestBed.inject(NehubaNavigationService)
-      const clearviewSpy = spyOn(service, 'clearViewerSub').and.callThrough()
-      const setupViewSpy = spyOn(service, 'setupViewerSub').and.callThrough()
-      nehubaInst$.next(null)
-      expect(clearviewSpy).toHaveBeenCalled()
-      expect(setupViewSpy).not.toHaveBeenCalled()
-    })
-
-    it('> on new emit with viewer, clear view sub and setupviewers are both called', () => {
-
-      service = TestBed.inject(NehubaNavigationService)
-      const clearviewSpy = spyOn(service, 'clearViewerSub').and.callThrough()
-      const setupViewSpy = spyOn(service, 'setupViewerSub').and.callThrough()
-      nehubaInst = {
-        viewerPositionChange: of(nav1) as any,
-        setNavigationState: jasmine.createSpy()
-      }
-      nehubaInst$.next(nehubaInst as NehubaViewerUnit)
-      expect(clearviewSpy).toHaveBeenCalled()
-      expect(setupViewSpy).toHaveBeenCalled()
-    })
-
-    describe('> #setupViewerSub', () => {
-      let dispatchSpy: jasmine.Spy
-      beforeEach(() => {
-        nehubaInst = {
-          viewerPositionChange: new Subject() as any,
-          setNavigationState: jasmine.createSpy(),
-        }
-
-        service = TestBed.inject(NehubaNavigationService)
-        service['nehubaViewerInstance'] = nehubaInst as NehubaViewerUnit
-
-        const mockStore = TestBed.inject(MockStore)
-        mockStore.overrideSelector(atlasSelection.selectors.navigation, nav1)
-        dispatchSpy = spyOn(mockStore, 'dispatch').and.callFake(() => {})
-      })
-
-      describe('> on viewerPosition change multiple times', () => {
-        beforeEach(() => {
-          service.setupViewerSub()
-        })
-        it('> viewerNav set to last value', fakeAsync(() => {
-
-          nehubaInst.viewerPositionChange.next(nav2)
-          nehubaInst.viewerPositionChange.next(nav1x2)
-          expect(
-            service.viewerNav
-          ).toEqual(nav1x2 as any)
-          discardPeriodicTasks()
-        }))
-
-        it('> dispatch does not get called immediately', fakeAsync(() => {
-
-          nehubaInst.viewerPositionChange.next(nav2)
-          nehubaInst.viewerPositionChange.next(nav1x2)
-          expect(dispatchSpy).not.toHaveBeenCalled()
-          discardPeriodicTasks()
-        }))
-
-        it('> dispatch called after 160 debounce', fakeAsync(() => {
-          
-          // next/'ing cannot be done in beforeEach
-          // or this test will fail
-          nehubaInst.viewerPositionChange.next(nav2)
-          nehubaInst.viewerPositionChange.next(nav1x2)
-          tick(160)
-          expect(dispatchSpy).toHaveBeenCalled()
-        }))
-      })
-    })
-  
-    describe('> on storeNavigation update', () => {
-      let navEqlSpy: jasmine.Spy
-      beforeEach(() => {
-        nehubaInst = {
-          setNavigationState: jasmine.createSpy(),
-          viewerPositionChange: new Subject() as any,
-        }
-        nehubaInst$.next(nehubaInst as NehubaViewerUnit)
-        navEqlSpy = spyOnProperty(NavUtil, 'navObjEqual')
-      })
-      it('> if navEq returnt true, do not setNav', () => {
-        navEqlSpy.and.returnValue(() => true)
-        service = TestBed.inject(NehubaNavigationService)
-        expect(nehubaInst.setNavigationState).not.toHaveBeenCalled()
-      })
-      it('> if navEq return false, call setNav', () => {
-        navEqlSpy.and.returnValue(() => false)
-        service = TestBed.inject(NehubaNavigationService)
-        expect(nehubaInst.setNavigationState).toHaveBeenCalled()
-      })
-    })
-  })
-})
diff --git a/src/viewerModule/nehuba/navigation.service/navigation.service.ts b/src/viewerModule/nehuba/navigation.service/navigation.service.ts
deleted file mode 100644
index e06ddd27fe488f686aec8dcd9c803491df1fad60..0000000000000000000000000000000000000000
--- a/src/viewerModule/nehuba/navigation.service/navigation.service.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import { Inject, Injectable, OnDestroy, Optional } from "@angular/core";
-import { select, Store } from "@ngrx/store";
-import { Observable, ReplaySubject, Subscription } from "rxjs";
-import { debounceTime } from "rxjs/operators";
-import { NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component";
-import { NEHUBA_INSTANCE_INJTKN } from "../util";
-import { INavObj, navObjEqual } from './navigation.util'
-import { actions } from "src/state/atlasSelection";
-import { atlasSelection, userPreference } from "src/state";
-
-@Injectable()
-export class NehubaNavigationService implements OnDestroy{
-
-  private subscriptions: Subscription[] = []
-  private viewerInstanceSubscriptions: Subscription[] = []
-
-  private nehubaViewerInstance: NehubaViewerUnit
-  public storeNav: INavObj
-  public viewerNav: INavObj
-  public viewerNav$ = new ReplaySubject<INavObj>(1)
-
-  // if set, ignores store attempt to update nav
-  private viewerNavLock: boolean = false
-
-  private globalAnimationFlag = true
-  private rafRef: number
-
-  constructor(
-    private store$: Store<any>,
-    @Optional() @Inject(NEHUBA_INSTANCE_INJTKN) nehubaInst$: Observable<NehubaViewerUnit>,
-  ){
-    this.subscriptions.push(
-      this.store$.pipe(
-        select(userPreference.selectors.useAnimation)
-      ).subscribe(flag => this.globalAnimationFlag = flag)
-    )
-
-    if (nehubaInst$) {
-      this.subscriptions.push(
-        nehubaInst$.subscribe(val => {
-          this.clearViewerSub()
-          this.nehubaViewerInstance = val
-          if (this.nehubaViewerInstance) {
-            this.setupViewerSub()
-          }
-        })
-      )
-    }
-
-    this.subscriptions.push(
-      // realtime state nav state
-      this.store$.pipe(
-        select(atlasSelection.selectors.navigation)
-      ).subscribe(v => {
-        this.storeNav = v
-        // if stored nav differs from viewerNav
-        if (!this.viewerNavLock && this.nehubaViewerInstance) {
-          const navEql = navObjEqual(this.storeNav, this.viewerNav)
-          if (!navEql) {
-            this.navigateViewer(this.storeNav)
-          }
-        }
-      })
-    )
-  }
-
-  navigateViewer(navigation: INavObj): void {
-    if (!navigation) return
-    // TODO
-    // readd consider how to do animation
-    this.nehubaViewerInstance.setNavigationState(navigation)
-  }
-
-  setupViewerSub(): void {
-    this.viewerInstanceSubscriptions.push(
-      // realtime viewer nav state
-      this.nehubaViewerInstance.viewerPositionChange.subscribe(
-        (val: INavObj) => {
-          this.viewerNav = val
-          this.viewerNav$.next(val)
-          this.viewerNavLock = true
-        }
-      ),
-      // debounced viewer nav state
-      this.nehubaViewerInstance.viewerPositionChange.pipe(
-        debounceTime(160)
-      ).subscribe((val: INavObj) => {
-        this.viewerNavLock = false
-
-        const { zoom, perspectiveZoom, position } = val
-        const roundedZoom = Math.round(zoom)
-        const roundedPz = Math.round(perspectiveZoom)
-        const roundedPosition = position.map(v => Math.round(v)) as [number, number, number]
-        const roundedNav = {
-          ...val,
-          zoom: roundedZoom,
-          perspectiveZoom: roundedPz,
-          position: roundedPosition,
-        }
-        const navEql = navObjEqual(roundedNav, this.storeNav)
-        
-        if (!navEql) {
-          this.store$.dispatch(
-            actions.setNavigation({
-              navigation: roundedNav
-            })
-          )
-        }
-      })
-    )
-  }
-
-  clearViewerSub(): void {
-    while (this.viewerInstanceSubscriptions.length > 0) this.viewerInstanceSubscriptions.pop().unsubscribe()
-  }
-
-  ngOnDestroy(): void {
-    this.clearViewerSub()
-    while (this.subscriptions.length > 0) this.subscriptions.pop().unsubscribe()
-  }
-}
diff --git a/src/viewerModule/nehuba/navigation.service/navigation.util.ts b/src/viewerModule/nehuba/navigation.service/navigation.util.ts
index 9ae2df4760f4216b0e9f98e4958750882b791c21..835e6ed94d852d7120019e3cc3a190accf38b81c 100644
--- a/src/viewerModule/nehuba/navigation.service/navigation.util.ts
+++ b/src/viewerModule/nehuba/navigation.service/navigation.util.ts
@@ -1,13 +1,6 @@
-import { TVec3, TVec4 } from "src/messaging/types";
+import { TVec3, TVec4, INavObj } from "../constants";
 import { arrayOfPrimitiveEqual } from "src/util/fn";
 
-export interface INavObj {
-  position: TVec3
-  orientation: TVec4
-  perspectiveOrientation: TVec4
-  perspectiveZoom: number
-  zoom: number
-}
 
 export function navMul(nav: INavObj, scalar: number): INavObj {
   return {
diff --git a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts
index 53c0afb97bb0933a327c68ff675d773cc4f008c2..746a3363a857b01a3f5bd0f06def5c6f9145c834 100644
--- a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts
+++ b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts
@@ -697,7 +697,7 @@ export class NehubaViewerUnit implements OnDestroy {
 
   private setLayerTransparency(layerName: string, alpha: number) {
     const layer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(layerName)
-    if (!layer) return
+    if (!(layer?.layer)) return
 
     /**
      * for segmentation layer
diff --git a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts
index 9b7a7b09d88e0ad801477b90e13f1c4a5f6e60bb..19314d6737ebfd0c3229b41f3fb62055d9b0bcb6 100644
--- a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts
+++ b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts
@@ -6,7 +6,6 @@ import { NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component"
 import { NehubaViewerContainerDirective } from "./nehubaViewerInterface.directive"
 import { NEVER, of, pipe, Subject } from "rxjs"
 import { userPreference, atlasSelection, atlasAppearance } from "src/state"
-import { NehubaNavigationService } from "../navigation.service"
 import { LayerCtrlEffects } from "../layerCtrl.service/layerCtrl.effects"
 import { mapTo } from "rxjs/operators"
 
@@ -31,13 +30,6 @@ describe('> nehubaViewerInterface.directive.ts', () => {
         ],
         providers: [
           provideMockStore(),
-          {
-            provide: NehubaNavigationService,
-            useValue: {
-              viewerNav$: NEVER,
-              storeNav: null
-            }
-          },
           {
             provide: LayerCtrlEffects,
             useValue: {
diff --git a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts
index 6454ea378c353eab8487b686f2009759eb95e819..aee39e6f48641ebdb9b5b8051f23c6c023a91545 100644
--- a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts
+++ b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts
@@ -6,13 +6,14 @@ import { distinctUntilChanged, filter, debounceTime, scan, map, throttleTime, sw
 import { serializeSegment } from "../util";
 import { LoggingService } from "src/logging";
 import { arrayOfPrimitiveEqual } from 'src/util/fn'
-import { INavObj, NehubaNavigationService } from "../navigation.service";
+import { INavObj } from "../constants"
 import { NehubaConfig, defaultNehubaConfig, getNehubaConfig } from "../config.service";
 import { atlasAppearance, atlasSelection, userPreference } from "src/state";
 import { SxplrAtlas, SxplrParcellation, SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes";
 import { arrayEqual } from "src/util/array";
 import { cvtNavigationObjToNehubaConfig } from "../config.service/util";
 import { LayerCtrlEffects } from "../layerCtrl.service/layerCtrl.effects";
+import { NavigationBaseSvc } from "../navigation.service/navigation.base.service";
 
 
 const determineProtocol = (url: string) => {
@@ -134,7 +135,6 @@ const accumulatorFn: (
 @Directive({
   selector: '[iav-nehuba-viewer-container]',
   exportAs: 'iavNehubaViewerContainer',
-  providers: [ NehubaNavigationService ]
 })
 export class NehubaViewerContainerDirective implements OnDestroy{
 
@@ -150,19 +150,16 @@ export class NehubaViewerContainerDirective implements OnDestroy{
   @Output()
   public iavNehubaViewerContainerViewerLoading: EventEmitter<boolean> = new EventEmitter()
   
-  private componentFactory: ComponentFactory<NehubaViewerUnit>
   private cr: ComponentRef<NehubaViewerUnit>
   private navigation: atlasSelection.AtlasSelectionState['navigation']
   constructor(
     private el: ViewContainerRef,
     private store$: Store<any>,
-    private navService: NehubaNavigationService,
+    private navBaseSvc: NavigationBaseSvc,
     private effect: LayerCtrlEffects,
     private cdr: ChangeDetectorRef,
-    cfr: ComponentFactoryResolver,
     @Optional() private log: LoggingService,
   ){
-    this.componentFactory = cfr.resolveComponentFactory(NehubaViewerUnit)
     this.cdr.detach()
 
     this.subscriptions.push(
@@ -247,9 +244,9 @@ export class NehubaViewerContainerDirective implements OnDestroy{
           this.nehubaViewerInstance.applyGpuLimit(limit)
         }
       }),
-      this.navService.viewerNav$.subscribe(v => {
-        this.navigationEmitter.emit(v)
-      }),
+      this.navBaseSvc.nehubaViewerUnit$.pipe(
+        switchMap(nvUnit => nvUnit.viewerPositionChange)
+      ).subscribe(v => this.navigationEmitter.emit(v)),
       this.store$.pipe(
         select(atlasSelection.selectors.navigation)
       ).subscribe(nav => this.navigation = nav)
@@ -295,14 +292,8 @@ export class NehubaViewerContainerDirective implements OnDestroy{
     await new Promise(rs => setTimeout(rs, 0))
 
     this.iavNehubaViewerContainerViewerLoading.emit(true)
-    this.cr = this.el.createComponent(this.componentFactory)
+    this.cr = this.el.createComponent(NehubaViewerUnit)
 
-    if (this.navService.storeNav) {
-      this.nehubaViewerInstance.initNav = {
-        ...this.navService.storeNav,
-        positionReal: true
-      }
-    }
 
     if (this.gpuLimit) {
       const initialNgState = nehubaConfig && nehubaConfig.dataset && nehubaConfig.dataset.initialNgState
diff --git a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.style.css b/src/viewerModule/nehuba/ngLayerCtlModule/index.ts
similarity index 100%
rename from src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.style.css
rename to src/viewerModule/nehuba/ngLayerCtlModule/index.ts
diff --git a/src/viewerModule/nehuba/ngLayerCtlModule/module.ts b/src/viewerModule/nehuba/ngLayerCtlModule/module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dbdf56167ba1073daaa8743fb034ae6f0d2db7b4
--- /dev/null
+++ b/src/viewerModule/nehuba/ngLayerCtlModule/module.ts
@@ -0,0 +1,26 @@
+import { CommonModule } from "@angular/common";
+import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from "@angular/core";
+import { MatButtonModule } from "@angular/material/button";
+import { MatTooltipModule } from "@angular/material/tooltip";
+import { NgLayerCtrlCmp } from "./ngLayerCtl/ngLayerCtrl.component";
+
+@NgModule({
+  imports: [
+    CommonModule,
+    MatTooltipModule,
+    MatButtonModule,
+    
+  ],
+  declarations: [
+    NgLayerCtrlCmp
+  ],
+  exports: [
+    NgLayerCtrlCmp
+  ],
+  schemas: [
+    CUSTOM_ELEMENTS_SCHEMA
+  ]
+})
+export class NgLayerCtlModule{
+
+}
\ No newline at end of file
diff --git a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.component.ts b/src/viewerModule/nehuba/ngLayerCtlModule/ngLayerCtl/ngLayerCtrl.component.ts
similarity index 81%
rename from src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.component.ts
rename to src/viewerModule/nehuba/ngLayerCtlModule/ngLayerCtl/ngLayerCtrl.component.ts
index cce89a171435f72d6c17f112548f92477ad47dc1..c39f0955038506f38623f7eafb07e09f578189f9 100644
--- a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.component.ts
+++ b/src/viewerModule/nehuba/ngLayerCtlModule/ngLayerCtl/ngLayerCtrl.component.ts
@@ -4,8 +4,7 @@ import { isMat4 } from "common/util"
 import { CONST } from "common/constants"
 import { Observable } from "rxjs";
 import { atlasAppearance, atlasSelection } from "src/state";
-import { NehubaViewerUnit } from "..";
-import { NEHUBA_INSTANCE_INJTKN } from "../util";
+import { NehubaViewerUnit, NEHUBA_INSTANCE_INJTKN } from "src/viewerModule/nehuba";
 import { getExportNehuba } from "src/util/fn";
 
 type Vec4 = [number, number, number, number]
@@ -60,7 +59,7 @@ export class NgLayerCtrlCmp implements OnChanges, OnDestroy{
   transform: Mat4 = idMat4
 
   @Input('ng-layer-ctl-transform')
-  set _transform(xform: string | Mat4) {
+  set _transform(xform: string | Mat4 | number[][]) {
     const parsedResult = typeof xform === "string"
       ? JSON.parse(xform)
       : xform
@@ -70,6 +69,9 @@ export class NgLayerCtrlCmp implements OnChanges, OnDestroy{
     this.transform = parsedResult as Mat4
   }
 
+  @Input('ng-layer-ctl-info')
+  info: Record<string, any>
+
   visible: boolean = true
   private viewer: NehubaViewerUnit
 
@@ -84,7 +86,10 @@ export class NgLayerCtrlCmp implements OnChanges, OnDestroy{
       () => sub.unsubscribe()
     )
 
-    getExportNehuba().then(exportNehuba => this.exportNehuba = exportNehuba)
+    getExportNehuba().then(exportNehuba => {
+      this.exportNehuba = exportNehuba
+      this.setOrientation()
+    })
   }
 
   ngOnDestroy(): void {
@@ -136,10 +141,23 @@ export class NgLayerCtrlCmp implements OnChanges, OnDestroy{
     const scaledM = mat4.scale(mat4.create(), incM, vec3.inverse(vec3.create(), scale))
     const q = mat4.getRotation(quat.create(0), scaledM)
 
+    let position: number[]
+    if (this.info) {
+      const { scales } = this.info
+      const sizeInNm = [0, 1, 2].map(idx => scales[0].size[idx] * scales[0].resolution[idx])
+      const start = vec3.transformMat4(vec3.create(), vec3.fromValues(0, 0, 0), incM)
+      const end = vec3.transformMat4(vec3.create(), vec3.fromValues(...sizeInNm), incM)
+      const final = vec3.add(vec3.create(), start, end)
+      vec3.scale(final, final, 0.5)
+      position = Array.from(final)
+    }
+    
+    
     this.store.dispatch(
       atlasSelection.actions.navigateTo({
         navigation: {
-          orientation: Array.from(q)
+          orientation: Array.from(q),
+          position,
         },
         animation: true
       })
diff --git a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.stories.ts b/src/viewerModule/nehuba/ngLayerCtlModule/ngLayerCtl/ngLayerCtrl.stories.ts
similarity index 95%
rename from src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.stories.ts
rename to src/viewerModule/nehuba/ngLayerCtlModule/ngLayerCtl/ngLayerCtrl.stories.ts
index b716f7d93824fd1e223450fde56655398aa91d0b..ff1fb3cd5f97eaf74b17751d28b5bd245707f9ba 100644
--- a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.stories.ts
+++ b/src/viewerModule/nehuba/ngLayerCtlModule/ngLayerCtl/ngLayerCtrl.stories.ts
@@ -2,7 +2,7 @@ import { idMat4, NgLayerCtrlCmp } from "./ngLayerCtrl.component"
 import { Meta, moduleMetadata, Story } from "@storybook/angular"
 import { CommonModule } from "@angular/common"
 import { MatButtonModule } from "@angular/material/button"
-import { NEHUBA_INSTANCE_INJTKN } from "../util"
+import { NEHUBA_INSTANCE_INJTKN } from "src/viewerModule/nehuba/util"
 import { NEVER } from "rxjs"
 import { action } from "@storybook/addon-actions"
 import { MatTooltipModule } from "@angular/material/tooltip"
diff --git a/src/viewerModule/nehuba/ngLayerCtlModule/ngLayerCtl/ngLayerCtrl.style.css b/src/viewerModule/nehuba/ngLayerCtlModule/ngLayerCtl/ngLayerCtrl.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..07e52b8e10c90b947671b91e17b5347eb894094d
--- /dev/null
+++ b/src/viewerModule/nehuba/ngLayerCtlModule/ngLayerCtl/ngLayerCtrl.style.css
@@ -0,0 +1,22 @@
+:host
+{
+    padding: 0.5rem 0;
+}
+
+.container
+{
+    display: flex;
+    width: 100%;
+    align-items: center;
+}
+
+button
+{
+    flex: 0 0 auto;
+}
+
+.layer-name
+{
+    flex: 1 1 0px;
+    overflow: hidden;
+}
\ No newline at end of file
diff --git a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.template.html b/src/viewerModule/nehuba/ngLayerCtlModule/ngLayerCtl/ngLayerCtrl.template.html
similarity index 67%
rename from src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.template.html
rename to src/viewerModule/nehuba/ngLayerCtlModule/ngLayerCtl/ngLayerCtrl.template.html
index b53ec540ad7fd2bcf7df004327d9cc3f2b3ba51b..4f5c45c9fd2baa02a8852a49203636fa52922f84 100644
--- a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.template.html
+++ b/src/viewerModule/nehuba/ngLayerCtlModule/ngLayerCtl/ngLayerCtrl.template.html
@@ -1,4 +1,4 @@
-<div [ngClass]="{ 'text-muted': !visible }">
+<div class="container" [ngClass]="{ 'text-muted': !visible }">
 
   <button mat-icon-button
     [matTooltip]="CONST.TOGGLE_LAYER_VISILITY"
@@ -6,7 +6,7 @@
     <i [ngClass]="visible ? 'fa-eye' : 'fa-eye-slash'" class="far"></i>
   </button>
   
-  <span>
+  <span class="layer-name">
     {{ name }}
   </span>
 
@@ -24,11 +24,12 @@
     <i class="fas fa-cog"></i>
   </button>
 
-  <ng-template [ngIf]="showOpacityCtrl">
-    <ng-layer-tune
-      [ngLayerName]="name"
-      [hideCtrl]="hideNgTuneCtrl"
-      [opacity]="defaultOpacity">
-    </ng-layer-tune>
-  </ng-template>
 </div>
+
+<ng-template [ngIf]="showOpacityCtrl">
+  <ng-layer-tune
+    [ngLayerName]="name"
+    [hideCtrl]="hideNgTuneCtrl"
+    [opacity]="defaultOpacity">
+  </ng-layer-tune>
+</ng-template>
\ No newline at end of file
diff --git a/src/viewerModule/nehuba/types.ts b/src/viewerModule/nehuba/types.ts
index 88b6095c5c1a4fa7b653a655cd8d21347c04e15d..c7684e637dd9a156c722db3ca806b7f381171bab 100644
--- a/src/viewerModule/nehuba/types.ts
+++ b/src/viewerModule/nehuba/types.ts
@@ -1,5 +1,5 @@
 import { SxplrRegion } from "src/atlasComponents/sapi/sxplrTypes";
-import { INavObj } from "./navigation.service";
+import { INavObj } from "./constants";
 
 export type TNehubaContextInfo = {
   nav: INavObj
diff --git a/src/viewerModule/viewer.common.effects.ts b/src/viewerModule/viewer.common.effects.ts
index cc888f129935050edd49a5238764a672f3d00d66..4aa169bb256ca4f14c8d2cd28a0ba971575f0d44 100644
--- a/src/viewerModule/viewer.common.effects.ts
+++ b/src/viewerModule/viewer.common.effects.ts
@@ -1,7 +1,7 @@
 import { Injectable } from "@angular/core";
 import { createEffect } from "@ngrx/effects";
 import { select, Store } from "@ngrx/store";
-import { forkJoin, of } from "rxjs";
+import { of } from "rxjs";
 import { distinctUntilChanged, map, switchMap } from "rxjs/operators";
 import * as atlasSelection from "src/state/atlasSelection";
 import * as atlasAppearance from "src/state/atlasAppearance"
@@ -17,31 +17,7 @@ export class ViewerCommonEffects {
       : this.store.pipe(
         select(atlasSelection.selectors.selectedTemplate),
         distinctUntilChanged((o, n) => o?.id === n?.id),
-        switchMap(template => {
-          if (!template) {
-            return of(null as atlasAppearance.const.UseViewer)
-          }
-          return forkJoin({
-            voxel: this.sapi.getVoxelTemplateImage(template),
-            surface: this.sapi.getSurfaceTemplateImage(template)
-          }).pipe(
-            map(vols => {
-              if (!vols) return null
-              const { voxel, surface } = vols
-              if (voxel.length > 0 && surface.length > 0) {
-                console.error(`both voxel and surface length are > 0, this should not happen.`)
-                return atlasAppearance.const.useViewer.NOT_SUPPORTED
-              }
-              if (voxel.length > 0) {
-                return atlasAppearance.const.useViewer.NEHUBA
-              }
-              if (surface.length > 0) {
-                return atlasAppearance.const.useViewer.THREESURFER
-              }
-              return atlasAppearance.const.useViewer.NOT_SUPPORTED
-            })
-          )
-        })
+        switchMap(template => this.sapi.useViewer(template))
       )
     ),
     map(viewer => atlasAppearance.actions.setUseViewer({ viewer }))
diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts
index 25c57644e627082617ed60ce3d5262def63be2d8..8961b4f440ae0298602daed8efd50ad01afdeb6d 100644
--- a/src/viewerModule/viewerCmp/viewerCmp.component.ts
+++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts
@@ -66,7 +66,6 @@ export class ViewerCmp implements OnDestroy {
 
   public CONST = CONST
   public ARIA_LABELS = ARIA_LABELS
-  public VOI_QUERY_FLAG = environment.EXPERIMENTAL_FEATURE_FLAG
 
   @ViewChild('genericInfoVCR', { read: ViewContainerRef })
   genericInfoVCR: ViewContainerRef
diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html
index 2c7d4b7089a4d6752d667eed3594cafe6c84e567..72c4cfdac0921c2ea38b7079e3e015b48ef2baa7 100644
--- a/src/viewerModule/viewerCmp/viewerCmp.template.html
+++ b/src/viewerModule/viewerCmp/viewerCmp.template.html
@@ -231,11 +231,9 @@
       <ng-container *ngTemplateOutlet="autocompleteTmpl; context: { showTour: true }">
       </ng-container>
       
-      <ng-template [ngIf]="VOI_QUERY_FLAG">
-        <div *ngIf="!((selectedRegions$ | async)[0])" class="sxplr-p-1 w-100">
-          <ng-container *ngTemplateOutlet="spatialFeatureListTmpl"></ng-container>
-        </div>
-      </ng-template>
+      <div *ngIf="!((selectedRegions$ | async)[0])" class="sxplr-p-1 w-100">
+        <ng-container *ngTemplateOutlet="spatialFeatureListTmpl"></ng-container>
+      </div>
     </div>
 
     <!-- such a gross implementation -->