diff --git a/common/constants.js b/common/constants.js
index d3f6f24546090062459459f82f8652db30691244..fb5a018be88f017fb2b1767e07a7305288fb7b03 100644
--- a/common/constants.js
+++ b/common/constants.js
@@ -108,7 +108,7 @@ These outlines are based on the authoritative Terms and Conditions are found <ht
 If you do not accept the Terms & Conditions you are not permitted to access or use the KG to search for, to submit, to post, or to download any materials found there-in.
 `,
 
-    NEHUBA_DRAG_DROP_TEXT: `Drag and drop any .nii.gz, .nii or .swc files.`,
+    NEHUBA_DRAG_DROP_TEXT: `Drag and drop any .nii.gz, .nii, .json or .swc files.`,
 
     LOADING_TXT: `Loading ...`,
 
diff --git a/src/atlasComponents/annotations/annotation.service.ts b/src/atlasComponents/annotations/annotation.service.ts
index 776fb7848761df1bc48e99403ded8b3607cdba0a..be1b3fe8ed9f57747c9ec1c27788c37173524edf 100644
--- a/src/atlasComponents/annotations/annotation.service.ts
+++ b/src/atlasComponents/annotations/annotation.service.ts
@@ -61,6 +61,13 @@ interface NgAnnotationLayer {
   visible: boolean
 }
 
+export const ID_AFFINE = [
+  [1, 0, 0, 0],
+  [0, 1, 0, 0],
+  [0, 0, 1, 0],
+  [0, 0, 0, 1],
+]
+
 export class AnnotationLayer {
   static Map = new Map<string, AnnotationLayer>()
   static Get(name: string, color: string){
@@ -79,7 +86,8 @@ export class AnnotationLayer {
   private idset = new Set<string>()
   constructor(
     private name: string = getUuid(),
-    private color="#ffffff"
+    private color="#ffffff",
+    affine=ID_AFFINE,
   ){
     const layerSpec = this.viewer.layerSpecification.getLayer(
       this.name,
@@ -88,12 +96,7 @@ export class AnnotationLayer {
         "annotationColor": this.color,
         "annotations": [],
         name: this.name,
-        transform: [
-          [1, 0, 0, 0],
-          [0, 1, 0, 0],
-          [0, 0, 1, 0],
-          [0, 0, 0, 1],
-        ]
+        transform: affine,
       }
     )
     this.nglayer = this.viewer.layerManager.addManagedLayer(layerSpec)
@@ -134,19 +137,35 @@ export class AnnotationLayer {
     }
   }
 
-  async addAnnotation(spec: AnnotationSpec){
+  /**
+   * Unsafe method. Caller should ensure this.nglayer.isReady()
+   * 
+   * @param spec 
+   */
+  #addSingleAnn(spec: AnnotationSpec) {
+    const localAnnotations = this.nglayer.layer.localAnnotations
+    this.idset.add(spec.id)
+    const annSpec = this.parseNgSpecType(spec)
+    localAnnotations.add(
+      annSpec
+    )  
+  }
+
+  async addAnnotation(spec: AnnotationSpec|AnnotationSpec[]){
     if (!this.nglayer) {
       throw new Error(`layer has already been disposed`)
     }
 
     PeriodicSvc.AddToQueue(() => {
       if (this.nglayer.isReady()) {
-        const localAnnotations = this.nglayer.layer.localAnnotations
-        this.idset.add(spec.id)
-        const annSpec = this.parseNgSpecType(spec)
-        localAnnotations.add(
-          annSpec
-        )  
+        if (Array.isArray(spec)) {
+          for (const item of spec) {
+            this.#addSingleAnn(item)
+          }
+        } else {
+          this.#addSingleAnn(spec)
+        }
+        
         return true
       }
       return false
@@ -162,8 +181,13 @@ export class AnnotationLayer {
       localAnnotations.references.delete(spec.id)
     }
   }
-  async updateAnnotation(spec: AnnotationSpec) {
-    await waitFor(() => !!this.nglayer?.layer?.localAnnotations)
+
+  /**
+   * Unsafe method. Caller should ensure this.nglayer.layer is defined
+   * 
+   * @param spec 
+   */
+  #updateSingleAnn(spec: AnnotationSpec) {
     const { localAnnotations } = this.nglayer.layer
     const ref = localAnnotations.references.get(spec.id)
     const _spec = this.parseNgSpecType(spec)
@@ -178,6 +202,17 @@ export class AnnotationLayer {
     }
   }
 
+  async updateAnnotation(spec: AnnotationSpec|AnnotationSpec[]) {
+    await waitFor(() => !!this.nglayer?.layer?.localAnnotations)
+    if (Array.isArray(spec)) {
+      for (const item of spec){
+        this.#updateSingleAnn(item)
+      }
+      return
+    }
+    this.#updateSingleAnn(spec)
+  }
+
   private get viewer() {
     if ((window as any).viewer) return (window as any).viewer
     throw new Error(`window.viewer not defined`)
diff --git a/src/atlasComponents/sapi/core/space/interspaceLinearXform.ts b/src/atlasComponents/sapi/core/space/interspaceLinearXform.ts
index 44c93c74186567e3967d2224cb4937ee391de215..74bf8078cb9ae9765684ec78e5b038d02d4b3c15 100644
--- a/src/atlasComponents/sapi/core/space/interspaceLinearXform.ts
+++ b/src/atlasComponents/sapi/core/space/interspaceLinearXform.ts
@@ -1,29 +1,33 @@
-export const VALID_LINEAR_XFORM_SRC = {
-  CCF: "Allen Common Coordination Framework"
-}
-
-export const VALID_LINEAR_XFORM_DST = {
-  NEHUBA: "nehuba"
-}
+const VALID_XFORM_SRC = ["CCF_V2_5", "QUICKNII"] as const
+const VALID_XFORM_DST = ["NEHUBA"] as const
 
-export type TVALID_LINEAR_XFORM_SRC = keyof typeof VALID_LINEAR_XFORM_SRC
-export type TVALID_LINEAR_XFORM_DST = keyof typeof VALID_LINEAR_XFORM_DST
+export type TVALID_LINEAR_XFORM_SRC = typeof VALID_XFORM_SRC[number]
+export type TVALID_LINEAR_XFORM_DST = typeof VALID_XFORM_DST[number]
 
 type TLinearXform = number[][]
 
 const _linearXformDict: Record<
-  keyof typeof VALID_LINEAR_XFORM_SRC,
+  TVALID_LINEAR_XFORM_SRC,
   Record<
-    keyof typeof VALID_LINEAR_XFORM_DST,
+  TVALID_LINEAR_XFORM_DST,
     TLinearXform
   >> = {
-    CCF: {
+    CCF_V2_5: {
       NEHUBA: [
         [-1e3, 0, 0, 11400000 - 5737500], //
         [0, 0, -1e3, 13200000 - 6637500], //
         [0, -1e3, 0, 8000000 - 4037500], //
         [0, 0, 0, 1],
       ]
+    },
+    // see https://www.nitrc.org/plugins/mwiki/index.php?title=quicknii:Coordinate_systems
+    QUICKNII: {
+      NEHUBA: [
+        [2.5e4, 0, 0, -5737500], //
+        [0, 2.5e4, 0,  -6637500], //
+        [0, 0, 2.5e4, -4037500], //
+        [0, 0, 0, 1],
+      ]
     }
   }
 
@@ -48,13 +52,13 @@ export const linearXformDict = getProxyXform(_linearXformDict, (value: Record<st
     return defaultXform
   })
 }) as Record<
-  keyof typeof VALID_LINEAR_XFORM_SRC,
+TVALID_LINEAR_XFORM_SRC,
   Record<
-    keyof typeof VALID_LINEAR_XFORM_DST,
+    TVALID_LINEAR_XFORM_DST,
     TLinearXform
   >>
 
 
-export const linearTransform = async (srcTmplName: keyof typeof VALID_LINEAR_XFORM_SRC, targetTmplName: keyof typeof VALID_LINEAR_XFORM_DST) => {
+export const linearTransform = async (srcTmplName: TVALID_LINEAR_XFORM_SRC, targetTmplName: TVALID_LINEAR_XFORM_DST) => {
   return linearXformDict[srcTmplName][targetTmplName]
 }
diff --git a/src/atlasComponents/userAnnotations/tools/line.ts b/src/atlasComponents/userAnnotations/tools/line.ts
index 9223b1d989aacccab83e470f9145aca1b81674a9..37299eb4e820d2696e7ed6e61b510c1b3cd7af1f 100644
--- a/src/atlasComponents/userAnnotations/tools/line.ts
+++ b/src/atlasComponents/userAnnotations/tools/line.ts
@@ -23,7 +23,6 @@ export type TLineJsonSpec = {
 } & TBaseAnnotationGeomtrySpec
 
 export class Line extends IAnnotationGeometry{
-  public id: string
   public annotationType = 'Line'
 
   public points: Point[] = []
diff --git a/src/atlasComponents/userAnnotations/tools/point.ts b/src/atlasComponents/userAnnotations/tools/point.ts
index 5086a30459665423439da1b8ee6cbb2fe51d35df..dea8847d747aec28f0f8d852dcdf6c030de8c6c1 100644
--- a/src/atlasComponents/userAnnotations/tools/point.ts
+++ b/src/atlasComponents/userAnnotations/tools/point.ts
@@ -11,7 +11,6 @@ export type TPointJsonSpec = {
 } & TBaseAnnotationGeomtrySpec
 
 export class Point extends IAnnotationGeometry {
-  id: string
   x: number
   y: number
   z: number
diff --git a/src/atlasComponents/userAnnotations/tools/poly.ts b/src/atlasComponents/userAnnotations/tools/poly.ts
index 4bc86182aa6849161b384923cabd24d76534f502..01dada57995f76dd898547980faa8c9dd9cefb10 100644
--- a/src/atlasComponents/userAnnotations/tools/poly.ts
+++ b/src/atlasComponents/userAnnotations/tools/poly.ts
@@ -12,7 +12,6 @@ export type TPolyJsonSpec = {
 } & TBaseAnnotationGeomtrySpec
 
 export class Polygon extends IAnnotationGeometry{
-  public id: string
   public annotationType = 'Polygon'
 
   public points: Point[] = []
diff --git a/src/viewerModule/nehuba/userLayers/service.ts b/src/viewerModule/nehuba/userLayers/service.ts
index 5937a106ed53e1fa3682f2900446bba171ffcdb5..0a960972f7f7b2770826ae1bdd970fa250e6ef3d 100644
--- a/src/viewerModule/nehuba/userLayers/service.ts
+++ b/src/viewerModule/nehuba/userLayers/service.ts
@@ -1,5 +1,5 @@
 import { Injectable, OnDestroy } from "@angular/core"
-import { MatDialog } from "@angular/material/dialog"
+import { MatDialog, MatDialogRef } from "@angular/material/dialog"
 import { select, Store } from "@ngrx/store"
 import { forkJoin, from, Subscription } from "rxjs"
 import { distinctUntilChanged, filter } from "rxjs/operators"
@@ -17,6 +17,8 @@ import { getExportNehuba, getUuid } from "src/util/fn"
 import { UserLayerInfoCmp } from "./userlayerInfo/userlayerInfo.component"
 import { translateV3Entities } from "src/atlasComponents/sapi/translateV3"
 import { MetaV1Schema } from "src/atlasComponents/sapi/typeV3"
+import { AnnotationLayer } from "src/atlasComponents/annotations"
+import { rgbToHex } from 'common/util'
 
 type OmitKeys = "clType" | "id" | "source"
 type LayerOption = Omit<atlasAppearance.const.NgLayerCustomLayer, OmitKeys>
@@ -32,9 +34,9 @@ const SUPPORTED_PREFIX = ["nifti://", "precomputed://", "swc://", "deepzoom://"]
 type ValidProtocol = typeof SUPPORTED_PREFIX[number]
 type ValidInputTypes = File|string
 
-type ProcessorOutput = {option: LayerOption, url: string, protocol: ValidProtocol, meta: Meta, cleanup: () => void}
+type ProcessorOutput = {option?: LayerOption, url?: string, protocol?: ValidProtocol, meta: Meta, cleanup: () => void}
 type ProcessResource = {
-  matcher: (input: ValidInputTypes) => boolean
+  matcher: (input: ValidInputTypes) => Promise<boolean>
   processor: (input: ValidInputTypes) => Promise<ProcessorOutput>
 }
 
@@ -52,6 +54,7 @@ function RegisterSource(matcher: ProcessResource['matcher']) {
 @Injectable()
 export class UserLayerService implements OnDestroy {
   #idToCleanup = new Map<string, () => void>()
+  #dialogRef: MatDialogRef<unknown>
 
   static VerifyUrl(source: string) {
     for (const prefix of SUPPORTED_PREFIX) {
@@ -63,7 +66,7 @@ export class UserLayerService implements OnDestroy {
   }
 
   @RegisterSource(
-    input => input instanceof File && input.name.endsWith(".swc")
+    async input => input instanceof File && input.name.endsWith(".swc")
   )
   async processSwc(file: File): ReturnType<ProcessResource['processor']> {
     let message = `The swc rendering is experimental. Please contact us on any feedbacks. `
@@ -71,7 +74,7 @@ export class UserLayerService implements OnDestroy {
     let src: TVALID_LINEAR_XFORM_SRC
     const dst: TVALID_LINEAR_XFORM_DST = "NEHUBA"
     if (/ccf/i.test(swcText)) {
-      src = "CCF"
+      src = "CCF_V2_5"
       message += `CCF detected, applying known transformation.`
     }
     if (!src) {
@@ -123,7 +126,7 @@ export class UserLayerService implements OnDestroy {
   }
 
   @RegisterSource(
-    input => input instanceof File && input.name.endsWith(".nii")
+    async input => input instanceof File && input.name.endsWith(".nii")
   )
   async processNifti(file: File){
     const buf = await file.arrayBuffer()
@@ -131,7 +134,7 @@ export class UserLayerService implements OnDestroy {
   }
 
   @RegisterSource(
-    input => input instanceof File && input.name.endsWith(".nii.gz")
+    async input => input instanceof File && input.name.endsWith(".nii.gz")
   )
   async processNiiGz(file: File) {
     const buf = await file.arrayBuffer()
@@ -146,7 +149,7 @@ export class UserLayerService implements OnDestroy {
   }
 
   @RegisterSource(
-    input => typeof input === "string" && input.startsWith(OVERLAY_LAYER_PROTOCOL)
+    async input => typeof input === "string" && input.startsWith(OVERLAY_LAYER_PROTOCOL)
   )
   async processOverlayPath(source: string) {
     const strippedSrc = source.replace(OVERLAY_LAYER_PROTOCOL, "")
@@ -161,7 +164,7 @@ export class UserLayerService implements OnDestroy {
   }
 
   @RegisterSource(
-    input => typeof input === "string" && input.startsWith("precomputed://")
+    async input => typeof input === "string" && input.startsWith("precomputed://")
   )
   async processPrecomputed(source: string): Promise<ProcessorOutput>{
     const url = source.replace("precomputed://", "")
@@ -190,7 +193,7 @@ export class UserLayerService implements OnDestroy {
   }
 
   @RegisterSource(
-    input => typeof input === "string" && input.startsWith("deepzoom://")
+    async input => typeof input === "string" && input.startsWith("deepzoom://")
   )
   async processDzi(source: string): Promise<ProcessorOutput> {
     const url = source.replace("deepzoom://", "")
@@ -214,13 +217,82 @@ export class UserLayerService implements OnDestroy {
     }
   }
 
+  @RegisterSource(async input => {
+    if (input instanceof File && input.name.endsWith(".json")) {
+      const JSON_KEYS = [
+        // "b",
+        // "count",
+        // "g",
+        // "idx",
+        // "name",
+        // "r",
+        "triplets"
+      ]
+      const text = await input.text()
+      const arr = JSON.parse(text)
+
+      // must be array
+      if (!Array.isArray(arr)) {
+        return false
+      }
+      // can only deal with length 1 for now
+      if (arr.length !== 1) {
+        return false
+      }
+      const item = arr[0]
+      for (const key of JSON_KEYS) {
+        if (!item[key]) {
+          console.log(`Parsing PCJson failed. ${key} does not exist`)
+          return false
+        }
+      }
+      return true
+    }
+    return false
+  })
+  async processPCJson(file: File): Promise<ProcessorOutput>{
+    const arr = JSON.parse(await file.text())
+    const item = arr[0]
+    const { r, g, b } = item
+    
+    const rgbString = [r, g, b].every(v => Number.isInteger(v))
+    ? rgbToHex([r, g, b])
+    : "#ff0000"
+
+    const id = getUuid()
+    const src = "QUICKNII"
+    const dst = "NEHUBA"
+    const xform = await linearTransform(src, dst)
+    const layer = new AnnotationLayer(id, rgbString, xform)
+
+    const triplets: number[][] = [item.triplets.slice(0, 3)]
+    for (const num of item.triplets as number[]) {
+      if (triplets.at(-1).length === 3) {
+        triplets.push([num])
+        continue
+      }
+      triplets.at(-1).push(num)
+    }
+    
+    layer.addAnnotation(triplets.map((triplet, idx) => ({
+      id: `${id}-${idx}`,
+      type: 'point',
+      point: triplet.map(v => v) as [number, number, number]
+    })))
+    return {
+      cleanup: () => layer.dispose(),
+      meta: {
+        filename: file.name,
+      }
+    }
+  }
+
   async #processInput(input: ValidInputTypes): Promise<ProcessorOutput> {
     for (const { matcher, processor } of SOURCE_PROCESSOR) {
-      if (matcher(input)) {
+      if (await matcher(input)) {
         return await processor.apply(this, [input])
       }
     }
-    debugger
     const inputStr = input instanceof File
       ? input.name
       : input
@@ -236,7 +308,7 @@ export class UserLayerService implements OnDestroy {
     this.#idToCleanup.set(id, cleanup)
     this.addUserLayer(
       id,
-      `${protocol}${url}`,
+      protocol && url &&`${protocol}${url}`,
       meta,
       option,
     )
@@ -245,24 +317,30 @@ export class UserLayerService implements OnDestroy {
 
   addUserLayer(
     id: string,
-    source: string,
+    source: string|null|undefined,
     meta: Meta,
     options: LayerOption = {}
   ) {
-    UserLayerService.VerifyUrl(source)
-    const layer = {
-      id,
-      clType: "customlayer/nglayer" as const,
-      source,
-      ...options,
+    if (source) {
+      UserLayerService.VerifyUrl(source)
+      const layer = {
+        id,
+        clType: "customlayer/nglayer" as const,
+        source,
+        ...options,
+      }
+      this.store$.dispatch(
+        atlasAppearance.actions.addCustomLayer({
+          customLayer: layer,
+        })
+      )
     }
-    this.store$.dispatch(
-      atlasAppearance.actions.addCustomLayer({
-        customLayer: layer,
-      })
-    )
 
-    this.dialog.open(UserLayerInfoCmp, {
+    if (this.#dialogRef) {
+      this.#dialogRef.close()
+      this.#dialogRef = null
+    }
+    this.#dialogRef = this.dialog.open(UserLayerInfoCmp, {
       data: {
         layerName: id,
         filename: meta.filename,
@@ -276,15 +354,16 @@ export class UserLayerService implements OnDestroy {
       autoFocus: false,
       panelClass: ["no-padding-dialog", "w-100"],
     })
-    .afterClosed()
-    .subscribe(() => {
-      this.store$.dispatch(atlasAppearance.actions.removeCustomLayer({ id }))
+  
+    this.#dialogRef.afterClosed().subscribe(() => {
       const cleanup = this.#idToCleanup.get(id)
-      if (!cleanup) {
-        console.warn(`idToCleanup ${id} could not be found! ${meta.filename}`)
-        return
+      cleanup && cleanup()
+      if (source) {
+        this.store$.dispatch(
+          atlasAppearance.actions.removeCustomLayer({ id })
+        )
       }
-      cleanup()
+      this.#idToCleanup.delete(id)
     })
   }
 
diff --git a/tsconfig.json b/tsconfig.json
index bfd3effbebe1bbe4804212fd1188169c72d3937e..77502016e3320cade026e538bf74cf842a976c72 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -3,8 +3,9 @@
     "experimentalDecorators": true,
     "emitDecoratorMetadata": true,
     "moduleResolution": "node",
+    "useDefineForClassFields": false,
     "module": "esnext",
-    "target": "es2020",
+    "target": "es2022",
     "sourceMap": false,
     "baseUrl": ".",
     "paths": {
@@ -25,4 +26,4 @@
     "annotateForClosureCompiler" : true,
     "strictTemplates": true
   }
-}
\ No newline at end of file
+}