Newer
Older
import { AfterContentChecked, AfterViewInit, ChangeDetectionStrategy, Component, EventEmitter, Inject, OnDestroy, Optional, Output, TemplateRef, ViewChild } from "@angular/core";
import { select, Store } from "@ngrx/store";
import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util";
import { distinctUntilChanged, filter, map } from "rxjs/operators";
import { ARIA_LABELS, CONST } from 'common/constants'
import { IViewer, TViewerEvent } from "../../viewer.interface";
import { NehubaMeshService } from "../mesh.service";
import { NehubaLayerControlService, SET_COLORMAP_OBS, SET_LAYER_VISIBILITY } from "../layerCtrl.service";
import { getExportNehuba, getUuid } from "src/util/fn";
import { NG_LAYER_CONTROL, SET_SEGMENT_VISIBILITY } from "../layerCtrl.service/layerCtrl.util";
import { MatSnackBar } from "@angular/material/snack-bar";
import { getShader } from "src/util/constants";
import { EnumColorMapName } from "src/util/colorMaps";
import { MatDialog } from "@angular/material/dialog";
import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service";
import { SapiRegionModel } from "src/atlasComponents/sapi";
import { NehubaConfig } from "../config.service";
import { atlasAppearance, atlasSelection, userInteraction } from "src/state";
import { linearTransform, TVALID_LINEAR_XFORM_DST, TVALID_LINEAR_XFORM_SRC } from "src/atlasComponents/sapi/core/space/interspaceLinearXform";
import { CustomLayer, NgLayerCustomLayer } from "src/state/atlasAppearance";
import { arrayEqual } from "src/util/array";
export const INVALID_FILE_INPUT = `Exactly one (1) file is required!`
@Component({
selector: 'iav-cmp-viewer-nehuba-glue',
templateUrl: './nehubaViewerGlue.template.html',
styleUrls: [
'./nehubaViewerGlue.style.css'
exportAs: 'iavCmpViewerNehubaGlue',
providers: [
{
provide: SET_MESHES_TO_LOAD,
useFactory: (meshService: NehubaMeshService) => meshService.loadMeshes$,
deps: [ NehubaMeshService ]
},
NehubaMeshService,
{
provide: SET_COLORMAP_OBS,
useFactory: (layerCtrl: NehubaLayerControlService) => layerCtrl.setColorMap$,
deps: [ NehubaLayerControlService ]
},
{
provide: SET_LAYER_VISIBILITY,
useFactory: (layerCtrl: NehubaLayerControlService) => layerCtrl.visibleLayer$,
deps: [ NehubaLayerControlService ]
},
{
provide: SET_SEGMENT_VISIBILITY,
useFactory: (layerCtrl: NehubaLayerControlService) => layerCtrl.segmentVis$,
deps: [ NehubaLayerControlService ]
},
{
provide: NG_LAYER_CONTROL,
useFactory: (layerCtrl: NehubaLayerControlService) => layerCtrl.ngLayersController$,
deps: [ NehubaLayerControlService ]
},
NehubaLayerControlService,
NehubaMeshService,
],
changeDetection: ChangeDetectionStrategy.OnPush
export class NehubaGlueCmp implements IViewer<'nehuba'>, AfterViewInit, OnDestroy {
@ViewChild('layerCtrlTmpl', { static: true })
layerCtrlTmpl: TemplateRef<any>
while (this.onDestroyCb.length) this.onDestroyCb.pop()()
}
public viewerEvent = new EventEmitter<TViewerEvent<'nehuba'>>()
@Optional() @Inject(CLICK_INTERCEPTOR_INJECTOR) clickInterceptor: ClickInterceptor,
/**
* define onclick behaviour
*/
if (clickInterceptor) {
const { deregister, register } = clickInterceptor
const selOnhoverRegion = this.selectHoveredRegion.bind(this)
this.onDestroyCb.push(() => deregister(selOnhoverRegion))
}
/**
* on hover segment
*/
const onhovSegSub = this.store$.pipe(
})
this.onDestroyCb.push(() => onhovSegSub.unsubscribe())
private selectHoveredRegion(_ev: any): boolean{
/**
* If label indicies are not defined by the ontology, it will be a string in the format of `{ngId}#{labelIndex}`
*/
const trueOnhoverSegments = this.onhoverSegments && this.onhoverSegments.filter(v => typeof v === 'object')
if (!trueOnhoverSegments || (trueOnhoverSegments.length === 0)) return true
atlasSelection.actions.selectRegion({
region: trueOnhoverSegments[0]
ngAfterViewInit(): void {
const customLayer = this.store$.pipe(
select(atlasAppearance.selectors.customLayers),
distinctUntilChanged(arrayEqual((o: NgLayerCustomLayer, n) => o.id === n.id)),
const customLayers = cl.filter(l => l.clType === "customlayer/nglayer/controller" && l.controllable)
filter(r => !!r),
distinctUntilChanged(arrayEqual((o: NgLayerCustomLayer, n) => o.source === n.source)),
).subscribe((l: NgLayerCustomLayer[]) => {
if (l && l.length === 1) {
this.openLayerController({layerName: l[0].id, fileName: l[0].source.split(',').pop()})
}
})
this.onDestroyCb.push(() => customLayer.unsubscribe())
}
private droppedLayerNames: {
layerName: string
resourceUrl: string
}[] = []
private dismissAllAddedLayers(){
while (this.droppedLayerNames.length) {
const { resourceUrl, layerName } = this.droppedLayerNames.pop()
this.store$.dispatch(
atlasAppearance.actions.removeCustomLayer({
id: layerName
})
)
URL.revokeObjectURL(resourceUrl)
}
}
if (files.length !== 1) {
this.snackbar.open(INVALID_FILE_INPUT, 'Dismiss', {
duration: 5000
})
return
}
const randomUuid = getUuid()
const file = files[0]
/**
* TODO check extension?
*/
this.dismissAllAddedLayers()
let message = `The swc rendering is experimental. Please contact us on any feedbacks. `
const swcText = await file.text()
let src: TVALID_LINEAR_XFORM_SRC
if (/ccf/i.test(swcText)) {
src = "CCF"
message += `CCF detected, applying known transformation.`
}
if (!src) {
message += `no known space detected. Applying default transformation.`
}
const xform = await linearTransform(src, dst)
const url = URL.createObjectURL(file)
this.droppedLayerNames.push({
layerName: randomUuid,
resourceUrl: url
})
this.store$.dispatch(
atlasAppearance.actions.addCustomLayer({
customLayer: {
id: randomUuid,
source: `swc://${url}`,
segments: ["1"],
clType: 'customlayer/nglayer' as const
}
})
)
this.snackbar.open(message, "Dismiss", {
duration: 10000
})
// Get file, try to inflate, if files, use original array buffer
const buf = await file.arrayBuffer()
let outbuf
try {
outbuf = getExportNehuba().pako.inflate(buf).buffer
} catch (e) {
console.log('unpack error', e)
outbuf = buf
}
method: 'PROCESS_NIFTI',
param: {
nifti: outbuf
transfers: [ outbuf ]
})
const { meta, buffer } = result
const url = URL.createObjectURL(new Blob([ buffer ]))
this.droppedLayerNames.push({
layerName: randomUuid,
resourceUrl: url
})
this.store$.dispatch(
atlasAppearance.actions.addCustomLayer({
customLayer: {
id: randomUuid,
source: `nifti://${url}`,
shader: getShader({
colormap: EnumColorMapName.MAGMA,
lowThreshold: meta.min || 0,
highThreshold: meta.max || 1
}),
clType: 'customlayer/nglayer'
}
})
)
this.openLayerController({layerName: randomUuid,
fileName: file.name,
min: meta.min || 0,
max: meta.max || 1,
warning: meta.warning || []})
} catch (e) {
console.error(e)
this.snackbar.open(`Error loading nifti: ${e.toString()}`, 'Dismiss', {
duration: 5000
})
}
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
openLayerController(meta: {layerName: string, fileName: string, min?: number, max?: number, warning?: any[]}) {
this.dialog.open(
this.layerCtrlTmpl,
{
data: {
layerName: meta.layerName,
filename: meta.fileName,
moreInfoFlag: false,
min: meta.min || 0,
max: meta.max || 1,
warning: meta.warning || []
},
hasBackdrop: false,
disableClose: true,
position: {
top: '0em'
},
autoFocus: false,
panelClass: [
'no-padding-dialog',
'w-100'
]
}
).afterClosed().subscribe(
() => this.dismissAllAddedLayers()
)
}