Newer
Older
import { Component, ElementRef, EventEmitter, OnDestroy, Output, Inject, Optional } from "@angular/core";
import { Subscription, BehaviorSubject, Observable, Subject, of, interval, combineLatest } from 'rxjs'
import { debounceTime, filter, scan, switchMap, take, distinctUntilChanged, debounce, map } from "rxjs/operators";
import { LoggingService } from "src/logging";
import { bufferUntil, getExportNehuba, getUuid, switchMapWaitFor } from "src/util/fn";
import { deserializeSegment, NEHUBA_INSTANCE_INJTKN } from "../util";
import { arrayOrderedEql, rgbToHex } from 'common/util'
import { IMeshesToLoad, SET_MESHES_TO_LOAD, PERSPECTIVE_ZOOM_FUDGE_FACTOR } from "../constants";
import { IColorMap, SET_COLORMAP_OBS, SET_LAYER_VISIBILITY } from "../layerCtrl.service";
import { EXTERNAL_LAYER_CONTROL, IExternalLayerCtl, INgLayerCtrl, NG_LAYER_CONTROL, SET_SEGMENT_VISIBILITY, TNgLayerCtrl, Z_TRAVERSAL_MULTIPLIER } from "../layerCtrl.service/layerCtrl.util";
import { NgCoordinateSpace, Unit } from "../types";
import { PeriodicSvc } from "src/util/periodic.service";
import { ViewerInternalStateSvc, AUTO_ROTATE } from "src/viewerModule/viewerInternalState.service";
function translateUnit(unit: Unit) {
if (unit === "m") {
return 1e9
}
throw new Error(`Cannot translate unit: ${unit}`)
}
export const IMPORT_NEHUBA_INJECT_TOKEN = `IMPORT_NEHUBA_INJECT_TOKEN`
}
labelIndicies: number[]
}
export const scanFn = (acc: LayerLabelIndex[], curr: LayerLabelIndex) => {
const found = acc.find(layerLabelIndex => {
return layerLabelIndex.layer.name === curr.layer.name
})
if (!found) {
return [ ...acc, curr ]
return acc.map(layerLabelIndex => {
return layerLabelIndex.layer.name === curr.layer.name
? curr
: layerLabelIndex
})
/**
* no selector is needed, as currently, nehubaviewer is created dynamically
*/
@Component({
templateUrl : './nehubaViewer.template.html',
styleUrls : [
export class NehubaViewerUnit implements OnDestroy {
#translateVoxelToReal: (voxels: number[]) => number[]
public viewerPosInReal$ = new BehaviorSubject<[number, number, number]>(null)
public mousePosInVoxel$ = new BehaviorSubject<[number, number, number]>(null)
public mousePosInReal$ = new BehaviorSubject(null)
private subscriptions: Subscription[] = []
@Output() public nehubaReady: EventEmitter<null> = new EventEmitter()
@Output() public layersChanged: EventEmitter<null> = new EventEmitter()
@Output() public viewerPositionChange: EventEmitter<{ orientation: number[], perspectiveOrientation: number[], perspectiveZoom: number, zoom: number, position: number[], positionReal?: boolean }> = new EventEmitter()
@Output() public errorEmitter: EventEmitter<any> = new EventEmitter()
/* only used to set initial navigation state */
private _dim: [number, number, number]
return this._dim
? this._dim
: [1.5e9, 1.5e9, 1.5e9]
}
#newViewerSubs: { unsubscribe: () => void }[] = []
Xiao Gui
committed
public nehubaLoaded: boolean = false
constructor(
@Inject(IMPORT_NEHUBA_INJECT_TOKEN) getImportNehubaPr: () => Promise<any>,
@Optional() @Inject(NEHUBA_INSTANCE_INJTKN) private nehubaViewer$: Subject<NehubaViewerUnit>,
@Optional() @Inject(SET_MESHES_TO_LOAD) private injSetMeshesToLoad$: Observable<IMeshesToLoad>,
@Optional() @Inject(SET_COLORMAP_OBS) private setColormap$: Observable<IColorMap>,
@Optional() @Inject(SET_LAYER_VISIBILITY) private layerVis$: Observable<string[]>,
@Optional() @Inject(SET_SEGMENT_VISIBILITY) private segVis$: Observable<string[]>,
@Optional() @Inject(NG_LAYER_CONTROL) private layerCtrl$: Observable<TNgLayerCtrl<keyof INgLayerCtrl>>,
@Optional() @Inject(Z_TRAVERSAL_MULTIPLIER) multiplier$: Observable<number>,
@Optional() @Inject(EXTERNAL_LAYER_CONTROL) private externalLayerCtrl: IExternalLayerCtl,
@Optional() intViewerStateSvc: ViewerInternalStateSvc,
if (multiplier$) {
this.ondestroySubscriptions.push(
multiplier$.subscribe(val => this.multplier[0] = val)
)
} else {
this.multplier[0] = 1
}
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
if (intViewerStateSvc) {
let raqRef: number
const {
done,
next,
} = intViewerStateSvc.registerEmitter({
"@type": "TViewerInternalStateEmitter",
viewerType: "nehuba",
applyState: arg => {
if (arg.viewerType === AUTO_ROTATE) {
if (raqRef) {
cancelAnimationFrame(raqRef)
}
const autoPlayFlag = (arg.payload as any).play
const reverseFlag = (arg.payload as any).reverse
const autoplaySpeed = (arg.payload as any).speed
if (!autoPlayFlag) {
return
}
const deg = (reverseFlag ? 1 : -1) * autoplaySpeed * 1e-3
const animate = () => {
raqRef = requestAnimationFrame(() => {
animate()
})
const perspectivePose = this.nehubaViewer?.ngviewer?.perspectiveNavigationState?.pose
if (!perspectivePose) {
return
}
perspectivePose.rotateAbsolute([0, 0, 1], deg, [0, 0, 0])
}
animate()
return
}
}
})
this.onDestroyCb.push(() => done())
next({
"@id": getUuid(),
'@type': "TViewerInternalStateEmitterEvent",
viewerType: "nehuba",
payload: {}
})
}
if (this.nehubaViewer$) {
this.nehubaViewer$.next(this)
}
.then(() => getExportNehuba())
.then(exportNehuba => {
const fixedZoomPerspectiveSlices = this.config && this.config.layout && this.config.layout.useNehubaPerspective && this.config.layout.useNehubaPerspective.fixedZoomPerspectiveSlices
if (fixedZoomPerspectiveSlices) {
const { sliceZoom, sliceViewportWidth, sliceViewportHeight } = fixedZoomPerspectiveSlices
const dim = Math.min(sliceZoom * sliceViewportWidth, sliceZoom * sliceViewportHeight)
this._dim = [dim, dim, dim]
}
this.patchNG()
this.loadNehuba()
const viewer = this.nehubaViewer.ngviewer
this.layersChangedHandler = viewer.layerManager.readyStateChanged.add(() => {
const readiedLayerNames: string[] = viewer.layerManager.managedLayers.filter(l => l.isReady()).map(l => l.name)
for (const layerName in this.ngIdSegmentsMap) {
if (!readiedLayerNames.includes(layerName)) {
return
}
}
this._nehubaReady = true
this.nehubaReady.emit(null)
})
viewer.registerDisposer(this.layersChangedHandler)
})
.catch(e => this.errorEmitter.emit(e))
if (this.setColormap$) {
this.ondestroySubscriptions.push(
this.setColormap$.pipe(
debounceTime(160),
).subscribe(v => {
const map = new Map()
for (const key in v) {
const m = new Map()
map.set(key, m)
for (const lblIdx in v[key]) {
m.set(lblIdx, v[key][lblIdx])
}
}
this.setColorMap(map)
})
)
} else {
this.log.error(`SET_COLORMAP_OBS not provided`)
}
if (this.layerVis$) {
this.ondestroySubscriptions.push(
this.layerVis$.pipe(
switchMap(switchMapWaitFor({
condition: () => this._nehubaReady
})),
distinctUntilChanged(arrayOrderedEql),
debounceTime(160),
).subscribe((layerNames: string[]) => {
/**
* debounce 160ms to set layer invisible etc
* on switch from freesurfer -> volumetric viewer, race con results in managed layer not necessarily setting layer visible correctly
*/
const managedLayers = this.nehubaViewer.ngviewer.layerManager.managedLayers
managedLayers.forEach(layer => {
if (this.externalLayerCtrl && this.externalLayerCtrl.ExternalLayerNames.has(layer.name)) {
return
}
layer.setVisible(false)
})
for (const layerName of layerNames) {
const layer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(layerName)
if (layer) {
layer.setVisible(true)
} else {
this.log.log('layer unavailable', layerName)
}
}
})
)
} else {
this.log.error(`SET_LAYER_VISIBILITY not provided`)
}
if (this.segVis$) {
this.ondestroySubscriptions.push(
// null === hide all seg
if (val === null) {
this.hideAllSeg()
return
}
// empty array === show all seg
if (val.length === 0) {
this.showAllSeg()
return
}
// show limited seg
this.showSegs(val)
})
)
} else {
this.log.error(`SET_SEGMENT_VISIBILITY not provided`)
}
if (this.layerCtrl$) {
this.ondestroySubscriptions.push(
this.layerCtrl$.pipe(
bufferUntil(({
}))
).subscribe(messages => {
for (const message of messages) {
if (message.type === 'add') {
const p = message as TNgLayerCtrl<'add'>
this.loadLayer(p.payload)
}
if (message.type === 'remove') {
const p = message as TNgLayerCtrl<'remove'>
for (const name of p.payload.names){
this.removeLayer({ name })
}
}
if (message.type === 'update') {
const p = message as TNgLayerCtrl<'update'>
this.updateLayer(p.payload)
}
if (message.type === 'setLayerTransparency') {
const p = message as TNgLayerCtrl<'setLayerTransparency'>
for (const key in p.payload) {
this.setLayerTransparency(key, p.payload[key])
}
}
if (message.type === "updateShader") {
const p = message as TNgLayerCtrl<'updateShader'>
for (const key in p.payload) {
this.setLayerShader(key, p.payload[key])
}
}
}
})
)
} else {
this.log.error(`NG_LAYER_CONTROL not provided`)
}
if (this.injSetMeshesToLoad$) {
this.subscriptions.push(
combineLatest([
this.#triggerMeshLoad$,
this.injSetMeshesToLoad$.pipe(
scan(scanFn, []),
),
]).pipe(
map(([_, val]) => val),
debounce(() => this._nehubaReady
? of(true)
: interval(160).pipe(
),
).subscribe(layersLabelIndex => {
let totalMeshes = 0
for (const layerLayerIndex of layersLabelIndex) {
const { layer, labelIndicies } = layerLayerIndex
totalMeshes += labelIndicies.length
this.nehubaViewer.setMeshesToLoad(labelIndicies, layer)
}
}),
)
} else {
this.log.error(`SET_MESHES_TO_LOAD not provided`)
}
}
if (gpuLimit && this.nehubaViewer) {
const limit = this.nehubaViewer.ngviewer.state.children.get('gpuMemoryLimit')
if (limit && limit.restoreState) {
limit.restoreState(gpuLimit)
}
}
}
private _multiNgIdColorMap: Map<string, Map<number, {red: number, green: number, blue: number}>>
get multiNgIdColorMap() {
return this._multiNgIdColorMap
}
set multiNgIdColorMap(val) {
this._multiNgIdColorMap = val
}
public mouseOverSegment: number | null
public getNgHash: () => string = () => this.exportNehuba
? this.exportNehuba.getNgHash()
this.nehubaViewer = createNehubaViewer(this.config, (err: string) => {
/* print in debug mode */
this.log.error(err)
/**
* Hide all layers except the base layer (template)
* Then show the layers referenced in multiNgIdLabelIndexMap
*/
const patchSliceview = async () => {
viewer.inputEventBindings.sliceView.set("at:wheel", "proxy-wheel")
viewer.inputEventBindings.sliceView.set("at:control+shift+wheel", "proxy-wheel-alt")
await (async () => {
let lenPanels = 0
while (lenPanels === 0) {
lenPanels = viewer.display.panels.size
/* creation of the layout is done on next frame, hence the settimeout */
await new Promise(rs => setTimeout(rs, 150))
}
})()
viewer.inputEventBindings.sliceView.set("at:wheel", "proxy-wheel-1")
viewer.inputEventBindings.sliceView.set("at:keyp", "proxy-wheel-1")
viewer.inputEventBindings.sliceView.set("at:keyn", "proxy-wheel-1")
viewer.inputEventBindings.sliceView.set("at:control+shift+wheel", "proxy-wheel-10")
viewer.display.panels.forEach(sliceView => patchSliceViewPanel(sliceView, this.exportNehuba, this.multplier))
}
viewer.inputEventBindings.sliceView.set("at:touchhold1", { action: "noop", stopPropagation: false })
viewer.inputEventBindings.perspectiveView.set("at:touchhold1", { action: "noop", stopPropagation: false })
this.onDestroyCb.push(() => {
window['nehubaViewer'] = null
})
if (this.nehubaViewer$) {
this.nehubaViewer$.next(null)
}
this.subscriptions.pop().unsubscribe()
}
while (this.#newViewerSubs.length > 0) {
this.#newViewerSubs.pop().unsubscribe()
}
this.ondestroySubscriptions.forEach(s => s.unsubscribe())
this.onDestroyCb.pop()()
}
this.nehubaViewer && this.nehubaViewer.dispose()
private onDestroyCb: Array<() => void> = []
private patchNG() {
const { LayerManager, UrlHashBinding } = this.exportNehuba.getNgPatchableObj()
// this.log.log('seturl hash')
// this.log.log('setting url hash')
}
UrlHashBinding.prototype.updateFromUrlHash = () => {
/* TODO find a more permanent fix to disable double click */
LayerManager.prototype.invokeAction = (arg) => {
/**
* The emitted value does not affect the region selection
* the region selection is taken care of in nehubaContainer
*/
this.regionSelectionEmitter.emit({
segment: this.mouseOverSegment,
layer: this.mouseOverLayer
})
/* eslint-disable-next-line @typescript-eslint/no-empty-function */
this.onDestroyCb.push(() => LayerManager.prototype.invokeAction = (_arg) => { /** in default neuroglancer, this function is invoked when selection occurs */ })
/**
* if selector is an empty object, select all layers
*/
return layerObj instanceof Object && Object.keys(layerObj).every(key =>
/**
* the property described by the selector must exist and ...
*/
/**
* if the selector is regex, test layer property
*/
( layerObj[key] instanceof RegExp
? layerObj[key].test(l[key])
/**
* if selector is string, test for strict equality
*/
: typeof layerObj[key] === 'string'
? layerObj[key] === l[key]
/**
* otherwise do not filter
*/
public setLayerVisibility(condition: {name: string|RegExp}, visible: boolean) {
if (!this.nehubaViewer) {
return false
const viewer = this.nehubaViewer.ngviewer
viewer.layerManager.managedLayers
.filter(l => this.filterLayers(l, condition))
.map(layer => layer.setVisible(visible))
return false
const removeLayer = (i) => (viewer.layerManager.removeManagedLayer(i), i.name)
return viewer.layerManager.managedLayers
const viewer = this.nehubaViewer.ngviewer
return Object.keys(layerObj)
/* if the layer exists, it will not be loaded */
!viewer.layerManager.getLayerByName(key))
/**
* new implementation of neuroglancer treats swc as a mesh layer of segmentation layer
* But it cannot *directly* be accessed by nehuba's setMeshesToLoad, since it filters by
* UserSegmentationLayer.
*
* The below monkey patch sets the mesh to load, allow the SWC to be shown
*/
let url = layerObj[key]['source']
if (typeof url !== "string") {
url = url['url']
}
const isSwc = url.includes("swc://")
const hasSegment = (layerObj[key]["segments"] || []).length > 0
if (isSwc && hasSegment) {
this.periodicSvc.addToQueue(
() => {
const layer = viewer.layerManager.getLayerByName(key)
if (!(layer?.layer)) {
return false
}
layer.layer.displayState.visibleSegments.setMeshesToLoad([1])
return true
}
)
}
const { transform=null, ...rest } = layerObj[key]
const combined = {
type: 'image',
...rest,
...(transform ? { transform } : {})
}
public updateLayer(layerObj: INgLayerCtrl['update']) {
const viewer = this.nehubaViewer.ngviewer
for (const layerName in layerObj) {
const layer = viewer.layerManager.getLayerByName(layerName)
if (!layer) continue
const { visible } = layerObj[layerName]
layer.setVisible(!!visible)
}
}
if (!this.nehubaViewer) return
for (const ngId in this.ngIdSegmentsMap) {
for (const idx of this.ngIdSegmentsMap[ngId]) {
this.nehubaViewer.hideSegment(idx, {
this.nehubaViewer.showSegment(0, {
for (const ngId in this.ngIdSegmentsMap) {
for (const idx of this.ngIdSegmentsMap[ngId]) {
this.nehubaViewer.showSegment(idx, {
name: ngId,
})
}
Xiao Gui
committed
this.hideAllSeg()
Xiao Gui
committed
/**
* TODO tobe deprecated
*/
if (typeof array[0] === 'number') {
const reduceFn: (acc: Map<string, number[]>, curr: string) => Map<string, number[]> = (acc, curr) => {
if (!exist) {
newMap.set(ngId, [Number(labelIndex)])
} else {
newMap.set(ngId, [...exist, Number(labelIndex)])
}
const newMap: Map<string, number[]> = array.reduce(reduceFn, new Map())
/**
* TODO
* ugh, ugly code. cleanify
*/
* sometimes, ngId still happends to be undefined
*/
newMap.forEach((segs, ngId) => {
this.nehubaViewer.hideSegment(0, {
})
segs.forEach(seg => {
this.nehubaViewer.showSegment(seg, {
this.log.warn('setNavigationState > this.nehubaViewer is not yet defined')
Xiao Gui
committed
const {
orientation,
perspectiveOrientation,
perspectiveZoom,
position,
positionReal,
Xiao Gui
committed
this.nehubaViewer.ngviewer.perspectiveNavigationState.zoomFactor.restoreState(perspectiveZoom * PERSPECTIVE_ZOOM_FUDGE_FACTOR)
Xiao Gui
committed
this.nehubaViewer.ngviewer.navigationState.zoomFactor.restoreState(zoom)
Xiao Gui
committed
this.nehubaViewer.ngviewer.perspectiveNavigationState.pose.orientation.restoreState( perspectiveOrientation )
Xiao Gui
committed
this.nehubaViewer.ngviewer.navigationState.pose.orientation.restoreState( orientation )
this.nehubaViewer.setPosition( this.vec3(position) , positionReal ? true : false )
Xiao Gui
committed
}
public toggleOctantRemoval(flag?: boolean) {
const ctrl = this.nehubaViewer?.ngviewer?.showPerspectiveSliceViews
if (!ctrl) {
this.log.error(`toggleOctantRemoval failed. this.nehubaViewer.ngviewer?.showPerspectiveSliceViews returns falsy`)
return
}
const newVal = typeof flag === 'undefined'
? !ctrl.value
: flag
ctrl.restoreState(newVal)
}
private setLayerTransparency(layerName: string, alpha: number) {
const layer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(layerName)
/**
* for segmentation layer
*/
if (layer.layer.displayState) layer.layer.displayState.objectAlpha.restoreState(alpha)
/**
* for image layer
*/
if (layer.layer.opacity) layer.layer.opacity.restoreState(alpha)
private setLayerShader(layerName: string, shader: string) {
const layer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(layerName)
if (layer?.layer?.fragmentMain) layer.layer.fragmentMain.restoreState(shader)
}
public setMeshTransparency(flag: boolean){
/**
* remove transparency from meshes in current layer(s)
*/
const layer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(layerKey)
if (layer) {
layer.layer.displayState.objectAlpha.restoreState(flag ? 0.2 : 1.0)
}
}
}
public redraw(){
this.nehubaViewer.redraw()
}
while (this.#newViewerSubs.length > 0) {
this.#newViewerSubs.pop().unsubscribe()
}
this.#newViewerSubs.push(
/* isn't this layer specific? */
/* TODO this is layer specific. need a way to distinguish between different segmentation layers */
this.nehubaViewer.mouseOver.segment.subscribe(({ segment, layer }) => {
this.mouseOverSegment = segment
this.mouseOverLayer = { ...layer }
this.nehubaViewer.mouseOver.segment.subscribe(({segment: segmentId, layer }) => {
this.mouseoverSegmentEmitter.emit({
layer,
segmentId,
})
}),
Xiao Gui
committed
// nehubaViewer.navigationState.all emits every time a new layer is added or removed from the viewer
this.nehubaViewer.navigationState.all
.distinctUntilChanged((a, b) => {
const {
orientation: o1,
perspectiveOrientation: po1,
perspectiveZoom: pz1,
position: p1,
} = a
const {
orientation: o2,
perspectiveOrientation: po2,
perspectiveZoom: pz2,
position: p2,
} = b
return [0, 1, 2, 3].every(idx => o1[idx] === o2[idx]) &&
[0, 1, 2, 3].every(idx => po1[idx] === po2[idx]) &&
pz1 === pz2 &&
z1 === z2
})
/**
* somewhat another fudge factor
* navigationState.all occassionally emits slice zoom and perspective zoom that maeks no sense
* filter those out
*
* TODO find out why, and perhaps inform pavel about this
*/
.filter(val => !this.initNav && val?.perspectiveZoom > 10)
.subscribe(({ orientation, perspectiveOrientation, perspectiveZoom, position, zoom }) => {
orientation : Array.from(orientation),
perspectiveOrientation : Array.from(perspectiveOrientation),
}),
this.nehubaViewer.navigationState.position.inVoxels
.filter(v => typeof v !== 'undefined' && v !== null)
.subscribe((v: Float32Array) => {
const coordInVoxel = Array.from(v)
this.viewerPosInVoxel$.next(coordInVoxel)
if (this.#translateVoxelToReal) {
const coordInReal = this.#translateVoxelToReal(coordInVoxel)
this.viewerPosInReal$.next(coordInReal as [number, number, number])
}
}),
Xiao Gui
committed
this.nehubaViewer.mousePosition.inVoxels
.filter((v: Float32Array) => typeof v !== 'undefined' && v !== null)
.subscribe((v: Float32Array) => {
const coordInVoxel = Array.from(v) as [number, number, number]
this.mousePosInVoxel$.next( coordInVoxel )
if (this.#translateVoxelToReal) {
const coordInReal = this.#translateVoxelToReal(coordInVoxel)
this.mousePosInReal$.next( coordInReal )
}
}),
const coordSpListener = this.nehubaViewer.ngviewer.coordinateSpace.changed.add(() => {
const coordSp = this.nehubaViewer.ngviewer.coordinateSpace.value as NgCoordinateSpace
if (coordSp.valid) {
this.#translateVoxelToReal = (coordInVoxel: number[]) => {
return coordInVoxel.map((voxel, idx) => (
translateUnit(coordSp.units[idx])
* coordSp.scales[idx]
* voxel
))
}
}
this.nehubaViewer.ngviewer.registerDisposer(coordSpListener)
if (this.initNav) {
this.setNavigationState(this.initNav)
this.initNav = null
}
private setColorMap(map: Map<string, Map<number, {red: number, green: number, blue: number}>>) {
for (const [ ngId, cMap ] of map.entries()) {
for (const [ key, cm ] of cMap.entries()) {
nRecord[key] = rgbToHex([cm.red, cm.green, cm.blue])
}
mainDict[ngId] = nRecord
/**
* n.b.
* cannot restoreState on each individual layer
* it seems to create duplicated datasources, which eats memory, and wrecks opacity
*/
}
/**
* n.b. 2
* updating layer colormap seems to also mess up the position ()
*/
const layersManager = this.nehubaViewer.ngviewer.state.children.get("layers")
const position = this.nehubaViewer.ngviewer.state.children.get("position")
const prevPos = position.toJSON()
const layerJson = layersManager.toJSON()
for (const layer of layerJson) {
if (layer.name in mainDict) {
layer['segmentColors'] = mainDict[layer.name]
Xiao Gui
committed
}
// TODO either emit contextmenu
// or capture longtouch on higher level as contextmenu
// at the moment, this is required to override default behavior (move to cursur location)
}
const patchSliceViewPanel = (sliceViewPanel: any, exportNehuba: any, mulitplier: Float32Array) => {
// patch draw calls to dispatch viewerportToData
sliceViewPanel.draw = function(this) {
if (this.sliceView) {
const viewportToDataEv = new CustomEvent('viewportToData', {
bubbles: true,
detail: {
viewportToData : this.sliceView.invViewMatrix,
})
this.element.dispatchEvent(viewportToDataEv)
}
originalDraw.call(this)
}
// patch ctrl+wheel & shift+wheel
const { navigationState } = sliceViewPanel
const { registerActionListener, vec3 } = exportNehuba
const tempVec3 = vec3.create()
for (const val of [1, 10]) {
registerActionListener(sliceViewPanel.element, `proxy-wheel-${val}`, event => {
const e = event.detail
let keyDelta = null
if (e.key === "p") {
keyDelta = -1
}
if (e.key === "n") {
keyDelta = 1
}
const wheelDelta = e.deltaY !== 0 ? e.deltaY : e.deltaX
const delta = keyDelta ?? wheelDelta
offset[0] = 0
offset[1] = 0
offset[2] = (delta > 0 ? -1 : 1) * mulitplier[0] * val
navigationState.pose.translateVoxelsRelative(offset)
})
}
registerActionListener(sliceViewPanel.element, `noop`, noop)