diff --git a/src/atlasComponents/userAnnotations/annotationMode/annotationMode.component.ts b/src/atlasComponents/userAnnotations/annotationMode/annotationMode.component.ts
index 0803ac641a12e53a0df2991192c190ba3cedac74..742c1ae728eb7afd09aa28d0fb33ba6faf842de2 100644
--- a/src/atlasComponents/userAnnotations/annotationMode/annotationMode.component.ts
+++ b/src/atlasComponents/userAnnotations/annotationMode/annotationMode.component.ts
@@ -1,7 +1,7 @@
 import {Component, HostListener, Inject, OnDestroy, OnInit, Optional} from "@angular/core";
 import {select, Store} from "@ngrx/store";
 import {ARIA_LABELS, CONST} from "common/constants";
-import { Observable, Subscription} from "rxjs";
+import { merge, Observable, Subscription} from "rxjs";
 import {getUuid} from "src/util/fn";
 import {VIEWER_INJECTION_TOKEN} from "src/ui/layerbrowser/layerDetail/layerDetail.component";
 import {buffer, debounceTime, distinctUntilChanged, filter, map, switchMapTo, take, takeUntil, tap} from "rxjs/operators";
@@ -22,6 +22,7 @@ import {CLICK_INTERCEPTOR_INJECTOR, ClickInterceptor} from "src/util";
 })
 export class AnnotationMode implements OnInit, OnDestroy {
 
+    public moduleAnnotationTypes: {instance: { name: string, iconClass: string }, onClick: Function} [] = []
     public selectedType = 0
 
     public position1: string
@@ -61,6 +62,7 @@ export class AnnotationMode implements OnInit, OnDestroy {
         @Optional() @Inject(NEHUBA_INSTANCE_INJTKN) nehubaViewer$: Observable<NehubaViewerUnit>,
         @Optional() @Inject(CLICK_INTERCEPTOR_INJECTOR) clickInterceptor: ClickInterceptor
     ) {
+      this.moduleAnnotationTypes = this.ans.moduleAnnotationTypes
       if (clickInterceptor) {
         const { register, deregister } = clickInterceptor
         const onMouseClick = this.onMouseClick.bind(this)
@@ -127,8 +129,20 @@ export class AnnotationMode implements OnInit, OnDestroy {
         filter((e: any) => e.eventName === 'mousemove')
       )
 
-      // Trigger mouse click on viewer (avoid dragging)
       this.subscriptions.push(
+        /**
+         * trigger annotation mouse events for modular tools
+         */
+        merge<{
+          eventName: 'mousedown' | 'mouseup' | 'mousemove'
+          event: MouseEvent
+        }>(...[mouseDown$, mouseUp$, mouseMove$]).subscribe(ev => {
+          this.ans.tmpAnnotationMouseEvent.next({
+            event: ev.event,
+            eventype: ev.eventName
+          })
+        }),
+        // Trigger mouse click on viewer (avoid dragging)
         mouseDown$.pipe(
           switchMapTo(
             mouseUp$.pipe(
diff --git a/src/atlasComponents/userAnnotations/annotationMode/annotationMode.template.html b/src/atlasComponents/userAnnotations/annotationMode/annotationMode.template.html
index a67bdb554ce7f162b9fd072739ebe800f906f3f9..9a5738463d1644e9451c755212d3b1ba4eced89d 100644
--- a/src/atlasComponents/userAnnotations/annotationMode/annotationMode.template.html
+++ b/src/atlasComponents/userAnnotations/annotationMode/annotationMode.template.html
@@ -1,17 +1,17 @@
-<div class="pe-all d-flex flex-column justify-content-content annotation-toolbar">
+<div class="pe-all d-flex flex-column justify-content-content z-index-10 w-3em">
 
-    <mat-card class="d-flex flex-column flex-grow-0 panel-default annotation-toolbar-content p-0">
+    <mat-card class="d-flex flex-column flex-grow-0 panel-default p-0">
 
         <div *ngFor="let type of ans.annotationTypes; let i = index; let last = last">
-        <button
-                mat-icon-button
-                type="button"
-                (click)="selectAnnotationType(i)"
-                class="mb-2 mt-2"
-                [matTooltip]="type.name"
-                [color]="selectedType === i? 'primary' : 'secondary'">
-            <i [ngClass]="type.class"></i>
-        </button>
+            <button
+                    mat-icon-button
+                    type="button"
+                    (click)="selectAnnotationType(i)"
+                    class="mb-2 mt-2"
+                    [matTooltip]="type.name"
+                    [color]="selectedType === i? 'primary' : 'secondary'">
+                <i [ngClass]="type.class"></i>
+            </button>
             <mat-divider *ngIf="last || type.action !== ans.annotationTypes[i+1].action"></mat-divider>
         </div>
         <button
@@ -23,5 +23,17 @@
                 color="warn">
             <i class="fas fa-times"></i>
         </button>
+        <div>
+            <mat-divider></mat-divider>
+            <button 
+                *ngFor="let moduleAnnotType of moduleAnnotationTypes"
+                mat-icon-button
+                (click)="moduleAnnotType.onClick()"
+                class="mt-2 mb-2"
+                [color]="(moduleAnnotType.instance.toolSelected$ | async) ? 'primary' : 'basic'"
+                type="button">
+                <i [class]="moduleAnnotType.instance.iconClass"></i>
+            </button>
+        </div>
     </mat-card>
 </div>
diff --git a/src/atlasComponents/userAnnotations/annotationService.service.ts b/src/atlasComponents/userAnnotations/annotationService.service.ts
index 5aea78b5bf62600844c559de96da709d24437264..9315e2b7b7e6cb682b04548782613a26c4c0846f 100644
--- a/src/atlasComponents/userAnnotations/annotationService.service.ts
+++ b/src/atlasComponents/userAnnotations/annotationService.service.ts
@@ -5,6 +5,7 @@ import {getUuid} from "src/util/fn";
 import {Store} from "@ngrx/store";
 import {VIEWER_INJECTION_TOKEN} from "src/ui/layerbrowser/layerDetail/layerDetail.component";
 import * as JSZip from 'jszip';
+import { Observable, Subject } from "rxjs";
 
 const USER_ANNOTATION_LAYER_SPEC = {
   "type": "annotation",
@@ -17,6 +18,9 @@ const USER_ANNOTATION_LAYER_SPEC = {
 @Injectable()
 export class AnnotationService {
 
+    public tmpAnnotationMouseEvent = new Subject<{ eventype: 'mousedown' | 'mouseup' | 'mousemove', event: MouseEvent }>()
+    public moduleAnnotationTypes: {instance: {name: string, iconClass: string, toolSelected$: Observable<boolean>}, onClick: Function}[] = []
+
     // Annotations to display on viewer
     public pureAnnotationsForViewer = []
 
diff --git a/src/atlasComponents/userAnnotations/module.ts b/src/atlasComponents/userAnnotations/module.ts
index c9e2aabecb990e2b8c5885bfb5f26d99b013a0ee..8a712e78e54ea6aa12d0e9f28ecabcfb9e7ff0d4 100644
--- a/src/atlasComponents/userAnnotations/module.ts
+++ b/src/atlasComponents/userAnnotations/module.ts
@@ -8,6 +8,7 @@ import {AnnotationMode} from "src/atlasComponents/userAnnotations/annotationMode
 import {AnnotationList} from "src/atlasComponents/userAnnotations/annotationList/annotationList.component";
 import {AnnotationService} from "src/atlasComponents/userAnnotations/annotationService.service";
 import {AnnotationMessage} from "src/atlasComponents/userAnnotations/annotationMessage/annotationMessage.component";
+import { UserAnnotationToolModule } from "./tools/module";
 
 @NgModule({
   imports: [
@@ -17,6 +18,7 @@ import {AnnotationMessage} from "src/atlasComponents/userAnnotations/annotationM
     FormsModule,
     ReactiveFormsModule,
     AngularMaterialModule,
+    UserAnnotationToolModule,
   ],
   declarations: [
     AnnotationMode,
diff --git a/src/atlasComponents/userAnnotations/tools/module.ts b/src/atlasComponents/userAnnotations/tools/module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a56d582d4dd917c103bc1e202cc5178d606e257a
--- /dev/null
+++ b/src/atlasComponents/userAnnotations/tools/module.ts
@@ -0,0 +1,20 @@
+import { NgModule } from "@angular/core";
+import { Subject } from "rxjs";
+import { ModularUserAnnotationToolService } from "./service";
+import { ANNOTATION_EVENT_INJ_TOKEN } from "./type";
+
+@NgModule({
+  providers: [
+    {
+      provide: ANNOTATION_EVENT_INJ_TOKEN,
+      useValue: new Subject()
+    },
+    ModularUserAnnotationToolService
+  ]
+})
+
+export class UserAnnotationToolModule {
+
+  // eslint-disable-next-line @typescript-eslint/no-empty-function
+  constructor(_svc: ModularUserAnnotationToolService){}
+}
diff --git a/src/atlasComponents/userAnnotations/tools/point.ts b/src/atlasComponents/userAnnotations/tools/point.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f5d7d41379d93ab37cb8cac30aa03b79a507b9ae
--- /dev/null
+++ b/src/atlasComponents/userAnnotations/tools/point.ts
@@ -0,0 +1,54 @@
+import { AbsToolClass, IAnnotationEvents, IAnnotationGeometry, IAnnotationTools, INgAnnotationTypes, TAnnotationEvent, TNgAnnotationEv, TToolType } from "./type";
+import { Observable, Subject } from "rxjs";
+
+export class Point extends IAnnotationGeometry {
+  id: string
+  x: number
+  y: number
+  z: number
+
+  static threshold = 1e-6
+  static eql(p1: Point, p2: Point) {
+    return Math.abs(p1.x - p2.x) < Point.threshold
+      && Math.abs(p1.y - p2.y) < Point.threshold
+      && Math.abs(p1.z - p2.z) < Point.threshold
+  }
+  constructor(arr: number[] = [], id?: string){
+    super({id})
+    if (arr.length !== 3) throw new Error(`constructor of points must be length 3`)
+    this.x = arr[0]
+    this.y = arr[1]
+    this.z = arr[2]
+  }
+  toJSON(){
+    const { id, x, y, z } = this
+    return { id, x, y, z }
+  }
+  toNgAnnotation(): INgAnnotationTypes['point'][]{
+    return [{
+      id: this.id,
+      point: [this.x, this.y, this.z],
+      type: 'point',
+    }]
+  }
+  static fromJSON(json: any) {
+    const { x, y, z, id } = json
+    return new Point([x, y, z], id)
+  }
+}
+
+export class ToolPoint extends AbsToolClass implements IAnnotationTools {
+  public name = 'Point'
+  public toolType: TToolType = 'drawing'
+  public iconClass = 'fas fa-circle'
+  private managedAnnotations: Point[] = []
+  public allNgAnnotations$ = new Subject<INgAnnotationTypes[keyof INgAnnotationTypes][]>()
+  constructor(
+    annotationEv$: Observable<TAnnotationEvent<keyof IAnnotationEvents>>
+  ){
+    super(annotationEv$)
+  }
+  ngAnnotationIsRelevant(annotation: TNgAnnotationEv){
+    return this.managedAnnotations.some(p => p.id === annotation.pickedAnnotationId)
+  }
+}
diff --git a/src/atlasComponents/userAnnotations/tools/poly.ts b/src/atlasComponents/userAnnotations/tools/poly.ts
new file mode 100644
index 0000000000000000000000000000000000000000..83ede9c02caee53817cac234127e41d9b0f6ca03
--- /dev/null
+++ b/src/atlasComponents/userAnnotations/tools/poly.ts
@@ -0,0 +1,202 @@
+import { IAnnotationTools, IAnnotationGeometry, TAnnotationEvent, IAnnotationEvents, AbsToolClass, INgAnnotationTypes, TNgAnnotationEv, TToolType } from "./type";
+import { Point } from './point'
+import { OnDestroy } from "@angular/core";
+import { merge, Observable, Subject, Subscription } from "rxjs";
+import { debounceTime, filter, map, switchMapTo, takeUntil } from "rxjs/operators";
+
+class Polygon extends IAnnotationGeometry{
+  public id: string
+
+  private points: Point[] = []
+  private idCounter = 0
+  private edges: [number, number][] = []
+
+  public hasPoint(p: Point) {
+    return this.points.indexOf(p) >= 0
+  }
+
+  public addPoint(p: Point | {x: number, y: number, z: number}, linkTo?: Point): Point {
+    if (linkTo && !this.hasPoint(linkTo)) {
+      throw new Error(`linkTo point does not exist for polygon!`)
+    }
+    
+    const pointToBeAdded = p instanceof Point
+      ? p
+      : new Point([p.x, p.y, p.z], `${this.id}_${this.idCounter}`)
+    this.idCounter += 1
+    
+    if (!this.hasPoint(pointToBeAdded)) this.points.push(pointToBeAdded)
+    if (linkTo) {
+      const newEdge = [
+        this.points.indexOf(linkTo),
+        this.points.indexOf(pointToBeAdded)
+      ] as [number, number]
+      this.edges.push(newEdge)
+    }
+    return pointToBeAdded
+  }
+
+  toJSON(){
+    const { id, points, edges } = this
+    return { id, points, edges }
+  }
+  toNgAnnotation(): INgAnnotationTypes['line'][]{
+    return this.edges.map((indices, edgeIdx) => {
+      const pt1 = this.points[indices[0]]
+      const pt2 = this.points[indices[1]]
+      return {
+        id: `${this.id}_${edgeIdx}_0`,
+        pointA: [pt1.x, pt1.y, pt1.z],
+        pointB: [pt2.x, pt2.y, pt2.z],
+        type: 'line',
+        description: ''
+      }
+    })
+  }
+
+  parseNgAnnotationObj(pickedAnnotationId: string, pickedOffset: number): { edge: [number, number], edgeIdx: number, point: Point, pointIdx: number } {
+    const [ id, edgeIdx, _shouldBeZero ] = pickedAnnotationId.split('_')
+    if (id !== this.id) return null
+
+    if (pickedOffset === 0) {
+      // focus === edge
+
+      const edgeIdxNumber = Number(edgeIdx)
+      return {
+        edgeIdx: edgeIdxNumber,
+        edge: this.edges[edgeIdxNumber],
+        pointIdx: null,
+        point: null
+      }
+    }
+    if (pickedOffset > 2) throw new Error(`polygon should not have picked offset > 2, but is ${pickedOffset}`)
+    const edgeIdxNumber = Number(edgeIdx)
+    const edge = this.edges[edgeIdxNumber]
+    const pointIdx = edge[ pickedOffset - 1 ]
+    return {
+      edgeIdx: edgeIdxNumber,
+      edge,
+      pointIdx,
+      point: this.points[pointIdx]
+    }
+  }
+
+  static fromJSON(json: any){
+    const { id, points, edges } = json
+    const p = new Polygon()
+    p.points = points.map(Point.fromJSON)
+    p.edges = edges
+    p.id = id
+    return p
+  }
+
+  constructor(){
+    super()
+  }
+}
+
+export class ToolPolygon extends AbsToolClass implements IAnnotationTools, OnDestroy {
+  public name = 'polygon'
+  public iconClass = 'fas fa-draw-polygon'
+  public toolType: TToolType = 'drawing'
+
+  private selectedPoly: Polygon
+  private lastAddedPoint: Point
+
+  private managedAnnotations: Polygon[] = []
+  private subs: Subscription[] = []
+  public allNgAnnotations$ = new Subject<INgAnnotationTypes[keyof INgAnnotationTypes][]>()
+
+  private hoveredAnnotation$: Observable<{
+    annotation: Polygon
+    detail: { point: Point }
+  }> = this.hoverAnnotation$.pipe(
+    map(ann => {
+      const { pickedAnnotationId, pickedOffset } = ann.detail
+      const annotation = this.managedAnnotations.find(poly => poly.parseNgAnnotationObj(pickedAnnotationId, pickedOffset))
+      if (!annotation) {
+        return null
+      }
+      return {
+        annotation,
+        detail: {
+          point: annotation.parseNgAnnotationObj(pickedAnnotationId, pickedOffset).point
+        }
+      }
+    })
+  )
+
+  constructor(
+    annotationEv$: Observable<TAnnotationEvent<keyof IAnnotationEvents>>
+  ){
+    super(annotationEv$)
+
+    const toolDeselect$ = this.toolSelected$.pipe(
+      filter(flag => !flag)
+    )
+    const toolSelThenClick$ = this.toolSelected$.pipe(
+      filter(flag => !!flag),
+      switchMapTo(this.mouseClick$.pipe(
+        takeUntil(toolDeselect$)
+      ))
+    )
+
+    this.subs.push(
+      /**
+       * on end tool select
+       */
+      toolDeselect$.subscribe(() => {
+        this.selectedPoly = null
+        this.lastAddedPoint = null
+      }),
+      /**
+       * on tool selected
+       * on mouse down
+       * until tool deselected
+       */
+      toolSelThenClick$.pipe(
+      ).subscribe(mouseev => {
+        if (!this.selectedPoly) {
+          this.selectedPoly = new Polygon()
+          this.managedAnnotations.push(this.selectedPoly)
+        }
+
+        const addedPoint = this.selectedPoly.addPoint(
+          mouseev.detail.ngMouseEvent,
+          this.lastAddedPoint
+        )
+        this.lastAddedPoint = addedPoint
+      }),
+
+      /**
+       * conditions by which ng annotations are refreshed
+       */
+      merge(
+        toolDeselect$,
+        toolSelThenClick$
+      ).pipe(
+        debounceTime(16),
+      ).subscribe(() => {
+        let out: INgAnnotationTypes['line'][] = []
+        for (const managedAnn of this.managedAnnotations) {
+          out = out.concat(...managedAnn.toNgAnnotation())
+        }
+        this.allNgAnnotations$.next(out)
+      }),
+      this.hoverAnnotation$.subscribe(val => {
+        if (val.detail) {
+          console.log(val.detail)
+        }
+      })
+    )
+  }
+
+  ngAnnotationIsRelevant(annotation: TNgAnnotationEv): boolean {
+    // perhaps use more advanced way to track if annotation is a part of polygon?
+    return this.managedAnnotations.some(poly => poly.id.indexOf(annotation.pickedAnnotationId) >= 0)
+  }
+
+  ngOnDestroy(){
+    if (this.subs.length > 0) this.subs.pop().unsubscribe()
+  }
+}
diff --git a/src/atlasComponents/userAnnotations/tools/service.ts b/src/atlasComponents/userAnnotations/tools/service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..791d2a2017c0e965edd91869569ffd6f2fff190f
--- /dev/null
+++ b/src/atlasComponents/userAnnotations/tools/service.ts
@@ -0,0 +1,242 @@
+import { Injectable } from "@angular/core";
+import { ARIA_LABELS } from 'common/constants'
+import { Inject, Optional } from "@angular/core";
+import { select, Store } from "@ngrx/store";
+import { Observable, of, Subject } from "rxjs";
+import { map, scan, switchMap } from "rxjs/operators";
+import { viewerStateSelectedTemplatePureSelector, viewerStateViewerModeSelector } from "src/services/state/viewerState/selectors";
+import { NehubaViewerUnit } from "src/viewerModule/nehuba";
+import { NEHUBA_INSTANCE_INJTKN } from "src/viewerModule/nehuba/util";
+import { AnnotationService } from "../annotationService.service";
+import { ToolPolygon } from "./poly";
+import { AbsToolClass, ANNOTATION_EVENT_INJ_TOKEN, IAnnotationEvents, INgAnnotationTypes, TAnnotationEvent } from "./type";
+import { switchMapWaitFor } from "src/util/fn";
+
+const IAV_VOXEL_SIZES_NM = {
+  'minds/core/referencespace/v1.0.0/265d32a0-3d84-40a5-926f-bf89f68212b9': [25000, 25000, 25000],
+  'minds/core/referencespace/v1.0.0/d5717c4a-0fa1-46e6-918c-b8003069ade8': [39062.5, 39062.5, 39062.5],
+  'minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588': [21166.666015625, 20000, 21166.666015625],
+  'minds/core/referencespace/v1.0.0/7f39f7be-445b-47c0-9791-e971c0b6d992': [1000000, 1000000, 1000000,],
+  'minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2': [1000000, 1000000, 1000000]
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+
+export class ModularUserAnnotationToolService {
+
+  static VIEWER_MODE = ARIA_LABELS.VIEWER_MODE_ANNOTATING
+
+  static ANNOTATION_LAYER_NAME = 'modular_tool_layer_name'
+  static USER_ANNOTATION_LAYER_SPEC = {
+    "type": "annotation",
+    "tool": "annotateBoundingBox",
+    "name": ModularUserAnnotationToolService.ANNOTATION_LAYER_NAME,
+    "annotationColor": "#ee00ff",
+    "annotations": [],
+  }
+
+  private selectedTmpl: { fullId: string, name: string }
+  private ngAnnotationLayer: any
+  private activeToolName: string
+  private ngAnnotations$ = new Subject<{
+    tool: string
+    annotations: INgAnnotationTypes[keyof INgAnnotationTypes][]
+  }>()
+  private registeredTool: {
+    name: string
+    iconClass: string
+    ngOnDestroy?: Function
+  }[] = []
+  private mousePosReal: [number, number, number]
+
+  /**
+   * @description register new annotation tool
+   * @param {AbsToolClass} Cls 
+   */
+  private registerTool(Cls: new (
+    svc: Subject<TAnnotationEvent<keyof IAnnotationEvents>>
+  ) => AbsToolClass){
+
+    const newTool = new Cls(this.annotnEvSubj) as AbsToolClass & { ngOnDestroy?: Function }
+    const { name, iconClass, ngOnDestroy } = newTool
+    
+    this.annotnSvc.moduleAnnotationTypes.push({
+      instance: newTool,
+      onClick: () => {
+        const tool = this.activeToolName === name
+          ? null
+          : name
+        this.activeToolName = tool
+        this.annotnEvSubj.next({
+          type: 'toolSelect',
+          detail: { name: tool }
+        } as TAnnotationEvent<'toolSelect'>)
+      }
+    })
+
+    newTool.allNgAnnotations$.subscribe(ann => {
+      this.ngAnnotations$.next({
+        tool: name,
+        annotations: ann
+      })
+    })
+
+    this.registeredTool.push({ name, iconClass, ngOnDestroy })
+  }
+
+  /**
+   * 
+   * @description deregister tool. Calls any necessary clean up function
+   * @param name name of the tool to be deregistered
+   * @returns void
+   */
+  private deregisterTool(name: string) {
+    this.annotnSvc.moduleAnnotationTypes = this.annotnSvc.moduleAnnotationTypes.filter(tool => tool.instance.name !== name)
+    const foundIdx = this.registeredTool.findIndex(spec => spec.name === name)
+    if (foundIdx >= 0) {
+      const tool = this.registeredTool.splice(foundIdx, 1)[0]
+      const { ngOnDestroy } = tool
+      if (ngOnDestroy) ngOnDestroy.call(tool)
+    }
+  }
+
+
+  constructor(
+    private annotnSvc: AnnotationService,
+    store: Store<any>,
+    @Inject(ANNOTATION_EVENT_INJ_TOKEN) private annotnEvSubj: Subject<TAnnotationEvent<keyof IAnnotationEvents>>,
+    @Optional() @Inject(NEHUBA_INSTANCE_INJTKN) nehubaViewer$: Observable<NehubaViewerUnit>,
+  ){
+    this.registerTool(ToolPolygon)
+
+    /**
+     * on new nehubaViewer, unset annotationLayer
+     */
+    nehubaViewer$.subscribe(() => {
+      this.ngAnnotationLayer = null
+    })
+
+    /**
+     * on new nehubaViewer, listen to mouseState
+     */
+    let cb: () => void
+    nehubaViewer$.pipe(
+      switchMap(switchMapWaitFor({
+        condition: nv => !!(nv?.nehubaViewer),
+      }))
+    ).subscribe(nehubaViewer => {
+      if (cb) cb()
+      if (nehubaViewer) {
+        const mouseState = nehubaViewer.nehubaViewer.ngviewer.mouseState
+        cb = mouseState.changed.add(() => {
+          const payload: IAnnotationEvents['hoverAnnotation'] = mouseState.active && !!mouseState.pickedAnnotationId
+            ? {
+              pickedAnnotationId: mouseState.pickedAnnotationId,
+              pickedOffset: mouseState.pickedOffset
+            }
+            : null
+          this.annotnEvSubj.next({
+            type: 'hoverAnnotation',
+            detail: payload
+          })
+        })
+      }
+    })
+
+    /**
+     * get mouse real position
+     */
+    nehubaViewer$.pipe(
+      switchMap(v => v?.mousePosInReal$ || of(null))
+    ).subscribe(v => this.mousePosReal = v)
+    
+    /**
+     * listen to mouse event on nehubaViewer, and emit as TAnnotationEvent
+     */
+    this.annotnSvc.tmpAnnotationMouseEvent.subscribe(ev => {
+      const payload = {
+        type: ev.eventype,
+        detail: {
+          event: ev.event,
+          ngMouseEvent: {
+            x: this.mousePosReal[0],
+            y: this.mousePosReal[1],
+            z: this.mousePosReal[2]
+          }
+        }
+      } as TAnnotationEvent<'mousedown' | 'mouseup' | 'mousemove'>
+      this.annotnEvSubj.next(payload)
+    })
+
+
+    /**
+     * on annotation update, update annotations
+     */
+    this.ngAnnotations$.pipe(
+      switchMap(switchMapWaitFor({
+        condition: () => !!this.ngAnnotationLayer
+      })),
+      scan((acc, curr) => {
+        console.log(curr)
+        return {
+          ...acc,
+          [curr.tool]: curr.annotations
+        }
+      }, {} as {
+        [key: string]: INgAnnotationTypes[keyof INgAnnotationTypes][] 
+      }),
+      map(acc => {
+        const out: INgAnnotationTypes[keyof INgAnnotationTypes][] = []
+        for (const key in acc) {
+          out.push(...acc[key])
+        }
+        return out
+      })
+    ).subscribe(val => {
+      this.ngAnnotationLayer.layer.localAnnotations.restoreState(val)
+    })
+
+    /**
+     * on viewer mode update, either create layer, or show/hide layer
+     */
+    store.pipe(
+      select(viewerStateViewerModeSelector)
+    ).subscribe(viewerMode => {
+      if (viewerMode === ModularUserAnnotationToolService.VIEWER_MODE) {
+        if (this.ngAnnotationLayer) this.ngAnnotationLayer.setVisible(true)
+        else {
+          const viewer = (window as any).viewer
+          const voxelSize = IAV_VOXEL_SIZES_NM[this.selectedTmpl.fullId]
+          if (!voxelSize) throw new Error(`voxelSize of ${this.selectedTmpl.fullId} cannot be found!`)
+          const layer = viewer.layerSpecification.getLayer(
+            ModularUserAnnotationToolService.ANNOTATION_LAYER_NAME,
+            {
+              ...ModularUserAnnotationToolService.USER_ANNOTATION_LAYER_SPEC,
+              transform: [
+                [1/voxelSize[0], 0, 0, 0],
+                [0, 1/voxelSize[1], 0, 0],
+                [0, 0, 1/voxelSize[2], 0],
+                [0, 0, 0, 1],
+              ]
+            }
+          )
+          this.ngAnnotationLayer = viewer.layerManager.addManagedLayer(layer)
+        }
+      } else {
+        if (this.ngAnnotationLayer) this.ngAnnotationLayer.setVisible(false)
+      }
+    })
+
+    /**
+     * on template select, update selectedtmpl
+     * required for metadata in annotation geometry and voxel size
+     */
+    store.pipe(
+      select(viewerStateSelectedTemplatePureSelector)
+    ).subscribe(tmpl => {
+      this.selectedTmpl = tmpl
+    })
+  }
+}
diff --git a/src/atlasComponents/userAnnotations/tools/type.ts b/src/atlasComponents/userAnnotations/tools/type.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6d4edf2b75cd119b3acb1e1f123bb835c083f11c
--- /dev/null
+++ b/src/atlasComponents/userAnnotations/tools/type.ts
@@ -0,0 +1,142 @@
+import { InjectionToken } from "@angular/core"
+import { Observable } from "rxjs"
+import { filter, map, mapTo, switchMap, takeUntil, tap } from 'rxjs/operators'
+import { getUuid } from "src/util/fn"
+
+export type TToolType = 'translation' | 'drawing' | 'deletion'
+
+type THasId = {
+  id?: string
+}
+export abstract class IAnnotationGeometry {
+  public id: string
+  
+  abstract toNgAnnotation(): INgAnnotationTypes[keyof INgAnnotationTypes][]
+  abstract toJSON(): object
+
+  constructor(spec?: THasId){
+    this.id = spec && spec.id || getUuid()
+  }
+}
+
+export interface IAnnotationTools {
+  name: string
+  iconClass: string
+  toolType: TToolType
+}
+
+export type TNgAnnotationEv = {
+  pickedAnnotationId: string
+  pickedOffset: number
+}
+
+export type TNgMouseEvent = {
+  event: MouseEvent
+  ngMouseEvent: {
+    x: number
+    y: number
+    z: number
+  }
+}
+
+export abstract class AbsToolClass {
+
+  public abstract name: string
+  public abstract iconClass: string
+
+  /**
+   * @description check if any specific annotation is relevant to the tool. Used for filtering annotations
+   * @param {TNgAnnotationEv} annotation
+   * @returns {boolean} if annotation is relevant to this tool
+   */
+  public abstract ngAnnotationIsRelevant(hoverEv: TNgAnnotationEv): boolean
+
+  public abstract allNgAnnotations$: Observable<INgAnnotationTypes[keyof INgAnnotationTypes][]>
+
+  constructor(
+    protected annotationEv$: Observable<TAnnotationEvent<keyof IAnnotationEvents>>
+  ){
+
+  }
+
+  public toolSelected$ = this.annotationEv$.pipe(
+    filter(ev => ev.type === 'toolSelect'),
+    map(ev => (ev as TAnnotationEvent<'toolSelect'>).detail.name === this.name)
+  )
+
+  protected mouseDown$ = this.annotationEv$.pipe(
+    filter(ev => ev.type === 'mousedown')
+  ) as Observable<TAnnotationEvent<'mousedown'>>
+
+  protected mouseUp$ = this.annotationEv$.pipe(
+    filter(ev => ev.type === 'mouseup')
+  ) as Observable<TAnnotationEvent<'mouseup'>>
+
+  protected mouseMove$ = this.annotationEv$.pipe(
+    filter(ev => ev.type === 'mousemove')
+  ) as Observable<TAnnotationEvent<'mousemove'>>
+
+  protected mouseClick$ = this.mouseDown$.pipe(
+    switchMap(ev => this.mouseUp$.pipe(
+      takeUntil(this.mouseMove$),
+      mapTo(ev)
+    ))
+  )
+
+  protected hoverAnnotation$ = this.annotationEv$.pipe(
+    filter(ev => ev.type === 'hoverAnnotation')
+  ) as Observable<TAnnotationEvent<'hoverAnnotation'>>
+
+  protected hoverAnnotationMouseDown$ = this.hoverAnnotation$.pipe(
+    tap(console.log),
+    filter(ev => !!this.ngAnnotationIsRelevant(ev.detail)),
+    switchMap(hoverAnnotationEv => this.mouseDown$.pipe(
+      map(mouseDownEv => {
+        return {
+          ngAnnotationEv: hoverAnnotationEv.detail,
+          mouseEvent: mouseDownEv.detail.event
+        }
+      })
+    ))
+  )
+}
+
+export interface IAnnotationEvents {
+  toolSelect: {
+    name: string
+  }
+  mousemove: TNgMouseEvent
+  mousedown: TNgMouseEvent
+  mouseup: TNgMouseEvent
+  hoverAnnotation: TNgAnnotationEv
+}
+
+export type TAnnotationEvent<T extends keyof IAnnotationEvents> = {
+  type: T
+  detail: IAnnotationEvents[T]
+}
+
+export const ANNOTATION_EVENT_INJ_TOKEN = new InjectionToken<
+  Observable<TAnnotationEvent<keyof IAnnotationEvents>>
+>('ANNOTATION_EVENT_INJ_TOKEN')
+
+
+export type TNgAnnotationLine = {
+  type: 'line'
+  pointA: [number, number, number]
+  pointB: [number, number, number]
+  id: string
+  description?: string
+}
+
+export type TNgAnnotationPoint = {
+  type: 'point'
+  point: [number, number, number]
+  id: string
+  description?: string
+}
+
+export interface INgAnnotationTypes {
+  line: TNgAnnotationLine
+  point: TNgAnnotationPoint
+}
diff --git a/src/res/css/extra_styles.css b/src/res/css/extra_styles.css
index fbe0d58637112890ef94b5f53e34b4d68aa20da0..a295e3f09f7c6c710e41a6686dbf6beaf64289c6 100644
--- a/src/res/css/extra_styles.css
+++ b/src/res/css/extra_styles.css
@@ -303,6 +303,10 @@ markdown-dom p
   width: 0!important;
 }
 
+.w-3em
+{
+  width: 3em!important;
+}
 .w-5em
 {
   width: 5em!important;
diff --git a/src/util/fn.ts b/src/util/fn.ts
index 4f673a08e5b2ace779f13d8994d68965687688af..b29be00b988db13f1614af91a171b382d7862570 100644
--- a/src/util/fn.ts
+++ b/src/util/fn.ts
@@ -74,11 +74,11 @@ export const arrayOfPrimitiveEqual = <T extends TPrimitive>(o: T[], n: T[]) =>
 
 interface ISwitchMapWaitFor {
   interval?: number
-  condition: () => boolean
+  condition: (arg?: any) => boolean
 }
-export function switchMapWaitFor(opts: ISwitchMapWaitFor){
-  return (arg: unknown) => interval(opts.interval || 16).pipe(
-    filter(() => opts.condition()),
+export function switchMapWaitFor<T>(opts: ISwitchMapWaitFor){
+  return (arg: T) => interval(opts.interval || 16).pipe(
+    filter(() => opts.condition(arg)),
     take(1),
     mapTo(arg)
   )