From 1963c49f256ab0fa70ccf13545120c5d138ae969 Mon Sep 17 00:00:00 2001 From: Xiao Gui <xgui3783@gmail.com> Date: Fri, 30 Jul 2021 16:21:25 +0200 Subject: [PATCH] hotfix: pli volume --- deploy/bkwdCompat/urlState.js | 4 +- src/glue.ts | 36 +++++++++-- src/main.module.ts | 8 ++- .../layerbrowser.component.ts | 12 +++- src/viewerModule/module.ts | 2 + .../layerCtrl.service/layerCtrl.service.ts | 40 ++++++++++-- .../viewerCmp/viewerCmp.component.ts | 28 ++++++++- .../viewerCmp/viewerCmp.template.html | 62 ++++++++++++++++++- 8 files changed, 174 insertions(+), 18 deletions(-) diff --git a/deploy/bkwdCompat/urlState.js b/deploy/bkwdCompat/urlState.js index f902a8a9f..3c41eb3c2 100644 --- a/deploy/bkwdCompat/urlState.js +++ b/deploy/bkwdCompat/urlState.js @@ -202,8 +202,8 @@ module.exports = (query, _warningCb) => { // ignore region selected and move on } } - - let redirectUrl = '/#' + const HOST_PATHNAME = process.env.HOST_PATHNAME || '' + let redirectUrl = `${HOST_PATHNAME}/#` if (standaloneVolumes) { searchParam.set('standaloneVolumes', standaloneVolumes) if (nav) redirectUrl += nav diff --git a/src/glue.ts b/src/glue.ts index ea07500c1..9a8373c4d 100644 --- a/src/glue.ts +++ b/src/glue.ts @@ -11,7 +11,6 @@ import { HttpClient } from "@angular/common/http" import { DS_PREVIEW_URL } from 'src/util/constants' import { ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer } from "./services/state/ngViewerState.store.helper" import { ARIA_LABELS } from 'common/constants' -import { NgLayersService } from "src/ui/layerbrowser/ngLayerService.service" import { Effect } from "@ngrx/effects" import { viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector, viewerStateSelectedParcellationSelector } from "./services/state/viewerState/selectors" import { ngViewerActionClearView } from './services/state/ngViewerState/actions' @@ -245,6 +244,13 @@ export class DatasetPreviewGlue implements IDatasetPreviewGlue, OnDestroy{ shareReplay(1), ) + public _volumePreview$ = this.previewingDatasetFiles$.pipe( + switchMap(arr => arr.length > 0 + ? forkJoin(arr.map(v => this.getDatasetPreviewFromId(v))) + : of([])), + map(arr => arr.filter(v => determinePreviewFileType(v) === EnumPreviewFileTypes.VOLUMES)) + ) + private diffPreviewingDatasetFiles$= this.previewingDatasetFiles$.pipe( debounceTime(100), startWith([] as IDatasetPreviewData[]), @@ -319,7 +325,6 @@ export class DatasetPreviewGlue implements IDatasetPreviewGlue, OnDestroy{ constructor( private store$: Store<any>, private http: HttpClient, - private layersService: NgLayersService, @Optional() @Inject(ACTION_TO_WIDGET_TOKEN) private actionOnWidget: TypeActionToWidget<any> ){ if (!this.actionOnWidget) console.warn(`actionOnWidget not provided in DatasetPreviewGlue. Did you forget to provide it?`) @@ -351,7 +356,6 @@ export class DatasetPreviewGlue implements IDatasetPreviewGlue, OnDestroy{ distinctUntilChanged(), )) ).subscribe(([ { prvToShow, prvToDismiss }, templateSelected ]) => { - const filterdPrvs = prvToShow.filter(prv => DatasetPreviewGlue.PreviewFileIsInCorrectSpace(prv, templateSelected)) for (const prv of filterdPrvs) { const { volumes } = prv['data']['iav-registered-volumes'] @@ -389,7 +393,6 @@ export class DatasetPreviewGlue implements IDatasetPreviewGlue, OnDestroy{ private openDatasetPreviewWidget(data: IDatasetPreviewData) { const { datasetId: kgId, filename } = data - if (!!this.actionOnWidget) { const previewId = DatasetPreviewGlue.GetDatasetPreviewId(data) @@ -543,3 +546,28 @@ export class ClickInterceptorService extends RegDeregController<any, boolean>{ // called when the call has not been intercepted } } + +export type _TPLIVal = { + name: string + filename: string + datasetSchema: string + datasetId: string + data: { + 'iav-registered-volumes': { + volumes: { + name: string + source: string + shader: string + transform: any + opacity: string + }[] + } + } + referenceSpaces: { + name: string + fullId: string + }[] + mimetype: 'application/json' +} + +export const _PLI_VOLUME_INJ_TOKEN = new InjectionToken<Observable<_TPLIVal[]>>('_PLI_VOLUME_INJ_TOKEN') diff --git a/src/main.module.ts b/src/main.module.ts index cc25b0115..9c22d1ba6 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -49,7 +49,7 @@ import 'hammerjs' import 'src/res/css/extra_styles.css' import 'src/res/css/version.css' import 'src/theme.scss' -import { DatasetPreviewGlue, datasetPreviewMetaReducer, IDatasetPreviewGlue, GlueEffects, ClickInterceptorService } from './glue'; +import { DatasetPreviewGlue, datasetPreviewMetaReducer, IDatasetPreviewGlue, GlueEffects, ClickInterceptorService, _PLI_VOLUME_INJ_TOKEN } from './glue'; import { viewerStateHelperReducer, viewerStateMetaReducers, ViewerStateHelperEffect } from './services/state/viewerState.store.helper'; import { TOS_OBS_INJECTION_TOKEN } from './ui/kgtos'; import { UiEffects } from './services/state/uiState/ui.effects'; @@ -198,7 +198,11 @@ export function debug(reducer: ActionReducer<any>): ActionReducer<any> { }, deps: [ UIService ] }, - + { + provide: _PLI_VOLUME_INJ_TOKEN, + useFactory: (glue: DatasetPreviewGlue) => glue._volumePreview$, + deps: [ DatasetPreviewGlue ] + }, { provide: IAV_DATASET_PREVIEW_ACTIVE, useFactory: (glue: DatasetPreviewGlue) => glue.datasetPreviewDisplayed.bind(glue), diff --git a/src/ui/layerbrowser/layerBrowserComponent/layerbrowser.component.ts b/src/ui/layerbrowser/layerBrowserComponent/layerbrowser.component.ts index 72a0687e3..043e0005a 100644 --- a/src/ui/layerbrowser/layerBrowserComponent/layerbrowser.component.ts +++ b/src/ui/layerbrowser/layerBrowserComponent/layerbrowser.component.ts @@ -13,6 +13,16 @@ import { ARIA_LABELS } from 'common/constants' import { INgLayerInterface } from "../index"; +const SHOW_LAYER_NAMES = [ + 'PLI Fiber Orientation Red Channel', + 'PLI Fiber Orientation Green Channel', + 'PLI Fiber Orientation Blue Channel', + 'Blockface Image', + 'PLI Transmittance', + 'T2w MRI', + 'MRI Labels' +] + @Component({ selector : 'layer-browser', templateUrl : './layerbrowser.template.html', @@ -98,7 +108,7 @@ export class LayerBrowser implements OnInit, OnDestroy { ).pipe( map(([baseNgLayerNames, loadedNgLayers]) => { const baseNameSet = new Set(baseNgLayerNames) - return loadedNgLayers.filter(l => !baseNameSet.has(l.name)) + return loadedNgLayers.filter(l => SHOW_LAYER_NAMES.includes(l.name)) }), distinctUntilChanged() ) diff --git a/src/viewerModule/module.ts b/src/viewerModule/module.ts index 7abfbb1be..25ea0facf 100644 --- a/src/viewerModule/module.ts +++ b/src/viewerModule/module.ts @@ -25,6 +25,7 @@ import { map } from "rxjs/operators"; import { TContextArg } from "./viewer.interface"; import { ViewerStateBreadCrumbModule } from "./viewerStateBreadCrumb/module"; import { KgRegionalFeatureModule } from "src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature"; +import { LayerBrowserModule } from "src/ui/layerbrowser"; @NgModule({ imports: [ @@ -47,6 +48,7 @@ import { KgRegionalFeatureModule } from "src/atlasComponents/regionalFeatures/bs ContextMenuModule, ViewerStateBreadCrumbModule, KgRegionalFeatureModule, + LayerBrowserModule, ], declarations: [ ViewerCmp, diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts index 56e87c065..43b6d922a 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts @@ -1,7 +1,7 @@ import { Inject, Injectable, OnDestroy, Optional } from "@angular/core"; import { select, Store } from "@ngrx/store"; -import { BehaviorSubject, combineLatest, from, merge, Observable, of, Subject, Subscription } from "rxjs"; -import { debounceTime, distinctUntilChanged, filter, map, shareReplay, switchMap, withLatestFrom } from "rxjs/operators"; +import { BehaviorSubject, combineLatest, from, merge, NEVER, Observable, of, Subject, Subscription } from "rxjs"; +import { debounceTime, distinctUntilChanged, filter, map, shareReplay, startWith, switchMap, withLatestFrom } from "rxjs/operators"; import { viewerStateCustomLandmarkSelector, viewerStateSelectedParcellationSelector, viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector } from "src/services/state/viewerState/selectors"; import { getRgb, IColorMap, INgLayerCtrl, INgLayerInterface, TNgLayerCtrl } from "./layerCtrl.util"; import { getMultiNgIdsRegionsLabelIndexMap } from "../constants"; @@ -12,6 +12,7 @@ import { EnumColorMapName } from "src/util/colorMaps"; import { getShader, PMAP_DEFAULT_CONFIG } from "src/util/constants"; import { ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer, ngViewerSelectorClearView, ngViewerSelectorLayers } from "src/services/state/ngViewerState.store.helper"; import { serialiseParcellationRegion } from 'common/util' +import { _PLI_VOLUME_INJ_TOKEN, _TPLIVal } from "src/glue"; export const BACKUP_COLOR = { red: 255, @@ -36,7 +37,9 @@ export function getAuxMeshesAndReturnIColor(auxMeshes: IAuxMesh[]): IColorMap{ return returnVal } -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class NehubaLayerControlService implements OnDestroy{ static PMAP_LAYER_NAME = 'regional-pmap' @@ -144,10 +147,25 @@ export class NehubaLayerControlService implements OnDestroy{ while (this.sub.length > 0) this.sub.pop().unsubscribe() } + private pliVol$: Observable<string[]> = this._pliVol$ + ? this._pliVol$.pipe( + map(arr => { + const output = [] + for (const item of arr) { + for (const volume of item.data["iav-registered-volumes"].volumes) { + output.push(volume.name) + } + } + return output + }) + ) + : NEVER constructor( private store$: Store<any>, + @Optional() @Inject(_PLI_VOLUME_INJ_TOKEN) private _pliVol$: Observable<_TPLIVal[]>, @Optional() @Inject(REGION_OF_INTEREST) roi$: Observable<TRegionDetail> ){ + if (roi$) { this.sub.push( @@ -285,10 +303,10 @@ export class NehubaLayerControlService implements OnDestroy{ shareReplay(1) ) - public visibleLayer$: Observable<string[]> = combineLatest([ + public expectedLayerNames$ = combineLatest([ this.selectedTemplateSelector$, this.auxMeshes$, - this.selParcNgIdMap$ + this.selParcNgIdMap$, ]).pipe( map(([ tmpl, auxMeshes, parcNgIdMap ]) => { const ngIdSet = new Set<string>() @@ -305,6 +323,18 @@ export class NehubaLayerControlService implements OnDestroy{ }) ) + public visibleLayer$: Observable<string[]> = combineLatest([ + this.expectedLayerNames$, + this.pliVol$.pipe( + startWith([]) + ), + ]).pipe( + map(([ expectedLayerNames, layerNames ]) => { + const ngIdSet = new Set<string>([...layerNames, ...expectedLayerNames]) + return Array.from(ngIdSet) + }) + ) + /** * define when shown segments should be updated */ diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts index f0eb2af8c..1ffe2929e 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.component.ts +++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts @@ -1,6 +1,6 @@ import { Component, ComponentFactory, ComponentFactoryResolver, ElementRef, Inject, Injector, Input, OnDestroy, Optional, TemplateRef, ViewChild, ViewContainerRef } from "@angular/core"; import { select, Store } from "@ngrx/store"; -import {combineLatest, merge, Observable, of, Subject, Subscription} from "rxjs"; +import {combineLatest, merge, NEVER, Observable, of, Subject, Subscription} from "rxjs"; import {catchError, distinctUntilChanged, filter, map, shareReplay, startWith, switchMap } from "rxjs/operators"; import { viewerStateSetSelectedRegions } from "src/services/state/viewerState/actions"; import { @@ -23,6 +23,8 @@ import { ContextMenuService, TContextMenuReg } from "src/contextMenuModule"; import { ComponentStore } from "../componentStore"; import { MAT_DIALOG_DATA } from "@angular/material/dialog"; import { GenericInfoCmp } from "src/atlasComponents/regionalFeatures/bsFeatures/genericInfo"; +import { _PLI_VOLUME_INJ_TOKEN, _TPLIVal } from "src/glue"; +import { uiActionSetPreviewingDatasetFiles } from "src/services/state/uiState.store.helper"; type TCStoreViewerCmp = { overlaySideNav: any @@ -115,7 +117,10 @@ export function ROIFactory(store: Store<any>, svc: PureContantService){ }) export class ViewerCmp implements OnDestroy { - + public _pliTitle = "Fiber structures of a human hippocampus based on joint DMRI, 3D-PLI, and TPFM acquisitions" + public _pliDesc = "The collected datasets provide real multimodal, multiscale structural connectivity insights into the human hippocampus. One post mortem hippocampus was scanned with Anatomical and Diffusion MRI (dMRI) [1], 3D Polarized Light Imaging (3D-PLI) [2], and Two-Photon Fluorescence Microscopy (TPFM) [3] using protocols specifically developed during SGA1 and SGA2, rendering joint tissue imaging possible. MRI scanning was performed with a 11.7 T Preclinical MRI system (gradients: 760 mT/m, slew rate: 9500 T/m/s) yielding T1-w and T2-w maps at 200 µm and dMRI-based maps at 300 µm resolution. During tissue sectioning (60 µm thickness) blockface (en-face) images were acquired from the surface of the frozen brain block, serving as reference for data integration/co-alignment. 530 brain sections were scanned with 3D-PLI. HPC-based image analysis provided transmittance, retardation, and fiber orientation maps at 1.3 µm in-plane resolution. TPFM was finally applied to selected brain sections utilizing autofluorescence properties of the fibrous tissue which appears after PBS washing (MAGIC protocol). The TPFM measurements provide a resolution of 0.44 µm x 0.44 µm x 1 µm." + public _pliLink = "https://doi.org/10.25493/JQ30-E08" + public CONST = CONST public ARIA_LABELS = ARIA_LABELS @@ -215,17 +220,36 @@ export class ViewerCmp implements OnDestroy { private getRegionFromlabelIndexId: Function private genericInfoCF: ComponentFactory<GenericInfoCmp> + + public pliVol$ = this._pliVol$ || NEVER + public clearVoi(){ + this.store$.dispatch( + uiActionSetPreviewingDatasetFiles({ + previewingDatasetFiles: [] + }) + ) + } constructor( private store$: Store<any>, private viewerModuleSvc: ContextMenuService<TContextArg<'threeSurfer' | 'nehuba'>>, private cStore: ComponentStore<TCStoreViewerCmp>, cfr: ComponentFactoryResolver, + @Optional() @Inject(_PLI_VOLUME_INJ_TOKEN) private _pliVol$: Observable<_TPLIVal[]>, @Optional() @Inject(REGION_OF_INTEREST) public regionOfInterest$: Observable<any> ){ this.genericInfoCF = cfr.resolveComponentFactory(GenericInfoCmp) this.subscriptions.push( + this.pliVol$.subscribe(val => { + if (val.length > 0) { + this.sidenavTopSwitch && this.sidenavTopSwitch.open() + this.sidenavLeftSwitch && this.sidenavLeftSwitch.open() + } else { + this.sidenavTopSwitch && this.sidenavTopSwitch.close() + this.sidenavLeftSwitch && this.sidenavLeftSwitch.close() + } + }), this.selectedRegions$.subscribe(() => { this.clearPreviewingDataset() }), diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html index aa2004d86..4b5a2e90e 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.template.html +++ b/src/viewerModule/viewerCmp/viewerCmp.template.html @@ -213,9 +213,14 @@ 'invisible overflow-hidden h-0': overlaySidenav$ | async, 'h-100': !(overlaySidenav$ | async) }" class="position-relative d-flex flex-column"> + + <ng-template let-pliVol [ngIf]="pliVol$ | async" [ngIfElse]="sidenavRegionTmpl"> + <ng-template [ngIf]="pliVol.length > 0" [ngIfElse]="sidenavRegionTmpl"> + <ng-template [ngTemplateOutlet]="voiTmpl"> - <ng-container *ngTemplateOutlet="sidenavRegionTmpl"> - </ng-container> + </ng-template> + </ng-template> + </ng-template> <!-- TODO dataset preview will become deprecated in the future. Regional feature/data feature will replace it --> @@ -405,6 +410,59 @@ </button> </ng-template> +<!-- VOI sidenav tmpl --> +<ng-template #voiTmpl> + + <!-- back btn --> + <button mat-button + (click)="clearVoi()" + [attr.aria-label]="ARIA_LABELS.CLOSE" + class="position-absolute z-index-10 m-2"> + <i class="fas fa-chevron-left"></i> + <span class="ml-1"> + Back + </span> + </button> + + <mat-card class="sidenav-cover-header-container"> + <div class="sidenav-cover-header-container"> + <mat-card-title> + {{ _pliTitle }} + </mat-card-title> + + <mat-card-subtitle class="d-inline-flex align-items-center flex-wrap"> + <mat-icon fontSet="fas" fontIcon="fa-database"></mat-icon> + <span> + Dataset preview + </span> + + <mat-divider vertical="true" class="ml-2 h-2rem"></mat-divider> + + <a [href]="_pliLink" + mat-icon-button + matTooltip="Explore in EBRAINS Knowledge Graph" + target="_blank"> + <i class="fas fa-external-link-alt"></i> + </a> + + </mat-card-subtitle> + </div> + + <small class="text-muted iv-custom-comp darker-bg"> + {{ _pliDesc }} + </small> + + <mat-expansion-panel class="sidenav-cover-header-container"> + <mat-expansion-panel-header> + <mat-panel-title> + Registered Volumes + </mat-panel-title> + </mat-expansion-panel-header> + <layer-browser></layer-browser> + </mat-expansion-panel> + </mat-card> +</ng-template> + <!-- region sidenav tmpl --> <ng-template #sidenavRegionTmpl> -- GitLab