diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html
new file mode 100644
index 0000000000000000000000000000000000000000..c581803c8eef27cd057d5ce191fd038689c34c0b
--- /dev/null
+++ b/.storybook/preview-head.html
@@ -0,0 +1,5 @@
+<script>
+  console.log("./storybook/preview-head.html working properly")
+</script>
+<script src="https://unpkg.com/kg-dataset-previewer@1.2.0/dist/kg-dataset-previewer/kg-dataset-previewer.js" defer></script>
+<script src="main.bundle.js" defer></script>
diff --git a/src/atlasComponents/sapi/core/sapiParcellation.ts b/src/atlasComponents/sapi/core/sapiParcellation.ts
index 05e367a54c71aa42f312c5232ff69dab251f8ccf..7add334991318dafd6be33a99b1c91b14c2e2c10 100644
--- a/src/atlasComponents/sapi/core/sapiParcellation.ts
+++ b/src/atlasComponents/sapi/core/sapiParcellation.ts
@@ -1,6 +1,6 @@
 import { SapiVolumeModel } from ".."
 import { SAPI } from "../sapi.service"
-import { SapiParcellationModel, SapiRegionModel } from "../type"
+import { SapiParcellationFeatureModel, SapiParcellationModel, SapiRegionModel } from "../type"
 
 export class SAPIParcellation{
   constructor(private sapi: SAPI, public atlasId: string, public id: string){
@@ -26,4 +26,22 @@ export class SAPIParcellation{
       `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/volumes`
     )
   }
+
+  getFeatures(): Promise<SapiParcellationFeatureModel[]> {
+    return this.sapi.http.get<SapiParcellationFeatureModel[]>(
+      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/features`,
+      {
+        params: {
+          per_page: 5,
+          page: 0,
+        }
+      }
+    ).toPromise()
+  }
+
+  getFeatureInstance(instanceId: string): Promise<SapiParcellationFeatureModel> {
+    return this.sapi.http.get<SapiParcellationFeatureModel>(
+      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/features/${encodeURIComponent(instanceId)}`,
+    ).toPromise()
+  }
 }
diff --git a/src/atlasComponents/sapi/index.ts b/src/atlasComponents/sapi/index.ts
index 092ebd6e947b5c63cad8074d1e3d311539f62a10..17d2f7cb23cdee8f3c1036282fdc757e499532f6 100644
--- a/src/atlasComponents/sapi/index.ts
+++ b/src/atlasComponents/sapi/index.ts
@@ -12,6 +12,9 @@ export {
   SapiSpatialFeatureModel
 } from "./type"
 
+import { SapiRegionalFeatureModel, SapiSpatialFeatureModel } from "./type"
+export type SapiFeatureModel = SapiRegionalFeatureModel | SapiSpatialFeatureModel
+
 export { SAPI } from "./sapi.service"
 export {
   SAPIAtlas,
diff --git a/src/atlasComponents/sapi/sapi.service.ts b/src/atlasComponents/sapi/sapi.service.ts
index 5d1572ca40fae1bddaf72c147d6b0a496fae48b0..07fb77d1567562de156b73b4081ada8d86897b5e 100644
--- a/src/atlasComponents/sapi/sapi.service.ts
+++ b/src/atlasComponents/sapi/sapi.service.ts
@@ -4,11 +4,12 @@ import { BS_ENDPOINT } from 'src/util/constants';
 import { map, shareReplay, take, tap } from "rxjs/operators";
 import { SAPIAtlas, SAPISpace } from './core'
 import { SapiAtlasModel, SapiParcellationModel, SapiRegionalFeatureModel, SapiRegionModel, SapiSpaceModel, SpyNpArrayDataModel } from "./type";
-import { CachedFunction } from "src/util/fn";
+import { CachedFunction, getExportNehuba } from "src/util/fn";
 import { SAPIParcellation } from "./core/sapiParcellation";
 import { SAPIRegion } from "./core/sapiRegion"
 import { MatSnackBar } from "@angular/material/snack-bar";
 import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service";
+import { EnumColorMapName } from "src/util/colorMaps";
 
 export const SIIBRA_API_VERSION_HEADER_KEY='x-siibra-api-version'
 export const SIIBRA_API_VERSION = '0.2.0'
@@ -133,8 +134,8 @@ export class SAPI{
       }
     })
   }
-
-  async processNpArrayData(input: SpyNpArrayDataModel) {
+  
+  async processNpArrayData<T extends keyof ProcessTypedArrayResult>(input: SpyNpArrayDataModel, method: PARSE_TYPEDARRAY = PARSE_TYPEDARRAY.RAW_ARRAY, params: ProcessTypedArrayResult[T]['input'] = null): Promise<ProcessTypedArrayResult[T]['output']> {
 
     const supportedDtype = [
       "uint8",
@@ -163,17 +164,84 @@ export class SAPI{
 
     try {
       const bin = atob(content)
-      const { pako } = (window as any).export_nehuba
+      const { pako } = getExportNehuba()
       const array = pako.inflate(bin)
-      this.workerSvc.sendMessage({
-        method: "",
+      let workerMsg: string
+      switch (method) {
+      case PARSE_TYPEDARRAY.CANVAS_FORTRAN_RGBA: {
+        workerMsg = "PROCESS_TYPED_ARRAY_F2RGBA"
+        break
+      }
+      case PARSE_TYPEDARRAY.CANVAS_COLORMAP_RGBA: {
+        workerMsg = "PROCESS_TYPED_ARRAY_CM2RGBA"
+        break
+      }
+      case PARSE_TYPEDARRAY.RAW_ARRAY: {
+        workerMsg = "PROCESS_TYPED_ARRAY_RAW"
+        break
+      }
+      default:{
+        throw new Error(`sapi.service#decodeNpArrayDataModel: method cannot be deciphered: ${method}`)
+      }
+      }
+      const { result } = await this.workerSvc.sendMessage({
+        method: workerMsg,
         param: {
-          
+          inputArray: array,
+          width,
+          height,
+          channel,
+          dtype,
+          processParams: params
         },
         transfers: [ array.buffer ]
       })
+      const { buffer, outputArray, min, max } = result
+      return {
+        type: method,
+        result: buffer,
+        rawArray: outputArray,
+        min,
+        max
+      }
     } catch (e) {
       throw new Error(`sapi.service#decodeNpArrayDataModel error: ${e}`)
     }
   }
 }
+
+export enum PARSE_TYPEDARRAY {
+  CANVAS_FORTRAN_RGBA="CANVAS_FORTRAN_RGBA",
+  CANVAS_COLORMAP_RGBA="CANVAS_COLORMAP_RGBA",
+  RAW_ARRAY="RAW_ARRAY",
+}
+
+type ProcessTypedArrayResult = {
+  [PARSE_TYPEDARRAY.CANVAS_FORTRAN_RGBA]: {
+    input: null
+    output: {
+      type: PARSE_TYPEDARRAY
+      result: Uint8ClampedArray
+    }
+  }
+  [PARSE_TYPEDARRAY.CANVAS_COLORMAP_RGBA]: {
+    input?: {
+      colormap?: EnumColorMapName
+      log?: boolean
+    }
+    output: {
+      type: PARSE_TYPEDARRAY
+      result: Uint8ClampedArray
+      max: number
+      min: number
+    }
+  }
+  [PARSE_TYPEDARRAY.RAW_ARRAY]: {
+    input: null
+    output: {
+      rawArray: number[][]
+      min: number
+      max: number
+    }
+  }
+}
diff --git a/src/atlasComponents/sapi/schema.ts b/src/atlasComponents/sapi/schema.ts
index c1f52d82ee9d74a9687466aa12867ad3ee4f54fa..9fb39e5fff86f2e4451f957c11d2affcbdb64d9a 100644
--- a/src/atlasComponents/sapi/schema.ts
+++ b/src/atlasComponents/sapi/schema.ts
@@ -197,11 +197,11 @@ export interface components {
       "@id": string;
       /** Name */
       name: string;
-      matrix: components["schemas"]["NpArrayDataModel"];
-      /** Columns */
-      columns: string[];
       /** Parcellations */
       parcellations: { [key: string]: string }[];
+      matrix?: components["schemas"]["NpArrayDataModel"];
+      /** Columns */
+      columns?: string[];
     };
     /** DatasetJsonModel */
     DatasetJsonModel: {
@@ -330,6 +330,17 @@ export interface components {
         [key: string]: components["schemas"]["IEEGElectrodeModel"];
       };
     };
+    /** NeurotransmitterMarkupModel */
+    NeurotransmitterMarkupModel: {
+      /** Latex */
+      latex: string;
+      /** Markdown */
+      markdown: string;
+      /** Name */
+      name: string;
+      /** Label */
+      label: string;
+    };
     /** NiiMetadataModel */
     NiiMetadataModel: {
       /** Min */
@@ -428,6 +439,10 @@ export interface components {
       fingerprints: {
         [key: string]: components["schemas"]["FingerPrintDataModel"];
       };
+      /** Receptor Symbols */
+      receptor_symbols: {
+        [key: string]: components["schemas"]["SymbolMarkupClass"];
+      };
     };
     /** ReceptorDatasetModel */
     ReceptorDatasetModel: {
@@ -443,6 +458,15 @@ export interface components {
       urls: components["schemas"]["Url"][];
       data?: components["schemas"]["ReceptorDataModel"];
     };
+    /** ReceptorMarkupModel */
+    ReceptorMarkupModel: {
+      /** Latex */
+      latex: string;
+      /** Markdown */
+      markdown: string;
+      /** Name */
+      name: string;
+    };
     /** RelationAssessmentItem */
     RelationAssessmentItem: {
       /**
@@ -604,6 +628,16 @@ export interface components {
        */
       versionIdentifier: string;
     };
+    /** SerializationErrorModel */
+    SerializationErrorModel: {
+      /**
+       * Type
+       * @constant
+       */
+      type?: "spy/serialization-error";
+      /** Message */
+      message: string;
+    };
     /** SiibraAtIdModel */
     SiibraAtIdModel: {
       /** @Id */
@@ -661,6 +695,11 @@ export interface components {
       /** Kgv1Id */
       kgV1Id: string;
     };
+    /** SymbolMarkupClass */
+    SymbolMarkupClass: {
+      receptor: components["schemas"]["ReceptorMarkupModel"];
+      neurotransmitter: components["schemas"]["NeurotransmitterMarkupModel"];
+    };
     /** Url */
     Url: {
       /** Doi */
@@ -1448,7 +1487,10 @@ export interface operations {
       /** Successful Response */
       200: {
         content: {
-          "application/json": components["schemas"]["ConnectivityMatrixDataModel"];
+          "application/json": Partial<
+            components["schemas"]["ConnectivityMatrixDataModel"]
+          > &
+            Partial<components["schemas"]["SerializationErrorModel"]>;
         };
       };
       /** Validation Error */
@@ -1466,12 +1508,19 @@ export interface operations {
         atlas_id: string;
         parcellation_id: string;
       };
+      query: {
+        per_page?: number;
+        page?: number;
+      };
     };
     responses: {
       /** Successful Response */
       200: {
         content: {
-          "application/json": components["schemas"]["ConnectivityMatrixDataModel"][];
+          "application/json": (Partial<
+            components["schemas"]["ConnectivityMatrixDataModel"]
+          > &
+            Partial<components["schemas"]["SerializationErrorModel"]>)[];
         };
       };
       /** Validation Error */
diff --git a/src/atlasComponents/sapi/stories.base.ts b/src/atlasComponents/sapi/stories.base.ts
index a9501454d526324927824ad05f7904255eaaa30d..56f0d88dfdb7dc75566843e480ec41fdae3f26e9 100644
--- a/src/atlasComponents/sapi/stories.base.ts
+++ b/src/atlasComponents/sapi/stories.base.ts
@@ -1,6 +1,7 @@
 import { forkJoin } from "rxjs"
 import { map, switchMap, withLatestFrom } from "rxjs/operators"
 import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionalFeatureModel, SapiRegionModel, SapiSpaceModel } from "."
+import { SapiParcellationFeatureModel } from "./type"
 
 /**
  * base class used to generate wrapper class for storybook
@@ -89,3 +90,7 @@ export async function getHoc1Left(): Promise<SapiRegionModel> {
 export async function getHoc1Features(): Promise<SapiRegionalFeatureModel[]> {
   return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20left/features`)).json()
 }
+
+export async function getJba29Features(): Promise<SapiParcellationFeatureModel[]> {
+  return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/features`)).json()
+}
diff --git a/src/atlasComponents/sapi/type.ts b/src/atlasComponents/sapi/type.ts
index dcb890b9a9d09fc05151c06e4e7cb6104b3c2468..87fddd2c6ee9a2afa81b843a94864a3472061b6b 100644
--- a/src/atlasComponents/sapi/type.ts
+++ b/src/atlasComponents/sapi/type.ts
@@ -39,12 +39,20 @@ export const guards = {
     && val.data.detail["neuroglancer/precomputed"]
 }
 
+/**
+ * serialization error type
+ */
+export type SapiSerializationErrorModel = components["schemas"]["SerializationErrorModel"]
+
 /**
  * datafeatures
  */
 export type SapiRegionalFeatureReceptorModel = components["schemas"]["ReceptorDatasetModel"]
 export type SapiRegionalFeatureModel = components["schemas"]["BaseDatasetJsonModel"] | SapiRegionalFeatureReceptorModel
 
+export type SapiParcellationFeatureMatrixModel = components["schemas"]["ConnectivityMatrixDataModel"]
+export type SapiParcellationFeatureModel = SapiParcellationFeatureMatrixModel | SapiSerializationErrorModel
+
 export function guardPipe<
   InputType,
   GuardType extends InputType
diff --git a/src/atlasComponents/sapiViews/features/connectivity/connectivityMatrix/connectivityMatrix.component.ts b/src/atlasComponents/sapiViews/features/connectivity/connectivityMatrix/connectivityMatrix.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..69cd8b70d89a6d9fc65be10e8448da65ff25ce88
--- /dev/null
+++ b/src/atlasComponents/sapiViews/features/connectivity/connectivityMatrix/connectivityMatrix.component.ts
@@ -0,0 +1,87 @@
+import { AfterViewInit, Component, ElementRef, Input, OnChanges, SimpleChanges } from "@angular/core";
+import { SAPI, SapiAtlasModel, SapiParcellationModel } from "src/atlasComponents/sapi";
+import { PARSE_TYPEDARRAY } from "src/atlasComponents/sapi/sapi.service";
+import { SapiParcellationFeatureMatrixModel, SapiSerializationErrorModel } from "src/atlasComponents/sapi/type";
+import { EnumColorMapName } from "src/util/colorMaps";
+
+@Component({
+  selector: 'sxplr-sapiviews-features-connectivity-matrix',
+  templateUrl: './connectivityMatrix.template.html',
+  styleUrls: [
+    `./connectivityMatrix.style.css`
+  ]
+})
+
+export class ConnectivityMatrixView implements OnChanges, AfterViewInit{
+
+  @Input('sxplr-sapiviews-features-connectivity-matrix-atlas')
+  atlas: SapiAtlasModel
+  
+  @Input('sxplr-sapiviews-features-connectivity-matrix-parcellation')
+  parcellation: SapiParcellationModel
+
+  @Input('sxplr-sapiviews-features-connectivity-matrix-featureid')
+  featureId: string
+
+  matrixData: SapiParcellationFeatureMatrixModel
+  private pleaseRender = false
+  private renderBuffer: Uint8ClampedArray
+  width: number
+  height: number
+  
+  private async fetchMatrixData(){
+    this.matrixData = null
+    const matrix = await this.sapi.getParcellation(this.atlas["@id"], this.parcellation["@id"]).getFeatureInstance(this.featureId)
+    if ((matrix as SapiSerializationErrorModel)?.type === "spy/serialization-error") {
+      return
+    }
+    this.matrixData = matrix as SapiParcellationFeatureMatrixModel
+    this.width = this.matrixData.matrix["x-width"]
+    this.height = this.matrixData.matrix["x-height"]
+  }
+
+  ngAfterViewInit(): void {
+    if (this.pleaseRender) {
+      this.renderCanvas()
+    }
+  }
+
+  async ngOnChanges(changes: SimpleChanges) {
+    await this.fetchMatrixData()
+    const { result, max, min } = await this.sapi.processNpArrayData<PARSE_TYPEDARRAY.CANVAS_COLORMAP_RGBA>(this.matrixData.matrix, PARSE_TYPEDARRAY.CANVAS_COLORMAP_RGBA, { colormap: EnumColorMapName.JET })
+    const rawResult = await this.sapi.processNpArrayData<PARSE_TYPEDARRAY.RAW_ARRAY>(this.matrixData.matrix, PARSE_TYPEDARRAY.RAW_ARRAY)
+    console.log({
+      rawResult
+    })
+    this.renderBuffer = result
+    this.renderCanvas()
+  }
+
+  private renderCanvas(){
+
+    if (!this.el) {
+      this.pleaseRender = true
+      return
+    }
+
+    const arContainer = (this.el.nativeElement as HTMLElement)
+    while (arContainer.firstChild) {
+      arContainer.removeChild(arContainer.firstChild)
+    }
+
+    const canvas = document.createElement("canvas")
+    canvas.height = this.height
+    canvas.width = this.width
+    arContainer.appendChild(canvas)
+    const ctx = canvas.getContext("2d")
+    const imgData = ctx.createImageData(this.width, this.height)
+    imgData.data.set(this.renderBuffer)
+    ctx.putImageData(imgData, 0, 0)
+    this.pleaseRender = false
+  }
+
+  constructor(private sapi: SAPI, private el: ElementRef){
+
+  }
+
+}
\ No newline at end of file
diff --git a/src/atlasComponents/sapiViews/features/connectivity/connectivityMatrix/connectivityMatrix.style.css b/src/atlasComponents/sapiViews/features/connectivity/connectivityMatrix/connectivityMatrix.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/atlasComponents/sapiViews/features/connectivity/connectivityMatrix/connectivityMatrix.template.html b/src/atlasComponents/sapiViews/features/connectivity/connectivityMatrix/connectivityMatrix.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/atlasComponents/sapiViews/features/connectivity/exampleConnectivity.stories.ts b/src/atlasComponents/sapiViews/features/connectivity/exampleConnectivity.stories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..94bf2a98472667e2290d02c22a3dfd3d804bb0dd
--- /dev/null
+++ b/src/atlasComponents/sapiViews/features/connectivity/exampleConnectivity.stories.ts
@@ -0,0 +1,105 @@
+import { CommonModule } from "@angular/common"
+import { HttpClientModule } from "@angular/common/http"
+import { Component } from "@angular/core"
+import { FormsModule } from "@angular/forms"
+import { BrowserAnimationsModule } from "@angular/platform-browser/animations"
+import { Meta, moduleMetadata, Story } from "@storybook/angular"
+import { SAPI, SapiAtlasModel, SapiParcellationModel } from "src/atlasComponents/sapi"
+import { getJba29Features, getHumanAtlas, getJba29 } from "src/atlasComponents/sapi/stories.base"
+import { SapiParcellationFeatureModel } from "src/atlasComponents/sapi/type"
+import { AngularMaterialModule } from "src/sharedModules"
+import { ConnectivityMatrixView } from "./connectivityMatrix/connectivityMatrix.component"
+
+@Component({
+  selector: 'autoradiograph-wrapper-cmp',
+  template: `
+  <mat-form-field appearance="fill">
+    <mat-select [(ngModel)]="featureId">
+      <mat-option value="null" disabled>
+        --select--
+      </mat-option>
+
+      <mat-option [value]="feat['@id']"
+        *ngFor="let feat of features">
+        {{ feat.name }}
+      </mat-option>
+    </mat-select>
+  </mat-form-field>
+  <sxplr-sapiviews-features-connectivity-matrix
+    class="d-inline-block w-100 h-100"
+    *ngIf="featureId"
+    [sxplr-sapiviews-features-connectivity-matrix-atlas]="atlas"
+    [sxplr-sapiviews-features-connectivity-matrix-parcellation]="parcellation"
+    [sxplr-sapiviews-features-connectivity-matrix-featureid]="featureId"
+  >
+  </sxplr-sapiviews-features-connectivity-matrix>
+  `,
+  styles: [
+    `
+    :host
+    {
+      display: block;
+      max-width: 60rem;
+      max-height: 60rem;
+    }
+    `
+  ]
+})
+class ExampleConnectivityMatrixWrapper {
+  atlas: SapiAtlasModel
+  parcellation: SapiParcellationModel
+  features: SapiParcellationFeatureModel[] = []
+  featureId: string
+}
+
+export default {
+  component: ExampleConnectivityMatrixWrapper,
+  decorators: [
+    moduleMetadata({
+      imports: [
+        CommonModule,
+        AngularMaterialModule,
+        HttpClientModule,
+        BrowserAnimationsModule,
+        FormsModule,
+      ],
+      providers: [
+        SAPI
+      ],
+      declarations: [
+        ConnectivityMatrixView
+      ]
+    })
+  ],
+} as Meta
+
+const Template: Story<ExampleConnectivityMatrixWrapper> = (args: ExampleConnectivityMatrixWrapper, { loaded }) => {
+  const { atlas, parc, features } = loaded
+  return ({
+    props: {
+      ...args,
+      atlas: atlas,
+      parcellation: parc,
+      features
+    },
+  })
+}
+
+Template.loaders = [
+  async () => {
+    const atlas = await getHumanAtlas()
+    const parc = await getJba29()
+    const features = await getJba29Features()
+    return {
+      atlas, parc, features
+    }
+  }
+]
+
+export const Default = Template.bind({})
+Default.args = {
+
+}
+Default.loaders = [
+  ...Template.loaders
+]
\ No newline at end of file
diff --git a/src/atlasComponents/sapiViews/features/connectivity/index.ts b/src/atlasComponents/sapiViews/features/connectivity/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/atlasComponents/sapiViews/features/connectivity/module.ts b/src/atlasComponents/sapiViews/features/connectivity/module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..745df380c4f81ae5095b6be8639cc3b2ee1cc0aa
--- /dev/null
+++ b/src/atlasComponents/sapiViews/features/connectivity/module.ts
@@ -0,0 +1,21 @@
+import { CommonModule } from "@angular/common";
+import { NgModule } from "@angular/core";
+import { SAPI } from "src/atlasComponents/sapi";
+import { ConnectivityMatrixView } from "./connectivityMatrix/connectivityMatrix.component";
+
+@NgModule({
+  imports: [
+    CommonModule,
+  ],
+  declarations: [
+    ConnectivityMatrixView,
+  ],
+  exports: [
+    ConnectivityMatrixView,
+  ],
+  providers: [
+    SAPI,
+  ]
+})
+
+export class SapiViewsFeatureConnectivityModule{}
\ No newline at end of file
diff --git a/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiograph.stories.ts b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiograph.stories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c8c33226cfa700d332307be2de1827017ac0de93
--- /dev/null
+++ b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiograph.stories.ts
@@ -0,0 +1,121 @@
+import { CommonModule } from "@angular/common"
+import { HttpClientModule } from "@angular/common/http"
+import { Component, ViewChild } from "@angular/core"
+import { FormsModule } from "@angular/forms"
+import { BrowserAnimationsModule } from "@angular/platform-browser/animations"
+import { Meta, moduleMetadata, Story } from "@storybook/angular"
+import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi"
+import { getHoc1Features, getHoc1Left, getHumanAtlas, getJba29, getMni152, HumanHoc1StoryBase } from "src/atlasComponents/sapi/stories.base"
+import { AngularMaterialModule } from "src/sharedModules"
+import { Autoradiography } from "./autoradiography.component"
+
+@Component({
+  selector: 'autoradiograph-wrapper-cmp',
+  template: `
+  <mat-form-field appearance="fill">
+    <mat-select [(ngModel)]="selectedSymbol">
+      <mat-option value="" disabled>
+        --select--
+      </mat-option>
+
+      <mat-option [value]="option"
+        *ngFor="let option of options">
+        {{ option }}
+      </mat-option>
+    </mat-select>
+  </mat-form-field>
+  <sxplr-sapiviews-features-receptor-autoradiograph
+    class="d-inline-block w-100 h-100"
+    [sxplr-sapiviews-features-receptor-atlas]="atlas"
+    [sxplr-sapiviews-features-receptor-parcellation]="parcellation"
+    [sxplr-sapiviews-features-receptor-template]="template"
+    [sxplr-sapiviews-features-receptor-region]="region"
+    [sxplr-sapiviews-features-receptor-featureid]="featureId"
+    [sxplr-sapiviews-features-receptor-autoradiograph-selected-symbol]="selectedSymbol"
+  >
+  </sxplr-sapiviews-features-receptor-autoradiograph>
+  `,
+  styles: [
+    `
+    :host
+    {
+      display: block;
+      max-width: 24rem;
+      max-height: 24rem;
+    }
+    `
+  ]
+})
+class AutoRadiographWrapperCls {
+  atlas: SapiAtlasModel
+  parcellation: SapiParcellationModel
+  template: SapiSpaceModel
+  region: SapiRegionModel
+  featureId: string
+
+  @ViewChild(Autoradiography)
+  ar: Autoradiography
+
+  selectedSymbol: string
+
+  get options(){
+    return Object.keys(this.ar?.receptorData?.data?.autoradiographs || {})
+  }
+}
+
+export default {
+  component: AutoRadiographWrapperCls,
+  decorators: [
+    moduleMetadata({
+      imports: [
+        CommonModule,
+        AngularMaterialModule,
+        HttpClientModule,
+        BrowserAnimationsModule,
+        FormsModule,
+      ],
+      providers: [
+        SAPI
+      ],
+      declarations: [
+        Autoradiography
+      ]
+    })
+  ],
+} as Meta
+
+const Template: Story<AutoRadiographWrapperCls> = (args: AutoRadiographWrapperCls, { loaded }) => {
+  const { atlas, parc, space, region, receptorfeat } = loaded
+  return ({
+    props: {
+      ...args,
+      atlas: atlas,
+      parcellation: parc,
+      template: space,
+      region: region,
+      featureId: receptorfeat["@id"]
+    },
+  })
+}
+
+Template.loaders = [
+  async () => {
+    const atlas = await getHumanAtlas()
+    const parc = await getJba29()
+    const region = await getHoc1Left()
+    const space = await getMni152()
+    const features = await getHoc1Features()
+    const receptorfeat = features.find(f => f.type === "siibra/receptor")
+    return {
+      atlas, parc, space, region, receptorfeat
+    }
+  }
+]
+
+export const Default = Template.bind({})
+Default.args = {
+
+}
+Default.loaders = [
+  ...Template.loaders
+]
\ No newline at end of file
diff --git a/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.component.ts b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.component.ts
index f7d764069608079a7c893d4df7aaa3662ae6f3c0..8e6c522938bc6f4a81985615448b0727620186f6 100644
--- a/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.component.ts
+++ b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.component.ts
@@ -1,11 +1,77 @@
-import { Component } from "@angular/core";
+import { AfterViewInit, Component, ElementRef, Input, OnChanges, SimpleChanges } from "@angular/core";
+import { SAPI } from "src/atlasComponents/sapi";
+import { PARSE_TYPEDARRAY } from "src/atlasComponents/sapi/sapi.service";
 import { BaseReceptor } from "../base";
 
 @Component({
+  selector: `sxplr-sapiviews-features-receptor-autoradiograph`,
   templateUrl: './autoradiography.template.html',
   styleUrls: [
     './autoradiography.style.css'
-  ]
+  ],
+  exportAs: 'sxplrSapiviewsFeaturesReceptorAR'
 })
 
-export class Autoradiography extends BaseReceptor{}
\ No newline at end of file
+export class Autoradiography extends BaseReceptor implements OnChanges, AfterViewInit{
+  
+  @Input('sxplr-sapiviews-features-receptor-autoradiograph-selected-symbol')
+  selectedSymbol: string
+
+  private pleaseRender = false
+
+  width: number
+  height: number
+  renderBuffer: Uint8ClampedArray
+
+  async ngOnChanges(simpleChanges: SimpleChanges) {
+    
+    if (this.baseInputChanged(simpleChanges)) {
+      await this.fetchReceptorData()
+    }
+
+    if (this.selectedSymbol) {
+      const fp = this.receptorData.data.autoradiographs[this.selectedSymbol]
+      if (!fp) {
+        this.error = `selectedSymbol ${this.selectedSymbol} cannot be found. Valid symbols are ${Object.keys(this.receptorData.data.autoradiographs)}`
+        return
+      }
+      const { "x-width": width, "x-height": height } = fp
+      
+      this.width = width
+      this.height = height
+
+      const { result } = await this.sapi.processNpArrayData<PARSE_TYPEDARRAY.CANVAS_FORTRAN_RGBA>(fp, PARSE_TYPEDARRAY.CANVAS_FORTRAN_RGBA)
+      this.renderBuffer = result
+      this.renderCanvas()
+    }
+  }
+  constructor(sapi: SAPI, private el: ElementRef){
+    super(sapi)
+  }
+
+  ngAfterViewInit(): void {
+    if (this.pleaseRender) this.renderCanvas()
+  }
+
+  private renderCanvas(){
+    if (!this.el) {
+      this.pleaseRender = true
+      return
+    }
+
+    const arContainer = (this.el.nativeElement as HTMLElement)
+    while (arContainer.firstChild) {
+      arContainer.removeChild(arContainer.firstChild)
+    }
+
+    const canvas = document.createElement("canvas")
+    canvas.height = this.height
+    canvas.width = this.width
+    arContainer.appendChild(canvas)
+    const ctx = canvas.getContext("2d")
+    const imgData = ctx.createImageData(this.width, this.height)
+    imgData.data.set(this.renderBuffer)
+    ctx.putImageData(imgData, 0, 0)
+    this.pleaseRender = false
+  }
+}
diff --git a/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.style.css b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.style.css
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..833dd425a375f37daf5e6e5c9bade25acf79ac1f 100644
--- a/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.style.css
+++ b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.style.css
@@ -0,0 +1,5 @@
+:host >>> canvas
+{
+  max-width: 100%;
+  max-height: 100%;
+}
\ No newline at end of file
diff --git a/src/atlasComponents/sapiViews/features/receptors/base.ts b/src/atlasComponents/sapiViews/features/receptors/base.ts
index b627e844b6af09b14896584108f6a675464a886a..2d0a047b2d4aab36a0f643533d760edd9951b2ff 100644
--- a/src/atlasComponents/sapiViews/features/receptors/base.ts
+++ b/src/atlasComponents/sapiViews/features/receptors/base.ts
@@ -1,30 +1,44 @@
-import { Input } from "@angular/core";
+import { Input, SimpleChanges } from "@angular/core";
 import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi";
 import { SapiRegionalFeatureReceptorModel } from "src/atlasComponents/sapi/type";
 
 export class BaseReceptor{
   
-  @Input('sxplor-sapiviews-features-receptor-atlas')
+  @Input('sxplr-sapiviews-features-receptor-atlas')
   atlas: SapiAtlasModel
   
-  @Input('sxplor-sapiviews-features-receptor-parcellation')
+  @Input('sxplr-sapiviews-features-receptor-parcellation')
   parcellation: SapiParcellationModel
 
-  @Input('sxplor-sapiviews-features-receptor-template')
+  @Input('sxplr-sapiviews-features-receptor-template')
   template: SapiSpaceModel
   
-  @Input('sxplor-sapiviews-features-receptor-region')
+  @Input('sxplr-sapiviews-features-receptor-region')
   region: SapiRegionModel
 
-  @Input('sxplor-sapiviews-features-receptor-featureid')
+  @Input('sxplr-sapiviews-features-receptor-featureid')
   featureId: string
 
   receptorData: SapiRegionalFeatureReceptorModel
 
   error: string
 
-  async ngOnChanges(){
-    console.log("ngOnchanges called", this)
+  protected baseInputChanged(simpleChanges: SimpleChanges) {
+    const {
+      atlas,
+      parcellation,
+      template,
+      region,
+      featureId,
+    } = simpleChanges
+    return atlas
+      || parcellation
+      || template
+      || region
+      || featureId
+  }
+
+  protected async fetchReceptorData() {
     this.error = null
     if (!this.atlas) {
       this.error = `atlas needs to be defined, but is not`
@@ -42,18 +56,15 @@ export class BaseReceptor{
       this.error = `featureId needs to be defined, but is not`
       return
     }
-    console.log("fetching!")
     const result = await this.sapi.getRegion(this.atlas["@id"], this.parcellation["@id"], this.region.name).getFeatureInstance(this.featureId, this.template["@id"])
     if (result.type !== "siibra/receptor") {
       throw new Error(`BaseReceptor Error. Expected .type to be "siibra/receptor", but was "${result.type}"`)
     }
     this.receptorData = result
-    console.log(this.receptorData)
   }
 
   constructor(
     protected sapi: SAPI
   ){
-    console.log('constructor called')
   }
 }
diff --git a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.component.ts b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.component.ts
index f1788bcca0a4464f1d5cc4bc7db45777acc248ad..3fd3f1760f2a1c4ab484cbb6c31b1abf22f405e5 100644
--- a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.component.ts
+++ b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.component.ts
@@ -1,7 +1,44 @@
-import { Component, OnChanges } from "@angular/core";
+import { AfterViewInit, Component, ElementRef, EventEmitter, HostListener, OnChanges, OnDestroy, Output, SimpleChanges, ViewChild } from "@angular/core";
+import { fromEvent, Subscription } from "rxjs";
+import { distinctUntilChanged, map } from "rxjs/operators";
 import { SAPI } from "src/atlasComponents/sapi";
+import { SapiRegionalFeatureReceptorModel } from "src/atlasComponents/sapi/type";
 import { BaseReceptor } from "../base";
 
+/**
+ * kg-dataset-dumb-radar requires input to be a different shape
+ * once the the return signature
+ */
+type RequiredType = {
+  receptor: {
+    label: string
+  }
+  density: {
+    mean: number
+    sd: number
+    unit: string
+  }
+}
+
+function transformRadar(input: SapiRegionalFeatureReceptorModel['data']['fingerprints']): RequiredType[]{
+  const listRequired: RequiredType[] = []
+  for (const key in input) {
+    const item = input[key]
+    listRequired.push({
+      receptor: {
+        label: key
+      },
+      density: {
+        mean: item.mean,
+        sd: item.std,
+        unit: item.unit
+      }
+    })
+    
+  }
+  return listRequired
+}
+
 @Component({
   selector: 'sxplr-sapiviews-features-receptor-fingerprint',
   templateUrl: './fingerprint.template.html',
@@ -10,14 +47,66 @@ import { BaseReceptor } from "../base";
   ]
 })
 
-export class Fingerprint extends BaseReceptor implements OnChanges{
+export class Fingerprint extends BaseReceptor implements OnChanges, AfterViewInit, OnDestroy{
+
+  @Output('sxplr-sapiviews-features-receptor-fingerprint-receptor-selected')
+  selectReceptor = new EventEmitter<string>()
+
+  @HostListener('click')
+  onClick(){
+    if (this.mouseOverReceptor) {
+      this.selectReceptor.emit(this.mouseOverReceptor)
+    }
+  }
 
-  async ngOnChanges() {
-    console.log('ng on changes claled onc hild')
-    await super.ngOnChanges()
+  async ngOnChanges(simpleChanges: SimpleChanges) {
+    if (this.baseInputChanged(simpleChanges)) {
+      await this.fetchReceptorData()
+    }
+    if (this.receptorData) {
+      this.setDumbRadar()
+    }
   }
-  constructor(sapi: SAPI){
+  constructor(sapi: SAPI, private el: ElementRef){
     super(sapi)
-    console.log(this.atlas)
+  }
+
+  get dumbRadarCmp(){
+    return this.el?.nativeElement?.querySelector('kg-dataset-dumb-radar')
+  }
+
+  private setDumbRadarPlease = false
+  private sub: Subscription[] = []
+  private mouseOverReceptor: string
+
+  ngOnDestroy(){
+    while (this.sub.length > 0) this.sub.pop().unsubscribe()
+  }
+
+  ngAfterViewInit(){
+    if (this.setDumbRadarPlease) {
+      this.setDumbRadar()
+    }
+
+    this.sub.push(
+      fromEvent<CustomEvent>(this.el.nativeElement, 'kg-ds-prv-regional-feature-mouseover').pipe(
+        map(ev => ev.detail?.data?.receptor?.label),
+        distinctUntilChanged(),
+      ).subscribe(label => {
+        this.mouseOverReceptor = label
+      })
+    )
+  }
+  
+  setDumbRadar(){
+    if (!this.dumbRadarCmp) {
+      this.setDumbRadarPlease = true
+      return
+    }
+    
+    this.dumbRadarCmp.metaBs = this.receptorData.data.receptor_symbols
+    this.dumbRadarCmp.radar= transformRadar(this.receptorData.data.fingerprints)
+
+    this.setDumbRadarPlease = false
   }
 }
diff --git a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.stories.ts b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.stories.ts
index fbb8ffb74558cf86a63eafba09683eb058b5a167..014a062467786bf42e0d053dd930ecb052a2748f 100644
--- a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.stories.ts
+++ b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.stories.ts
@@ -1,27 +1,36 @@
-import { CommonModule } from "@angular/common"
+import { CommonModule, DOCUMENT } from "@angular/common"
 import { HttpClientModule } from "@angular/common/http"
-import { Component, OnDestroy } from "@angular/core"
+import { Component, EventEmitter, Output } from "@angular/core"
 import { Meta, moduleMetadata, Story } from "@storybook/angular"
-import { forkJoin, Observable, Observer, Subscription } from "rxjs"
-import { map, switchMap } from "rxjs/operators"
 import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi"
 import { getHoc1Features, getHoc1Left, getHumanAtlas, getJba29, getMni152, HumanHoc1StoryBase } from "src/atlasComponents/sapi/stories.base"
 import { AngularMaterialModule } from "src/sharedModules"
-import { BaseReceptor } from "../base"
-import { Fingerprint } from "./fingerprint.component"
+import { appendScriptFactory, APPEND_SCRIPT_TOKEN } from "src/util/constants"
+import { ReceptorViewModule } from ".."
 
 @Component({
   selector: 'fingerprint-wrapper-cmp',
   template: `
   <sxplr-sapiviews-features-receptor-fingerprint
-    [sxplor-sapiviews-features-receptor-atlas]="atlas"
-    [sxplor-sapiviews-features-receptor-parcellation]="parcellation"
-    [sxplor-sapiviews-features-receptor-template]="template"
-    [sxplor-sapiviews-features-receptor-region]="region"
-    [sxplor-sapiviews-features-receptor-featureid]="featureId"
+    [sxplr-sapiviews-features-receptor-atlas]="atlas"
+    [sxplr-sapiviews-features-receptor-parcellation]="parcellation"
+    [sxplr-sapiviews-features-receptor-template]="template"
+    [sxplr-sapiviews-features-receptor-region]="region"
+    [sxplr-sapiviews-features-receptor-featureid]="featureId"
+    (sxplr-sapiviews-features-receptor-fingerprint-receptor-selected)="selectReceptor.emit($event)"
   >
   </sxplr-sapiviews-features-receptor-fingerprint>
-  `
+  `,
+  styles: [
+    `
+    :host
+    {
+      display: block;
+      width: 20rem;
+      height: 20rem;
+    }
+    `
+  ]
 })
 class FingerprintWrapperCls {
   atlas: SapiAtlasModel
@@ -30,6 +39,8 @@ class FingerprintWrapperCls {
   region: SapiRegionModel
   featureId: string
 
+  @Output()
+  selectReceptor = new EventEmitter()
 }
 
 export default {
@@ -40,20 +51,23 @@ export default {
         CommonModule,
         AngularMaterialModule,
         HttpClientModule,
+        ReceptorViewModule,
       ],
       providers: [
-        SAPI
+        SAPI,
+        {
+          provide: APPEND_SCRIPT_TOKEN,
+          useFactory: appendScriptFactory,
+          deps: [ DOCUMENT ]
+        }
       ],
-      declarations: [
-        Fingerprint
-      ]
+      declarations: []
     })
   ],
 } as Meta
 
 const Template: Story<FingerprintWrapperCls> = (args: FingerprintWrapperCls, { loaded }) => {
   const { atlas, parc, space, region, receptorfeat } = loaded
-  console.log({ atlas, parc, space, region, receptorfeat })
   return ({
     props: {
       ...args,
diff --git a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.template.html b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.template.html
index 70cc0121402142353ee134383b609ccdb4f299d2..1588598e3ece35f1f7353139cf2b61a9d662f395 100644
--- a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.template.html
+++ b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.template.html
@@ -1,5 +1,6 @@
 <div *ngIf="error">
   {{ error }}
 </div>
-<!-- <kg-dataset-dumb-radar [attr.kg-ds-prv-darkmode]="true">
-</kg-dataset-dumb-radar> -->
\ No newline at end of file
+
+<kg-dataset-dumb-radar>
+</kg-dataset-dumb-radar>
\ No newline at end of file
diff --git a/src/atlasComponents/sapiViews/features/receptors/module.ts b/src/atlasComponents/sapiViews/features/receptors/module.ts
index 4fdeaf55ceb7fc46ec5761bc11e2a86832f145fe..a1229981ae85fd58e141c4ecb59a7add5f61d872 100644
--- a/src/atlasComponents/sapiViews/features/receptors/module.ts
+++ b/src/atlasComponents/sapiViews/features/receptors/module.ts
@@ -1,5 +1,6 @@
 import { CommonModule } from "@angular/common";
-import { NgModule } from "@angular/core";
+import { APP_INITIALIZER, NgModule } from "@angular/core";
+import { APPEND_SCRIPT_TOKEN } from "src/util/constants";
 import { Autoradiography } from "./autoradiography/autoradiography.component";
 import { Fingerprint } from "./fingerprint/fingerprint.component"
 import { Profile } from "./profile/profile.component"
@@ -17,7 +18,22 @@ import { Profile } from "./profile/profile.component"
     Autoradiography,
     Fingerprint,
     Profile,
-  ]
+  ],
+  providers: [{
+    provide: APP_INITIALIZER,
+    multi: true,
+    useFactory: (appendScriptFn: (url: string) => Promise<any>) => {
+
+      const libraries = [
+        'https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js',
+        'https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.1.2/es5/tex-svg.js'
+      ]
+      return () => Promise.all(libraries.map(appendScriptFn))
+    },
+    deps: [
+      APPEND_SCRIPT_TOKEN
+    ]
+  }]
 })
 
 export class ReceptorViewModule{}
diff --git a/src/atlasComponents/userAnnotations/tools/service.ts b/src/atlasComponents/userAnnotations/tools/service.ts
index c6f075adc864af1874b54662caab6eea5a362066..546cd1238464c9830f8778d91bc38d1810c9c62c 100644
--- a/src/atlasComponents/userAnnotations/tools/service.ts
+++ b/src/atlasComponents/userAnnotations/tools/service.ts
@@ -7,7 +7,7 @@ import {map, switchMap, filter, shareReplay, pairwise } from "rxjs/operators";
 import { NehubaViewerUnit } from "src/viewerModule/nehuba";
 import { NEHUBA_INSTANCE_INJTKN } from "src/viewerModule/nehuba/util";
 import { AbsToolClass, ANNOTATION_EVENT_INJ_TOKEN, IAnnotationEvents, IAnnotationGeometry, INgAnnotationTypes, INJ_ANNOT_TARGET, TAnnotationEvent, ClassInterface, TCallbackFunction, TSands, TGeometryJson, TNgAnnotationLine, TCallback } from "./type";
-import { switchMapWaitFor } from "src/util/fn";
+import { getExportNehuba, switchMapWaitFor } from "src/util/fn";
 import { Polygon } from "./poly";
 import { Line } from "./line";
 import { Point } from "./point";
@@ -583,14 +583,14 @@ export class ModularUserAnnotationToolService implements OnDestroy{
     const bin = atob(encoded)
     
     await retry(() => {
-      if (!!(window as any).export_nehuba) return true
+      if (!!getExportNehuba()) return true
       else throw new Error(`export nehuba not yet ready`)
     }, {
       timeout: 1000,
       retries: 10
     })
     
-    const { pako } = (window as any).export_nehuba
+    const { pako } = getExportNehuba()
     const decoded = pako.inflate(bin, { to: 'string' })
     const arr = JSON.parse(decoded)
     const anns: IAnnotationGeometry[] = []
@@ -626,8 +626,9 @@ export class ModularUserAnnotationToolService implements OnDestroy{
       arr.push(json)
     }
     const stringifiedJSON = JSON.stringify(arr)
-    if (!(window as any).export_nehuba) return
-    const { pako } = (window as any).export_nehuba
+    const exportNehuba = getExportNehuba()
+    if (!exportNehuba) return
+    const { pako } = exportNehuba
     const compressed = pako.deflate(stringifiedJSON)
     let out = ''
     for (const num of compressed) {
diff --git a/src/atlasViewer/atlasViewer.apiService.service.ts b/src/atlasViewer/atlasViewer.apiService.service.ts
index 48906afedcd635979c4713c7f104c5f00a23b03f..f4529026d680352e9e3902e8642ac3f371edc77c 100644
--- a/src/atlasViewer/atlasViewer.apiService.service.ts
+++ b/src/atlasViewer/atlasViewer.apiService.service.ts
@@ -231,11 +231,6 @@ export class AtlasViewerAPIServices implements OnDestroy{
 
         layersRegionLabelIndexMap: new Map(),
 
-        datasetsBSubject : this.store.pipe(
-          select('dataStore'),
-          select('fetchedDataEntries'),
-          startWith([])
-        ),
       },
       uiHandle : {
         getModalHandler : () => {
@@ -369,7 +364,6 @@ export interface IInteractiveViewerInterface {
     loadedTemplates: any[]
     regionsLabelIndexMap: Map<number, any> | null
     layersRegionLabelIndexMap: Map<string, Map<number, any>>
-    datasetsBSubject: Observable<any[]>
   }
 
   viewerHandle?: IVIewerHandle
diff --git a/src/messaging/nmvSwc/index.ts b/src/messaging/nmvSwc/index.ts
index 08b0ce6994d4ad595d3aa1689e872e8d45a702b8..2f4e7586970acd9450a6456fb9fe6e06ba7ddea9 100644
--- a/src/messaging/nmvSwc/index.ts
+++ b/src/messaging/nmvSwc/index.ts
@@ -1,5 +1,5 @@
 import { Observable, Subject } from "rxjs"
-import { getUuid } from "src/util/fn"
+import { getExportNehuba, getUuid } from "src/util/fn"
 import { IMessagingActions, IMessagingActionTmpl, TVec4, TMat4 } from "../types"
 import { INmvTransform } from "./type"
 
@@ -110,7 +110,7 @@ export const processJsonLd = (json: { [key: string]: any }): Observable<IMessagi
       }
     })
 
-    await waitFor(() => !!(window as any).export_nehuba)
+    await waitFor(() => !!getExportNehuba())
 
     const b64Encoded = encoding.indexOf('base64') >= 0
     const isGzipped = encoding.indexOf('gzip') >= 0
@@ -119,7 +119,7 @@ export const processJsonLd = (json: { [key: string]: any }): Observable<IMessagi
       data = atob(data)
     }
     if (isGzipped) {
-      data = (window as any).export_nehuba.pako.inflate(data)
+      data = getExportNehuba().pako.inflate(data)
     }
     let output = ``
     for (let i = 0; i < data.length; i++) {
@@ -146,7 +146,7 @@ export const processJsonLd = (json: { [key: string]: any }): Observable<IMessagi
     ]
     // NG translation works on nm scale
     const scaleUmToNm = 1e3
-    const { mat3, vec3 } = (window as any).export_nehuba
+    const { mat3, vec3 } = getExportNehuba()
     const modA = mat3.fromValues(
       scaleUmToVoxelFixed[0], 0, 0,
       0, scaleUmToVoxelFixed[1], 0,
diff --git a/src/services/state/userConfigState.store.ts b/src/services/state/userConfigState.store.ts
index 4c869cd8f9ee3a752e5572752f910882b1bad319..f5906c02c01085998f4dc95dcd9784ec52cd0522 100644
--- a/src/services/state/userConfigState.store.ts
+++ b/src/services/state/userConfigState.store.ts
@@ -1,5 +1,5 @@
 import { Injectable, OnDestroy } from "@angular/core";
-import { Actions, Effect, ofType } from "@ngrx/effects";
+import { Actions, createEffect, Effect, ofType } from "@ngrx/effects";
 import { Action, createAction, createReducer, props, select, Store, on, createSelector } from "@ngrx/store";
 import { of, Subscription } from "rxjs";
 import { catchError, filter, map } from "rxjs/operators";
@@ -7,8 +7,8 @@ import { LOCAL_STORAGE_CONST } from "src/util//constants";
 // Get around the problem of importing duplicated string (ACTION_TYPES), even using ES6 alias seems to trip up the compiler
 // TODO file bug and reverse
 import { HttpClient } from "@angular/common/http";
-import { actionSetMobileUi } from "./viewerState/actions";
 import { PureContantService } from "src/util";
+import * as stateCtrl from "src/state"
 
 interface ICsp{
   'connect-src'?: string[]
@@ -94,6 +94,13 @@ export class UserConfigStateUseEffect implements OnDestroy {
 
   private subscriptions: Subscription[] = []
 
+  storeUseMobileInLocalStorage = createEffect(() => this.actions$.pipe(
+    ofType(stateCtrl.userInterface.actions.useModileUi),
+    map(({ flag }) => {
+      window.localStorage.setItem(LOCAL_STORAGE_CONST.MOBILE_UI, JSON.stringify(flag))
+    })
+  ), { dispatch: false })
+
   constructor(
     private actions$: Actions,
     private store$: Store<any>,
@@ -114,21 +121,6 @@ export class UserConfigStateUseEffect implements OnDestroy {
         }
       }),
     )
-
-    this.subscriptions.push(
-      this.actions$.pipe(
-        ofType(actionSetMobileUi.type),
-        map((action: any) => {
-          const { payload } = action
-          const { useMobileUI } = payload
-          return useMobileUI
-        }),
-        filter(bool => bool !== null),
-      ).subscribe((bool: boolean) => {
-        window.localStorage.setItem(LOCAL_STORAGE_CONST.MOBILE_UI, JSON.stringify(bool))
-      }),
-    )
-
   }
 
   public ngOnDestroy() {
diff --git a/src/services/state/viewerConfig.store.helper.ts b/src/services/state/viewerConfig.store.helper.ts
index 719b4745979f04cc7df9c98172dbc17bdf09c965..7dc4a3c6c184e0f56ea034d9990c5dff58e52825 100644
--- a/src/services/state/viewerConfig.store.helper.ts
+++ b/src/services/state/viewerConfig.store.helper.ts
@@ -7,7 +7,7 @@ export interface IViewerConfigState {
   useMobileUI: boolean
 }
 
-export const viewerConfigSelectorUseMobileUi = createSelector(
-  state => state[VIEWER_CONFIG_FEATURE_KEY],
-  viewerConfigState => viewerConfigState.useMobileUI
-)
+// export const viewerConfigSelectorUseMobileUi = createSelector(
+//   state => state[VIEWER_CONFIG_FEATURE_KEY],
+//   viewerConfigState => viewerConfigState.useMobileUI
+// )
diff --git a/src/services/state/viewerConfig.store.ts b/src/services/state/viewerConfig.store.ts
index 27850b946bd2e0ca51f8616c91fd5a9b6ff52a23..650ba69c8ab78341ebd44c6b3baa7d07f299ea4c 100644
--- a/src/services/state/viewerConfig.store.ts
+++ b/src/services/state/viewerConfig.store.ts
@@ -2,7 +2,6 @@ import { Action } from "@ngrx/store";
 import { LOCAL_STORAGE_CONST } from "src/util/constants";
 
 import { IViewerConfigState as StateInterface } from './viewerConfig.store.helper'
-import { actionSetMobileUi } from "./viewerState/actions";
 export { StateInterface }
 
 interface ViewerConfigurationAction extends Action {
@@ -23,7 +22,6 @@ export const CONFIG_CONSTANTS = {
 export const VIEWER_CONFIG_ACTION_TYPES = {
   SET_ANIMATION: `SET_ANIMATION`,
   UPDATE_CONFIG: `UPDATE_CONFIG`,
-  SET_MOBILE_UI: actionSetMobileUi.type,
 }
 
 // get gpu limit
@@ -58,14 +56,6 @@ export const defaultState: StateInterface = {
 
 export const getStateStore = ({ state = defaultState } = {}) => (prevState: StateInterface = state, action: ViewerConfigurationAction) => {
   switch (action.type) {
-  case VIEWER_CONFIG_ACTION_TYPES.SET_MOBILE_UI: {
-    const { payload } = action
-    const { useMobileUI } = payload
-    return {
-      ...prevState,
-      useMobileUI,
-    }
-  }
   case VIEWER_CONFIG_ACTION_TYPES.UPDATE_CONFIG:
     return {
       ...prevState,
diff --git a/src/services/state/viewerState/actions.ts b/src/services/state/viewerState/actions.ts
index 0157c15f83ad1cb8a1fcf84302db772d883a98f0..aa5b5aad286a38154a281d31ee58d489712f08d0 100644
--- a/src/services/state/viewerState/actions.ts
+++ b/src/services/state/viewerState/actions.ts
@@ -24,8 +24,3 @@ export const viewerStateMouseOverCustomLandmarkInPerspectiveView = createAction(
   `[viewerState] mouseOverCustomLandmarkInPerspectiveView`,
   props<{ payload: { label: string } }>()
 )
-
-export const actionSetMobileUi = createAction(
-  `[viewerState] setMobileUi`,
-  props<{ payload: { useMobileUI: boolean } }>()
-)
diff --git a/src/services/stateStore.service.ts b/src/services/stateStore.service.ts
index 558823d1ba54dced97da93a2a6c251833999bfda..ceef165258252e721d88e3215f8c8917a99a45b8 100644
--- a/src/services/stateStore.service.ts
+++ b/src/services/stateStore.service.ts
@@ -119,13 +119,11 @@ export function isDefined(obj) {
 export interface IavRootStoreInterface {
   viewerConfigState: ViewerConfigStateInterface
   ngViewerState: NgViewerStateInterface
-  dataStore: any
   uiState: IUiState
   userConfigState: UserConfigStateInterface
 }
 
 export const defaultRootState: any = {
-  dataStore: {},
   ngViewerState: ngViewerDefaultState,
   uiState: uiDefaultState,
   userConfigState: userConfigDefaultState,
diff --git a/src/state/userInteraction/actions.ts b/src/state/userInteraction/actions.ts
index b7d30d2de689972864d6fede14753ee376525600..fd21b4fd0068f01e69c04468e18fe1c75c314908 100644
--- a/src/state/userInteraction/actions.ts
+++ b/src/state/userInteraction/actions.ts
@@ -1,7 +1,7 @@
 import { createAction, props } from "@ngrx/store"
 import { nameSpace } from "./const"
 import * as atlasSelection from "../atlasSelection"
-import { SapiRegionModel } from "src/atlasComponents/sapi"
+import { SapiRegionModel, SapiSpatialFeatureModel, SapiVolumeModel } from "src/atlasComponents/sapi"
 import * as userInterface from "../userInterface"
 
 export const {
@@ -31,3 +31,14 @@ export const mouseoverRegions = createAction(
     regions: SapiRegionModel[]
   }>()
 )
+
+export const showFeature = createAction(
+  `${nameSpace} showFeature`,
+  props<{
+    feature: SapiSpatialFeatureModel
+  }>()
+)
+
+export const clearShownFeature = createAction(
+  `${nameSpace} clearShownFeature`,
+)
diff --git a/src/state/userInteraction/selectors.ts b/src/state/userInteraction/selectors.ts
index 5b9b0bd79c821d3d21e31b6482894340775974b4..b9aa84c6147f3427c7e5652749fa537aaa336648 100644
--- a/src/state/userInteraction/selectors.ts
+++ b/src/state/userInteraction/selectors.ts
@@ -1,5 +1,4 @@
-import { createAction, createSelector, props } from "@ngrx/store";
-import { SapiRegionModel } from "src/atlasComponents/sapi";
+import { createSelector } from "@ngrx/store";
 import { nameSpace } from "./const"
 import { UserInteraction } from "./store";
 
@@ -9,3 +8,8 @@ export const mousingOverRegions = createSelector(
   selectStore,
   state => state.mouseoverRegions
 )
+
+export const selectedFeature = createSelector(
+  selectStore,
+  state => state.selectedFeature
+)
diff --git a/src/state/userInteraction/store.ts b/src/state/userInteraction/store.ts
index 83bf75863f3c1c76733851f74a0930dea37afc5a..0ce97c2ff9c60708d2867fb93af6a9d660520c21 100644
--- a/src/state/userInteraction/store.ts
+++ b/src/state/userInteraction/store.ts
@@ -1,12 +1,14 @@
 import { createReducer, on } from "@ngrx/store";
-import { SapiRegionModel } from "src/atlasComponents/sapi";
+import { SapiRegionModel, SapiFeatureModel } from "src/atlasComponents/sapi";
 import * as actions from "./actions"
 
 export type UserInteraction = {
   mouseoverRegions: SapiRegionModel[]
+  selectedFeature: SapiFeatureModel
 }
 
 const defaultState: UserInteraction = {
+  selectedFeature: null,
   mouseoverRegions: []
 }
 
@@ -20,5 +22,23 @@ export const reducer = createReducer(
         mouseoverRegions: regions
       }
     }
+  ),
+  on(
+    actions.showFeature,
+    (state, { feature }) => {
+      return {
+        ...state,
+        selectedFeature: feature
+      }
+    }
+  ),
+  on(
+    actions.clearShownFeature,
+    state => {
+      return {
+        ...state,
+        selectedFeature: null
+      }
+    }
   )
-)
\ No newline at end of file
+)
diff --git a/src/state/userInterface/actions.ts b/src/state/userInterface/actions.ts
index f229006bc473dc280521befb7e4cf1ae8fa78c6a..1e80ac94aa3053e7f8d519ff364918b5ee2bf026 100644
--- a/src/state/userInterface/actions.ts
+++ b/src/state/userInterface/actions.ts
@@ -4,19 +4,13 @@ import { MatSnackBarConfig } from "@angular/material/snack-bar";
 import { createAction, props } from "@ngrx/store";
 import { nameSpace } from "./const"
 
-export const showFeature = createAction(
-  `${nameSpace} showFeature`,
+export const useModileUi = createAction(
+  `${nameSpace} useMobileUi`,
   props<{
-    feature: {
-      "@id": string
-    }
+    flag: boolean
   }>()
 )
 
-export const clearShownFeature = createAction(
-  `${nameSpace} clearShownFeature`,
-)
-
 export const openSidePanel = createAction(
   `${nameSpace} openSidePanel`
 )
diff --git a/src/state/userInterface/selectors.ts b/src/state/userInterface/selectors.ts
index fd36b44ec95d78fb394e3c17ad785661e02d7fa8..15705846fcdfab1a412be6b9948497868c41a8e4 100644
--- a/src/state/userInterface/selectors.ts
+++ b/src/state/userInterface/selectors.ts
@@ -4,7 +4,7 @@ import { UiStore } from "./store"
 
 const selectStore = state => state[nameSpace] as UiStore
 
-export const selectedFeature = createSelector(
+export const useMobileUi = createSelector(
   selectStore,
-  state => state.selectedFeature
+  state => state.useMobileUi
 )
diff --git a/src/state/userInterface/store.ts b/src/state/userInterface/store.ts
index 400e7983b59269a4c47983d42797d3b63e2ea788..aabe2f556b529a7adecc30cd273add55878488fe 100644
--- a/src/state/userInterface/store.ts
+++ b/src/state/userInterface/store.ts
@@ -1,33 +1,23 @@
 import { createReducer, on } from "@ngrx/store";
-import { SapiVolumeModel } from "src/atlasComponents/sapi";
 import * as actions from "./actions"
 
 export type UiStore = {
-  selectedFeature: SapiVolumeModel
+  useMobileUi: boolean
 }
 
 const defaultStore: UiStore = {
-  selectedFeature: null
+  useMobileUi: false
 }
 
 export const reducer = createReducer(
   defaultStore,
   on(
-    actions.showFeature,
-    (state, { feature }) => {
+    actions.useModileUi,
+    (state, { flag }) => {
       return {
         ...state,
-        feature
+        useMobileUi: flag
       }
     }
   ),
-  on(
-    actions.clearShownFeature,
-    state => {
-      return {
-        ...state,
-        feature: null
-      }
-    }
-  )
 )
diff --git a/src/ui/config/configCmp/config.component.ts b/src/ui/config/configCmp/config.component.ts
index f34ca0eec31149a10ab3b96a425b2cf8ebfd5bee..ffc01128ffd8c75aa9be53fd981fa811d5d1f8f3 100644
--- a/src/ui/config/configCmp/config.component.ts
+++ b/src/ui/config/configCmp/config.component.ts
@@ -13,6 +13,7 @@ import { PureContantService } from 'src/util';
 import { ngViewerActionSwitchPanelMode } from 'src/services/state/ngViewerState/actions';
 import { ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from 'src/services/state/ngViewerState/selectors';
 import { atlasSelection } from 'src/state';
+import * as stateCtrl from "src/state"
 
 const GPU_TOOLTIP = `Higher GPU usage can cause crashes on lower end machines`
 const ANIMATION_TOOLTIP = `Animation can cause slowdowns in lower end machines`
@@ -115,12 +116,11 @@ export class ConfigComponent implements OnInit, OnDestroy {
 
   public toggleMobileUI(ev: MatSlideToggleChange) {
     const { checked } = ev
-    this.store.dispatch({
-      type: VIEWER_CONFIG_ACTION_TYPES.SET_MOBILE_UI,
-      payload: {
-        useMobileUI: checked,
-      },
-    })
+    this.store.dispatch(
+      stateCtrl.userInterface.actions.useModileUi({
+        flag: checked
+      })
+    )
   }
 
   public toggleAnimationFlag(ev: MatSlideToggleChange ) {
diff --git a/src/util/pureConstant.service.ts b/src/util/pureConstant.service.ts
index 9ffc4b9e992880421d931a88083f73bed093dee5..34fa6e3bf93ac8487a8bf3598a6806ecbbaf6e41 100644
--- a/src/util/pureConstant.service.ts
+++ b/src/util/pureConstant.service.ts
@@ -1,17 +1,15 @@
 import { Inject, Injectable, OnDestroy } from "@angular/core";
 import { Store, select } from "@ngrx/store";
-import { Observable, Subscription, of, forkJoin, combineLatest, from } from "rxjs";
-import { viewerConfigSelectorUseMobileUi } from "src/services/state/viewerConfig.store.helper";
-import { shareReplay, tap, scan, catchError, filter, switchMap, map, distinctUntilChanged, mapTo } from "rxjs/operators";
+import { Observable, Subscription, of, forkJoin, from } from "rxjs";
+import { shareReplay, switchMap, map } from "rxjs/operators";
 import { HttpClient } from "@angular/common/http";
 import { LoggingService } from "src/logging";
 import { BS_ENDPOINT, BACKENDURL } from "src/util/constants";
-import { TAtlas, TId, TParc, TRegionDetail, TRegionSummary, TSpaceFull, TSpaceSummary, TVolumeSrc } from "./siibraApiConstants/types";
+import { TId, TParc, TRegionDetail, TRegionSummary, TSpaceFull, TSpaceSummary } from "./siibraApiConstants/types";
 import { MultiDimMap, recursiveMutate, mutateDeepMerge } from "./fn";
 import { patchRegions } from './patchPureConstants'
-import { environment } from "src/environments/environment";
 import { MatSnackBar } from "@angular/material/snack-bar";
-import { atlasSelection } from "src/state";
+import { atlasSelection, userInterface } from "src/state";
 
 const validVolumeType = new Set([
   'neuroglancer/precomputed',
@@ -307,7 +305,7 @@ Raise/track issues at github repo: <a target = "_blank" href = "${this.repoUrl}"
     )
 
     this.useTouchUI$ = this.store.pipe(
-      select(viewerConfigSelectorUseMobileUi),
+      select(userInterface.selectors.useMobileUi),
       shareReplay(1)
     )
 
diff --git a/src/viewerModule/nehuba/util.ts b/src/viewerModule/nehuba/util.ts
index 9c4baa3b039c4736e543ddb6e1c196e8ac2babc5..fcec9aa50df3f4a6147b1fb124a343bd52226b92 100644
--- a/src/viewerModule/nehuba/util.ts
+++ b/src/viewerModule/nehuba/util.ts
@@ -2,7 +2,7 @@ import { InjectionToken } from '@angular/core'
 import { Observable, pipe } from 'rxjs'
 import { filter, scan, take } from 'rxjs/operators'
 import { PANELS } from 'src/services/state/ngViewerState.store.helper'
-import { getViewer } from 'src/util/fn'
+import { getExportNehuba, getViewer } from 'src/util/fn'
 import { NehubaViewerUnit } from './nehubaViewer/nehubaViewer.component'
 import { NgConfigViewerState } from "./config.service"
 import { RecursivePartial } from './config.service/type'
@@ -213,7 +213,7 @@ export const userLmUnchanged = (oldlms, newlms) => {
 export const importNehubaFactory = appendSrc => {
   let pr: Promise<any>
   return () => {
-    if ((window as any).export_nehuba) return Promise.resolve()
+    if (getExportNehuba()) return Promise.resolve()
 
     if (pr) return pr
     pr = appendSrc('main.bundle.js')
diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts
index 00ac6d7e2551046ff3c50838bec54c7da57b5a42..b19dec0a8cbc277c528390d83df762cebfc3285b 100644
--- a/src/viewerModule/viewerCmp/viewerCmp.component.ts
+++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts
@@ -166,7 +166,7 @@ export class ViewerCmp implements OnDestroy {
   public viewerCtx$ = this.ctxMenuSvc.context$
 
   public selectedFeature$ = this.store$.pipe(
-    select(userInterface.selectors.selectedFeature)
+    select(userInteraction.selectors.selectedFeature)
   )
 
   /**
@@ -375,7 +375,7 @@ export class ViewerCmp implements OnDestroy {
     )
 
     this.store$.dispatch(
-      userInterface.actions.showFeature({
+      userInteraction.actions.showFeature({
         feature
       })
     )
@@ -383,7 +383,7 @@ export class ViewerCmp implements OnDestroy {
 
   clearSelectedFeature(){
     this.store$.dispatch(
-      userInterface.actions.clearShownFeature()
+      userInteraction.actions.clearShownFeature()
     )
   }
 }
diff --git a/worker/worker-typedarray.js b/worker/worker-typedarray.js
index cec7f4260b6eb8a6be7467dc9732f85cc57a8fed..c6d5933ced25281ac91bf51a90e6f0b5a380904c 100644
--- a/worker/worker-typedarray.js
+++ b/worker/worker-typedarray.js
@@ -1,13 +1,125 @@
 (function(exports){
-  const packNpArray = (inputArray, dtype, width, height) => {
-    if (dtype === "float32") {
-      
+  /**
+   * CM_CONST adopted from https://github.com/bpostlethwaite/colormap
+   * at commit hash 3406182
+   * 
+   * with MIT license
+   * 
+   * Copyright (c) <2012> ICRL
+   * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+   * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+   * 
+   * https://github.com/bpostlethwaite/colormap/blob/3406182/colorScale.js
+   */
+  const CM_CONST = {
+    "jet":[{"index":0,"rgb":[0,0,131]},{"index":0.125,"rgb":[0,60,170]},{"index":0.375,"rgb":[5,255,255]},{"index":0.625,"rgb":[255,255,0]},{"index":0.875,"rgb":[250,0,0]},{"index":1,"rgb":[128,0,0]}],
+    "viridis": [{"index":0,"rgb":[68,1,84]},{"index":0.13,"rgb":[71,44,122]},{"index":0.25,"rgb":[59,81,139]},{"index":0.38,"rgb":[44,113,142]},{"index":0.5,"rgb":[33,144,141]},{"index":0.63,"rgb":[39,173,129]},{"index":0.75,"rgb":[92,200,99]},{"index":0.88,"rgb":[170,220,50]},{"index":1,"rgb":[253,231,37]}],
+    "plasma": [{"index":0,"rgb":[13,8,135]},{"index":0.13,"rgb":[75,3,161]},{"index":0.25,"rgb":[125,3,168]},{"index":0.38,"rgb":[168,34,150]},{"index":0.5,"rgb":[203,70,121]},{"index":0.63,"rgb":[229,107,93]},{"index":0.75,"rgb":[248,148,65]},{"index":0.88,"rgb":[253,195,40]},{"index":1,"rgb":[240,249,33]}],
+    "magma": [{"index":0,"rgb":[0,0,4]},{"index":0.13,"rgb":[28,16,68]},{"index":0.25,"rgb":[79,18,123]},{"index":0.38,"rgb":[129,37,129]},{"index":0.5,"rgb":[181,54,122]},{"index":0.63,"rgb":[229,80,100]},{"index":0.75,"rgb":[251,135,97]},{"index":0.88,"rgb":[254,194,135]},{"index":1,"rgb":[252,253,191]}],
+    "inferno": [{"index":0,"rgb":[0,0,4]},{"index":0.13,"rgb":[31,12,72]},{"index":0.25,"rgb":[85,15,109]},{"index":0.38,"rgb":[136,34,106]},{"index":0.5,"rgb":[186,54,85]},{"index":0.63,"rgb":[227,89,51]},{"index":0.75,"rgb":[249,140,10]},{"index":0.88,"rgb":[249,201,50]},{"index":1,"rgb":[252,255,164]}],
+    "greyscale": [{"index":0,"rgb":[0,0,0]},{"index":1,"rgb":[255,255,255]}],
+  }
+
+  function lerp(min, max, val) {
+    const absDiff = max - min
+    const lowerVal = (val - min) / absDiff
+    return [lowerVal, 1 - lowerVal]
+  }
+
+  function getLerpToCm(colormap) {
+    if (!CM_CONST[colormap]) {
+      throw new Error(`colormap ${colormap} does not exist in CM_CONST`)
+    }
+    const cm = CM_CONST[colormap]
+
+    function check(nv, current) {
+      if (!cm[current + 1] || !cm[current]) {
+        debugger
+      }
+      const lower = cm[current].index <= nv
+      const higher = nv <= cm[current + 1].index
+      let returnVal = null
+      if (lower && higher) {
+        returnVal = {
+          index: nv,
+          rgb: [0, 0, 0]
+        }
+        const [ lowerPc, higherPc ] = lerp(cm[current].index, cm[current + 1].index, nv)
+        returnVal.rgb = returnVal.rgb.map((_, idx) => cm[current]['rgb'][idx] * lowerPc + cm[current + 1]['rgb'][idx] * higherPc)
+      }
+      return [ returnVal, { lower, higher } ]
+    }
+    return function lerpToCm(nv) {
+      let minLow = 0, maxHigh = cm.length, current = Math.floor((maxHigh + minLow) / 2), found
+      let iter = 0
+      while (true) {
+        iter ++
+        if (iter > 100) {
+          debugger
+          throw new Error(`iter > 1000, something we`)
+        }
+        const [val, { lower, higher }] = check(nv, current)
+        if (val) {
+          found = val
+          break
+        }
+        if (lower) {
+          minLow = current
+        }
+        if (higher) {
+          maxHigh = current
+        }
+        current = Math.floor((maxHigh + minLow) / 2)
+      }
+      return found
     }
-    if (dtype === "int32") {
+  }
+
 
+  function unpackToArray(inputArray, width, height, channel, dtype) {
+    /**
+     * NB: assuming C (row major) order!
+     */
+
+    if (channel !== 1) {
+      throw new Error(`cm2rgba channel must be 1, but is ${channel} instead`)
+    }
+    const depth = (() => {
+      if (dtype === "int32") return 4
+      if (dtype === "float32") return 4
+      if (dtype === "uint8") return 1
+      throw new Error(`unrecognised dtype: ${dtype}`)
+    })()
+    if (width * height * depth !== inputArray.length) {
+      throw new Error(`expecting width * height * depth === input.length, but ${width} * ${height} * ${depth} === ${width * height * depth} !== ${inputArray.length}`)
     }
-    if (dtype === "uint8") {
 
+    const UseConstructor = (() => {
+
+      if (dtype === "int32") return Int32Array
+      if (dtype === "float32") return Float32Array
+      if (dtype === "uint8") return Uint8Array
+      throw new Error(`unrecognised dtype: ${dtype}`)
+    })()
+    
+    const newArray = new UseConstructor(inputArray.buffer)
+    const outputArray = []
+    let min = null
+    let max = null
+    for (let y = 0; y < height; y ++) {
+      if (!outputArray[y]) outputArray[y] = []
+      for (let x = 0; x < width; x++) {
+        const num = newArray[y * width + x]
+        min = min === null ? num : Math.min(num, min)
+        max = max === null ? num : Math.max(num, max)
+        outputArray[y][x] = newArray[y * width + x]
+      }
+    }
+    return {
+      outputArray,
+      min,
+      max
     }
   }
 
@@ -40,6 +152,53 @@
         }
       }
       return buffer
+    },
+    cm2rgba(inputArray, width, height, channel, dtype, processParams) {
+      const { 
+        outputArray,
+        min,
+        max,
+       } = unpackToArray(inputArray, width, height, channel, dtype)
+      const {
+        colormap="jet"
+      } = processParams || {}
+
+      const _ = new ArrayBuffer(width * height * 4)
+      const buffer = new Uint8ClampedArray(_)
+
+      const absDiff = max - min
+      const lerpToCm = getLerpToCm(colormap)
+      
+      for (let row = 0; row < height; row ++) {
+        for (let col = 0; col < width; col ++){
+          const normalizedValue = (outputArray[row][col] - min) / absDiff
+          const { rgb } = lerpToCm(normalizedValue)
+
+          const toIdx = (row * width + col) * 4
+          buffer[toIdx] = rgb[0]
+          buffer[toIdx + 1] = rgb[1]
+          buffer[toIdx + 2] = rgb[2]
+          buffer[toIdx + 3] = 255
+        }
+      }
+      return {
+        buffer,
+        min,
+        max,
+      }
+    },
+    rawArray(inputArray, width, height, channel, dtype) {
+      const { 
+        outputArray,
+        min,
+        max,
+       } = unpackToArray(inputArray, width, height, channel, dtype)
+       console.log({ outputArray })
+       return {
+          outputArray,
+          min,
+          max,
+       }
     }
   }
 })(
diff --git a/worker/worker.js b/worker/worker.js
index 036dec5c8e96c293d4ce3eefc2b0ac2d8991d21a..ab8cc83715853cc94fb09e4a2bfce4b5f2597b6a 100644
--- a/worker/worker.js
+++ b/worker/worker.js
@@ -26,6 +26,8 @@ const VALID_METHOD = {
   PROCESS_NIFTI: 'PROCESS_NIFTI',
   PROCESS_TYPED_ARRAY: `PROCESS_TYPED_ARRAY`,
   PROCESS_TYPED_ARRAY_F2RGBA: `PROCESS_TYPED_ARRAY_F2RGBA`,
+  PROCESS_TYPED_ARRAY_CM2RGBA: "PROCESS_TYPED_ARRAY_CM2RGBA",
+  PROCESS_TYPED_ARRAY_RAW: "PROCESS_TYPED_ARRAY_RAW",
 }
 
 const VALID_METHODS = [
@@ -33,6 +35,8 @@ const VALID_METHODS = [
   VALID_METHOD.PROCESS_NIFTI,
   VALID_METHOD.PROCESS_TYPED_ARRAY,
   VALID_METHOD.PROCESS_TYPED_ARRAY_F2RGBA,
+  VALID_METHOD.PROCESS_TYPED_ARRAY_CM2RGBA,
+  VALID_METHOD.PROCESS_TYPED_ARRAY_RAW,
 ]
 
 const validOutType = [
@@ -310,11 +314,57 @@ onmessage = (message) => {
         })
       }
     }
+    if (message.data.method === VALID_METHOD.PROCESS_TYPED_ARRAY_CM2RGBA) {
+      try {
+        const { inputArray, width, height, channel, dtype, processParams } = message.data.param
+        const { buffer, min, max } = self.typedArray.cm2rgba(inputArray, width, height, channel, dtype, processParams)
+
+        postMessage({
+          id,
+          result: {
+            buffer,
+            min,
+            max,
+          }
+        }, [ buffer.buffer ])
+      } catch (e) {
+        postMessage({
+          id,
+          error: {
+            code: 401,
+            message: `process typed array error: ${e.toString()}`
+          }
+        })
+      }
+    }
+    if (message.data.method === VALID_METHOD.PROCESS_TYPED_ARRAY_RAW) {
+      try {
+        const { inputArray, width, height, channel, dtype, processParams } = message.data.param
+        const { outputArray, min, max } = self.typedArray.rawArray(inputArray, width, height, channel, dtype, processParams)
+
+        postMessage({
+          id,
+          result: {
+            outputArray,
+            min,
+            max,
+          }
+        })
+      } catch (e) {
+        postMessage({
+          id,
+          error: {
+            code: 401,
+            message: `process typed array error: ${e.toString()}`
+          }
+        })
+      }
+    }
     postMessage({
       id,
       error: {
         code: 404,
-        message: `worker method not found`
+        message: `worker method not found. ${message.data.method}`
       }
     })
     return