diff --git a/src/api/service.ts b/src/api/service.ts index 9e68398251d25590c838ae15f65af7205ffc4cd1..49b22c9ec2dc5e59648dca26fcd13e21716cd490 100644 --- a/src/api/service.ts +++ b/src/api/service.ts @@ -14,7 +14,7 @@ export type NAMESPACE_TYPE = "sxplr" export const namespace: NAMESPACE_TYPE = "sxplr" const nameSpaceRegex = new RegExp(`^${namespace}`) -type AddableLayer = atlasAppearance.NgLayerCustomLayer +type AddableLayer = atlasAppearance.const.NgLayerCustomLayer type AtId = { "@id": string diff --git a/src/atlasComponents/sapi/sapi.service.ts b/src/atlasComponents/sapi/sapi.service.ts index fb51ddd60f159234296d393e7f0bf65fdc209d5a..43eb63b63bcfccc3e6c453422694a7656fb3a088 100644 --- a/src/atlasComponents/sapi/sapi.service.ts +++ b/src/atlasComponents/sapi/sapi.service.ts @@ -210,6 +210,14 @@ export class SAPI{ ...sapiParam }) } + + getV3FeatureDetailWithId(id: string) { + return this.v3Get("/feature/{feature_id}", { + path: { + feature_id: id + } + }) + } getFeature(featureId: string, opts: Record<string, string> = {}) { return new SAPIFeature(this, featureId, opts) @@ -366,7 +374,7 @@ export class SAPI{ const arraybuffer = await resp.arrayBuffer() let outbuf: ArrayBuffer try { - outbuf = getExportNehuba().pako.inflate(arraybuffer).buffer + outbuf = (await getExportNehuba()).pako.inflate(arraybuffer).buffer } catch (e) { console.log("unpack error", e) outbuf = arraybuffer @@ -550,7 +558,7 @@ export class SAPI{ try { const bin = atob(content) - const { pako } = getExportNehuba() + const { pako } = await getExportNehuba() const array = pako.inflate(bin) let workerMsg: string switch (method) { diff --git a/src/atlasComponents/sapi/translateV3.ts b/src/atlasComponents/sapi/translateV3.ts index 4134af67f94b6ca59e8d72bbf4ab94ac76d527cd..63390575627568844ac47cd019858510d34a3c7d 100644 --- a/src/atlasComponents/sapi/translateV3.ts +++ b/src/atlasComponents/sapi/translateV3.ts @@ -100,7 +100,6 @@ class TranslateV3 { } const [transform, info] = await Promise.all([ (async () => { - const resp = await fetch(`${precomputedVol}/transform.json`) if (resp.status >= 400) { console.error(`cannot retrieve transform: ${resp.status}`) @@ -195,7 +194,7 @@ class TranslateV3 { for (const { volume: volIdx, fragment, label } of map.indices[regionname]) { const volume = map.volumes[volIdx || 0] if (!volume.formats.includes("gii-label")) { - console.warn(`getting three label error! volume does not provide gii-label! skipping!`) + // Does not support gii-label... skipping! continue } const { ["gii-label"]: giiLabel } = volume.providedVolumes @@ -206,7 +205,7 @@ class TranslateV3 { continue } if (!giiLabel[fragment]) { - console.warn(`fragment '${fragment}' not provided by volume.gii-label! skipping!`) + // Does not support gii-label... skipping! continue } let laterality: 'left' | 'right' @@ -222,7 +221,11 @@ class TranslateV3 { return threeLabelMap } + #wkmpLblMapToNgSegLayers = new WeakMap() async translateLabelledMapToNgSegLayers(map:PathReturn<"/map">): Promise<Record<string,{layer:NgSegLayerSpec, region: LabelledMap[]}>> { + if (this.#wkmpLblMapToNgSegLayers.has(map)) { + return this.#wkmpLblMapToNgSegLayers.get(map) + } const nglayerSpecMap: Record<string,{layer:NgSegLayerSpec, region: LabelledMap[]}> = {} const registerLayer = async (url: string, label: number, region: LabelledMap) => { @@ -259,7 +262,8 @@ class TranslateV3 { const volume = map.volumes[volumeIdx] if (!volume.providedVolumes["neuroglancer/precomputed"]) { - console.error(`${error}, volume does not provide neuroglancer/precomputed. Skipping.`) + // volume does not provide neuroglancer/precomputed + // probably when fsaverage has been selected continue } @@ -276,7 +280,7 @@ class TranslateV3 { await registerLayer(precomputedVol[fragment], label, { name: regionname, label }) } } - + this.#wkmpLblMapToNgSegLayers.set(map, nglayerSpecMap) return nglayerSpecMap } diff --git a/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.component.ts b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.component.ts index b9212a9079ca3811050e0705fc8d2e5d3a73394b..5ad6847dcf3ea4d1156329aa63c54063dfc42a3a 100644 --- a/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.component.ts +++ b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.component.ts @@ -3,7 +3,6 @@ import { UntypedFormControl } from "@angular/forms"; import { Subscription } from "rxjs"; import { debounceTime, distinctUntilChanged, filter, startWith } from "rxjs/operators"; import { SxplrRegion } from "src/atlasComponents/sapi/sxplrTypes"; -import { translateV3Entities } from "src/atlasComponents/sapi/translateV3" import { SxplrFlatHierarchyTreeView } from "src/components/flatHierarchy/treeView/treeView.component"; import { FilterByRegexPipe } from "./filterByRegex.pipe"; import { RegionTreeFilterPipe } from "./regionTreeFilter.pipe"; @@ -23,13 +22,7 @@ const filterByRegexPipe = new FilterByRegexPipe() export class SapiViewsCoreRichRegionsHierarchy { static IsParent(region: SxplrRegion, parentRegion: SxplrRegion): boolean { - const _region = translateV3Entities.retrieveRegion(region) - const _parentRegion = translateV3Entities.retrieveRegion(parentRegion) - const { ["@id"]: parentRegionId } = _parentRegion - return _region.hasParent?.some(parent => { - const { ["@id"]: pId } = parent - return pId === parentRegionId - }) + return region.parentIds.some(id => parentRegion.id === id) } static FilterRegions(regions: SxplrRegion[], searchTerm: string): SxplrRegion[]{ diff --git a/src/atlasComponents/sapiViews/features/feature-view/feature-view.component.scss b/src/atlasComponents/sapiViews/features/feature-view/feature-view.component.scss index e39c0ecb5869b91204fd4782600e6686f8581cee..733e12f491703e2a25badbdc40f9b3cf89526482 100644 --- a/src/atlasComponents/sapiViews/features/feature-view/feature-view.component.scss +++ b/src/atlasComponents/sapiViews/features/feature-view/feature-view.component.scss @@ -9,3 +9,9 @@ { width: 100%; } + +spinner-cmp +{ + margin: auto; + margin-left: 0.5rem; +} diff --git a/src/atlasComponents/sapiViews/features/feature-view/feature-view.component.ts b/src/atlasComponents/sapiViews/features/feature-view/feature-view.component.ts index 903ec3ffef6a55045266ce0efea3c610fd5d1630..59dda8f6107c4e2dadf173fc0bbf43b95857a72a 100644 --- a/src/atlasComponents/sapiViews/features/feature-view/feature-view.component.ts +++ b/src/atlasComponents/sapiViews/features/feature-view/feature-view.component.ts @@ -30,7 +30,6 @@ export class FeatureViewComponent implements OnChanges { constructor(private sapi: SAPI) { } ngOnChanges(): void { - console.log(this.feature) this.tabular$.next(null) this.busy$.next(true) this.sapi.v3Get("/feature/{feature_id}", { diff --git a/src/atlasComponents/userAnnotations/tools/service.ts b/src/atlasComponents/userAnnotations/tools/service.ts index 5b35420884dd340e0e524bab253e7876aa1cb712..166efee58d09603f68f710bf2576b11c13943c5e 100644 --- a/src/atlasComponents/userAnnotations/tools/service.ts +++ b/src/atlasComponents/userAnnotations/tools/service.ts @@ -12,7 +12,6 @@ import { Polygon } from "./poly"; import { Line } from "./line"; import { Point } from "./point"; import { FilterAnnotationsBySpace } from "../filterAnnotationBySpace.pipe"; -import { retry } from 'common/util' import { MatSnackBar } from "@angular/material/snack-bar"; import { actions } from "src/state/atlasSelection"; import { atlasSelection } from "src/state"; @@ -535,15 +534,7 @@ export class ModularUserAnnotationToolService implements OnDestroy{ if (!encoded) return [] const bin = atob(encoded) - await retry(() => { - if (!!getExportNehuba()) return true - else throw new Error(`export nehuba not yet ready`) - }, { - timeout: 1000, - retries: 10 - }) - - const { pako } = getExportNehuba() + const { pako } = await getExportNehuba() const decoded = pako.inflate(bin, { to: 'string' }) const arr = JSON.parse(decoded) const anns: IAnnotationGeometry[] = [] @@ -572,14 +563,14 @@ export class ModularUserAnnotationToolService implements OnDestroy{ */ private metadataMap = new Map<string, TAnnotationMetadata>() - private storeAnnotation(anns: IAnnotationGeometry[]){ + private async storeAnnotation(anns: IAnnotationGeometry[]){ const arr = [] for (const ann of anns) { const json = ann.toJSON() arr.push(json) } const stringifiedJSON = JSON.stringify(arr) - const exportNehuba = getExportNehuba() + const exportNehuba = await getExportNehuba() if (!exportNehuba) return const { pako } = exportNehuba const compressed = pako.deflate(stringifiedJSON) diff --git a/src/features/entry/entry.component.html b/src/features/entry/entry.component.html index 97802ff5f431881e22e9e20ba78169cd48db088f..c7fb2d5dc679952e9d9cf7714e24d7119539b7dd 100644 --- a/src/features/entry/entry.component.html +++ b/src/features/entry/entry.component.html @@ -15,9 +15,11 @@ <mat-panel-description> <spinner-cmp *ngIf="categoryAcc.isBusy$ | async"></spinner-cmp> - <span> - {{ categoryAcc.total$ | async }} - </span> + <ng-template [ngIf]="categoryAcc.total$ | async" let-total> + <span> + {{ total }} + </span> + </ng-template> </mat-panel-description> </mat-expansion-panel-header> @@ -36,6 +38,7 @@ </mat-card-title> </mat-card-header> <mat-card-content> + <spinner-cmp *ngIf="(list.state$ | async) === 'busy'"></spinner-cmp> <sxplr-feature-list [template]="template" [parcellation]="parcellation" @@ -45,8 +48,7 @@ (onClickFeature)="onClickFeature($event)" #list="featureList" > - </sxplr-feature-list> - <spinner-cmp *ngIf="(list.state$ | async) === 'busy'"></spinner-cmp> + </sxplr-feature-list> </mat-card-content> </mat-card> </div> diff --git a/src/features/list/list.component.html b/src/features/list/list.component.html index c50515f2580ecaa0d415a5e5e9b14fb7639aae3b..28d0b7dabbee811aecda8eda61e2ec33abf58866 100644 --- a/src/features/list/list.component.html +++ b/src/features/list/list.component.html @@ -1,11 +1,11 @@ -<mat-list> - <mat-list-item mat-ripple - *ngFor="let feature of features$ | async" +<cdk-virtual-scroll-viewport itemSize="36" + class="virtual-scroll-viewport"> + <button *cdkVirtualFor="let feature of features$ | async" + mat-button + class="virtual-scroll-item sxplr-w-100" [matTooltip]="feature.name" matTooltipPosition="right" (click)="onClickItem(feature)"> - <span class="feature-name"> - {{ feature.name }} - </span> - </mat-list-item> -</mat-list> + {{ feature.name }} + </button> +</cdk-virtual-scroll-viewport> diff --git a/src/features/list/list.component.scss b/src/features/list/list.component.scss index 99448693600d945c9af3622013e7f196f900d221..97873a44ba16b4cd541b11a786a29cf1fc979125 100644 --- a/src/features/list/list.component.scss +++ b/src/features/list/list.component.scss @@ -2,6 +2,7 @@ { display: block; width: 100%; + height:100%; } .feature-name @@ -13,3 +14,13 @@ { cursor: default; } + +.virtual-scroll-viewport +{ + height: 100%; +} + +.virtual-scroll-item +{ + height:36px; +} diff --git a/src/features/module.ts b/src/features/module.ts index 7de3d6ea36a0404f869ca7a8a0b8663815b1e750..958956bea943a9f27ebc2c8c0fc07d76661ce30a 100644 --- a/src/features/module.ts +++ b/src/features/module.ts @@ -13,6 +13,8 @@ import { FetchDirective } from "./fetch.directive"; import { ListComponent } from './list/list.component'; import { CategoryAccDirective } from './category-acc.directive'; import { SapiViewsFeatureConnectivityModule } from "./connectivity"; +import { ScrollingModule } from "@angular/cdk/scrolling"; +import { MatButtonModule } from "@angular/material/button" @NgModule({ imports: [ @@ -24,7 +26,9 @@ import { SapiViewsFeatureConnectivityModule } from "./connectivity"; MatTooltipModule, UtilModule, MatRippleModule, - SapiViewsFeatureConnectivityModule + SapiViewsFeatureConnectivityModule, + ScrollingModule, + MatButtonModule, ], declarations: [ EntryComponent, diff --git a/src/main.module.ts b/src/main.module.ts index b681764f400aa4f8fabea3a4544a3ca03b64dc9a..a0ddf6d025324c91da5836c9b9412c5f9541e04f 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -50,6 +50,7 @@ import { EffectsModule } from '@ngrx/effects'; import { LayerCtrlEffects } from './viewerModule/nehuba/layerCtrl.service/layerCtrl.effects'; import { NehubaNavigationEffects } from './viewerModule/nehuba/navigation.service/navigation.effects'; import { CONST } from "common/constants" +import { ViewerCommonEffects } from './viewerModule'; @NgModule({ imports: [ @@ -77,6 +78,7 @@ import { CONST } from "common/constants" ...getStoreEffects(), LayerCtrlEffects, NehubaNavigationEffects, + ViewerCommonEffects, ]), RootStoreModule, HttpClientModule, diff --git a/src/messaging/nmvSwc/index.ts b/src/messaging/nmvSwc/index.ts index 2f4e7586970acd9450a6456fb9fe6e06ba7ddea9..6dc9abb482da80c3ffaf76c2478211587de31255 100644 --- a/src/messaging/nmvSwc/index.ts +++ b/src/messaging/nmvSwc/index.ts @@ -5,15 +5,6 @@ import { INmvTransform } from "./type" export const TYPE = 'bas:datasource' -const waitFor = (condition: (...arg: any[]) => boolean) => new Promise<void>((rs, rj) => { - const intervalRef = setInterval(() => { - if (condition()) { - clearInterval(intervalRef) - rs() - } - }, 1000) -}) - const NM_IDS = { AMBA_V3: 'hbp:Allen_Mouse_CCF_v3(um)', WAXHOLM_V1_01: 'hbp:WHS_SD_Rat_v1.01(um)', @@ -110,16 +101,15 @@ export const processJsonLd = (json: { [key: string]: any }): Observable<IMessagi } }) - await waitFor(() => !!getExportNehuba()) - const b64Encoded = encoding.indexOf('base64') >= 0 const isGzipped = encoding.indexOf('gzip') >= 0 let data = rawData if (b64Encoded) { data = atob(data) } + const { pako, mat3, vec3 } = await getExportNehuba() if (isGzipped) { - data = getExportNehuba().pako.inflate(data) + data = pako.inflate(data) } let output = `` for (let i = 0; i < data.length; i++) { @@ -146,7 +136,6 @@ export const processJsonLd = (json: { [key: string]: any }): Observable<IMessagi ] // NG translation works on nm scale const scaleUmToNm = 1e3 - const { mat3, vec3 } = getExportNehuba() const modA = mat3.fromValues( scaleUmToVoxelFixed[0], 0, 0, 0, scaleUmToVoxelFixed[1], 0, diff --git a/src/overwrite.scss b/src/overwrite.scss index a60e2755623bed99f769435701c0798d47ce927d..42cdf515c7483f04addad9c19d02ded67e9aaf6e 100644 --- a/src/overwrite.scss +++ b/src/overwrite.scss @@ -289,3 +289,8 @@ a[mat-raised-button] { pointer-events: none!important; } + +.virtual-scroll-viewport > .cdk-virtual-scroll-content-wrapper +{ + width: 100%; +} diff --git a/src/routerModule/routeStateTransform.service.ts b/src/routerModule/routeStateTransform.service.ts index 8a6fe4d335e622fc62672716790827f3cb9ba2ba..86cad2fd1291c9aaff05285005e1319c605ef311 100644 --- a/src/routerModule/routeStateTransform.service.ts +++ b/src/routerModule/routeStateTransform.service.ts @@ -228,7 +228,7 @@ export class RouteStateTransformSvc { try { if (returnObj.f && returnObj.f.length === 1) { const decodedFeatId = decodeId(returnObj.f[0]) - const feature = await this.sapi.getFeature(decodedFeatId).detail$.toPromise() + const feature = await this.sapi.getV3FeatureDetailWithId(decodedFeatId).toPromise() returnState["[state.userInteraction]"].selectedFeature = feature } } catch (e) { diff --git a/src/state/atlasAppearance/action.ts b/src/state/atlasAppearance/action.ts index 6a4ce26e148d801c726ba23c3eee906e736aa510..9cdf1fcf4888766f314fd1a411435741d473c1f6 100644 --- a/src/state/atlasAppearance/action.ts +++ b/src/state/atlasAppearance/action.ts @@ -1,5 +1,5 @@ import { createAction, props } from "@ngrx/store"; -import { CustomLayer, nameSpace } from "./const" +import { CustomLayer, nameSpace, UseViewer } from "./const" export const setOctantRemoval = createAction( `${nameSpace} setOctantRemoval`, @@ -28,3 +28,10 @@ export const removeCustomLayer = createAction( id: string }>() ) + +export const setUseViewer = createAction( + `${nameSpace} useViewer`, + props<{ + viewer: UseViewer + }>() +) diff --git a/src/state/atlasAppearance/const.ts b/src/state/atlasAppearance/const.ts index 910acab7a8607b74e5b1a4c5d9d06491145fabe2..41c9ebd7216465719aab792025b8ad2b5b268f6d 100644 --- a/src/state/atlasAppearance/const.ts +++ b/src/state/atlasAppearance/const.ts @@ -50,3 +50,11 @@ export type NgLayerCustomLayer = { * - id allows custom layer to be removed, if necessary */ export type CustomLayer = ColorMapCustomLayer | NgLayerCustomLayer | ThreeSurferCustomLayer | ThreeSurferCustomLabelLayer + +export const useViewer = { + THREESURFER: "THREESURFER", + NEHUBA: "NEHUBA", + NOT_SUPPORTED: "NOT_SUPPORTED" +} as const + +export type UseViewer = keyof typeof useViewer diff --git a/src/state/atlasAppearance/index.ts b/src/state/atlasAppearance/index.ts index 739f036f65baf71930eff7ae9fd37067cfb4e012..5a133c488c65470459968c71d918e1bce322a0a0 100644 --- a/src/state/atlasAppearance/index.ts +++ b/src/state/atlasAppearance/index.ts @@ -1,4 +1,5 @@ export * as actions from "./action" export * as selectors from "./selector" -export { nameSpace, ColorMapCustomLayer, CustomLayer, NgLayerCustomLayer } from "./const" +export * as const from "./const" +export { nameSpace } from "./const" export { reducer, AtlasAppearanceStore, defaultState } from "./store" diff --git a/src/state/atlasAppearance/selector.ts b/src/state/atlasAppearance/selector.ts index b7eac7bc1701c6e490a6a72ccd9cf8412d4e7aeb..05b5aface33998d9477e0cc88d851fffe24c716a 100644 --- a/src/state/atlasAppearance/selector.ts +++ b/src/state/atlasAppearance/selector.ts @@ -18,3 +18,8 @@ export const customLayers = createSelector( selectStore, state => state.customLayers ) + +export const useViewer = createSelector( + selectStore, + state => state.useViewer +) diff --git a/src/state/atlasAppearance/store.ts b/src/state/atlasAppearance/store.ts index 6c060da26b4696af0865d0f5c545fdc0d42cffd4..384655c0e3ba44d774926182ad7cc09eb05719ec 100644 --- a/src/state/atlasAppearance/store.ts +++ b/src/state/atlasAppearance/store.ts @@ -1,15 +1,16 @@ import { createReducer, on } from "@ngrx/store" import * as actions from "./action" -import { CustomLayer } from "./const" +import { UseViewer, CustomLayer } from "./const" export type AtlasAppearanceStore = { - + useViewer: UseViewer octantRemoval: boolean showDelineation: boolean customLayers: CustomLayer[] } export const defaultState: AtlasAppearanceStore = { + useViewer: null, octantRemoval: true, showDelineation: true, customLayers: [] @@ -58,5 +59,14 @@ export const reducer = createReducer( customLayers: customLayers.filter(l => l.id !== id) } } + ), + on( + actions.setUseViewer, + (state, { viewer }) => { + return { + ...state, + useViewer: viewer + } + } ) ) diff --git a/src/util/fn.ts b/src/util/fn.ts index 14e561f032012ea44cf5278e963cf84979cabe2c..271c783f68237e7cce54b4b6e5ebbcd901fe9372 100644 --- a/src/util/fn.ts +++ b/src/util/fn.ts @@ -17,8 +17,13 @@ export function getDebug() { return (window as any).__DEBUG__ } -export function getExportNehuba() { - return (window as any).export_nehuba +export async function getExportNehuba() { + while (true) { + + const nehuba = (window as any).export_nehuba + if (!!nehuba) return nehuba + await new Promise((rs) => setTimeout(rs, 160)) + } } const recursiveFlatten = (region, {ngId}) => { diff --git a/src/viewerModule/index.ts b/src/viewerModule/index.ts index 6e5c74fbe55be040c17b54d98c7b6f29cd66a513..adcd5938e479a67360e364f7740984d0c40384cd 100644 --- a/src/viewerModule/index.ts +++ b/src/viewerModule/index.ts @@ -1 +1,2 @@ -export { ViewerModule } from "./module" \ No newline at end of file +export { ViewerModule } from "./module" +export { ViewerCommonEffects } from "./viewer.common.effects" diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts index a12d8fcfd2ad87cb8c2e545f7c43e657b4fcd70c..0253e4eef876e510be04ab6effb78244c2d1adc1 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts @@ -1,27 +1,25 @@ import { Injectable } from "@angular/core"; import { createEffect } from "@ngrx/effects"; import { select, Store } from "@ngrx/store"; -import { forkJoin, from, NEVER, of, throwError } from "rxjs"; -import { mapTo, switchMap, withLatestFrom, filter, catchError, map, debounceTime, shareReplay, distinctUntilChanged, startWith, pairwise, tap } from "rxjs/operators"; -import { NgSegLayerSpec, SxplrAtlas, SxplrParcellation, SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes"; +import { forkJoin, from, of } from "rxjs"; +import { switchMap, withLatestFrom, filter, catchError, map, debounceTime, shareReplay, distinctUntilChanged, startWith, pairwise, tap } from "rxjs/operators"; +import { Feature, NgSegLayerSpec, SxplrAtlas, SxplrParcellation, SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes"; import { SAPI } from "src/atlasComponents/sapi" import { SapiFeatureModel, SapiSpatialFeatureModel, } from "src/atlasComponents/sapi/typeV3"; -import { translateV3Entities } from "src/atlasComponents/sapi/translateV3" import { atlasAppearance, atlasSelection, userInteraction } from "src/state"; import { arrayEqual } from "src/util/array"; import { EnumColorMapName } from "src/util/colorMaps"; import { getShader } from "src/util/constants"; import { PMAP_LAYER_NAME } from "../constants"; import { QuickHash } from "src/util/fn"; -import { BaseService } from "../base.service/base.service"; import { getParcNgId } from "../config.service"; @Injectable() export class LayerCtrlEffects { - static TransformVolumeModel(volumeModel: SapiSpatialFeatureModel['volume']): atlasAppearance.NgLayerCustomLayer[] { + static TransformVolumeModel(volumeModel: SapiSpatialFeatureModel['volume']): atlasAppearance.const.NgLayerCustomLayer[] { /** * TODO implement */ @@ -33,67 +31,76 @@ export class LayerCtrlEffects { return [] } - onRegionSelectClearPmapLayer = createEffect(() => this.store.pipe( - select(atlasSelection.selectors.selectedRegions), - distinctUntilChanged( - arrayEqual((o, n) => o.name === n.name) - ), - mapTo( - atlasAppearance.actions.removeCustomLayer({ - id: PMAP_LAYER_NAME - }) - ) - )) + #onATP$ = this.store.pipe( + atlasSelection.fromRootStore.distinctATP(), + map(val => val as { atlas: SxplrAtlas, parcellation: SxplrParcellation, template: SxplrTemplate }), + ) + #pmapUrl: string - onRegionSelectShowNewPmapLayer = createEffect(() => this.store.pipe( - select(atlasSelection.selectors.selectedRegions), - filter(regions => regions.length === 1), - map(regions => regions[0]), - distinctUntilChanged((ro, rn) => ro.name === rn.name), - withLatestFrom( - this.store.pipe( - atlasSelection.fromRootStore.distinctATP() - ) - ), - switchMap(([ region, { parcellation, template } ]) => { - return this.sapi.getStatisticalMap(parcellation, template, region).pipe( - catchError(() => NEVER), - map(({ buffer, meta }) => { - if (!!this.#pmapUrl) { - URL.revokeObjectURL(this.#pmapUrl) + #cleanupUrl(){ + if (!!this.#pmapUrl) { + URL.revokeObjectURL(this.#pmapUrl) + this.#pmapUrl = null + } + } + + onRegionSelect = createEffect(() => this.store.pipe( + select(atlasAppearance.selectors.useViewer), + switchMap(viewer => { + const rmPmapAction = atlasAppearance.actions.removeCustomLayer({ + id: PMAP_LAYER_NAME + }) + if (viewer !== "NEHUBA") { + this.#cleanupUrl() + return of(rmPmapAction) + } + return this.store.pipe( + select(atlasSelection.selectors.selectedRegions), + distinctUntilChanged( + arrayEqual((o, n) => o.name === n.name) + ), + withLatestFrom(this.#onATP$), + // since region selection changed, pmap will definitely be removed. revoke the url resource. + tap(() => this.#cleanupUrl()), + switchMap(([ regions, { parcellation, template } ]) => { + if (regions.length !== 1) { + return of(rmPmapAction) } - this.#pmapUrl = URL.createObjectURL(new Blob([buffer], {type: "application/octet-stream"})) - return atlasAppearance.actions.addCustomLayer({ - customLayer: { - clType: "customlayer/nglayer", - id: PMAP_LAYER_NAME, - source: `nifti://${this.#pmapUrl}`, - shader: getShader({ - colormap: EnumColorMapName.VIRIDIS, - highThreshold: meta.max, - lowThreshold: meta.min, - removeBg: true, - }) - } - }) + return this.sapi.getStatisticalMap(parcellation, template, regions[0]).pipe( + switchMap(({ buffer, meta }) => { + this.#pmapUrl = URL.createObjectURL(new Blob([buffer], {type: "application/octet-stream"})) + return of( + rmPmapAction, + atlasAppearance.actions.addCustomLayer({ + customLayer: { + clType: "customlayer/nglayer", + id: PMAP_LAYER_NAME, + source: `nifti://${this.#pmapUrl}`, + shader: getShader({ + colormap: EnumColorMapName.VIRIDIS, + highThreshold: meta.max, + lowThreshold: meta.min, + removeBg: true, + }) + } + }) + ) + }), + catchError(() => of(rmPmapAction)), + ) }) ) - }), + }) )) - onATP$ = this.store.pipe( - atlasSelection.fromRootStore.distinctATP(), - map(val => val as { atlas: SxplrAtlas, parcellation: SxplrParcellation, template: SxplrTemplate }), - ) - onShownFeature = createEffect(() => this.store.pipe( select(userInteraction.selectors.selectedFeature), - startWith(null as SapiFeatureModel), - pairwise<SapiFeatureModel>(), + startWith(null as Feature), + pairwise(), map(([ prev, curr ]) => { - const removeLayers: atlasAppearance.NgLayerCustomLayer[] = [] - const addLayers: atlasAppearance.NgLayerCustomLayer[] = [] + const removeLayers: atlasAppearance.const.NgLayerCustomLayer[] = [] + const addLayers: atlasAppearance.const.NgLayerCustomLayer[] = [] if (prev?.["@type"].includes("feature/volume_of_interest")) { const prevVoi = prev as SapiSpatialFeatureModel removeLayers.push( @@ -119,7 +126,7 @@ export class LayerCtrlEffects { ])) )) - onATPClearBaseLayers = createEffect(() => this.onATP$.pipe( + onATPClearBaseLayers = createEffect(() => this.#onATP$.pipe( withLatestFrom( this.store.pipe( select(atlasAppearance.selectors.customLayers), @@ -142,7 +149,7 @@ export class LayerCtrlEffects { ) )) - onATPDebounceNgLayers$ = this.onATP$.pipe( + onATPDebounceNgLayers$ = this.#onATP$.pipe( debounceTime(16), switchMap(({ atlas, template, parcellation }) => forkJoin({ @@ -190,7 +197,7 @@ export class LayerCtrlEffects { switchMap(ngLayers => { const { parcNgLayers, tmplAuxNgLayers, tmplNgLayers } = ngLayers - const customBaseLayers: atlasAppearance.NgLayerCustomLayer[] = [] + const customBaseLayers: atlasAppearance.const.NgLayerCustomLayer[] = [] for (const layers of [parcNgLayers, tmplAuxNgLayers, tmplNgLayers]) { for (const key in layers) { const { source, transform, opacity, visible } = layers[key] @@ -217,6 +224,5 @@ export class LayerCtrlEffects { constructor( private store: Store<any>, private sapi: SAPI, - private baseService: BaseService, ){} } \ No newline at end of file diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts index 3035dc2620c7d1c437e49022f14e12e94fae356e..b1c82b156dfa5888c6b828b944641c66bde8d7b6 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts @@ -1,19 +1,16 @@ import { Injectable, OnDestroy } from "@angular/core"; import { select, Store } from "@ngrx/store"; -import { combineLatest, from, merge, Observable, Subject, Subscription } from "rxjs"; +import { combineLatest, merge, Observable, Subject, Subscription } from "rxjs"; import { debounceTime, distinctUntilChanged, filter, map, pairwise, shareReplay, startWith, switchMap, withLatestFrom } from "rxjs/operators"; import { IColorMap, INgLayerCtrl, TNgLayerCtrl } from "./layerCtrl.util"; -import { getParcNgId } from "../config.service" import { annotation, atlasAppearance, atlasSelection } from "src/state"; import { serializeSegment } from "../util"; import { LayerCtrlEffects } from "./layerCtrl.effects"; import { arrayEqual } from "src/util/array"; -import { ColorMapCustomLayer } from "src/state/atlasAppearance"; import { SxplrRegion } from "src/atlasComponents/sapi/sxplrTypes"; import { AnnotationLayer } from "src/atlasComponents/annotations"; import { PMAP_LAYER_NAME } from "../constants" import { getShader } from "src/util/constants"; -import { SAPI } from "src/atlasComponents/sapi"; import { BaseService } from "../base.service/base.service"; export const BACKUP_COLOR = { @@ -55,8 +52,8 @@ export class NehubaLayerControlService implements OnDestroy{ map(([record, layers]) => { const returnVal: IColorMap = {} - const cmCustomLayers = layers.filter(l => l.clType === "customlayer/colormap") as ColorMapCustomLayer[] - const cmBaseLayers = layers.filter(l => l.clType === "baselayer/colormap") as ColorMapCustomLayer[] + const cmCustomLayers = layers.filter(l => l.clType === "customlayer/colormap") as atlasAppearance.const.ColorMapCustomLayer[] + const cmBaseLayers = layers.filter(l => l.clType === "baselayer/colormap") as atlasAppearance.const.ColorMapCustomLayer[] const useCm = (() => { /** @@ -81,7 +78,7 @@ export class NehubaLayerControlService implements OnDestroy{ for (const [ngId, labelRecord] of Object.entries(record)) { for (const [label, region] of Object.entries(labelRecord)) { if (!region.color) continue - const [ red, green, blue ] = useCm.get(region) + const [ red, green, blue ] = useCm.get(region) || [200, 200, 200] if (!returnVal[ngId]) { returnVal[ngId] = {} } @@ -210,13 +207,6 @@ export class NehubaLayerControlService implements OnDestroy{ /** * define when shown segments should be updated */ - public _segmentVis$: Observable<string[]> = combineLatest([ - this.selectedATP$, - this.selectedRegion$ - ]).pipe( - map(() => ['']) - ) - public segmentVis$: Observable<string[]> = combineLatest([ /** * selectedRegions @@ -232,9 +222,9 @@ export class NehubaLayerControlService implements OnDestroy{ map(layers => layers.filter(l => l.clType === "customlayer/nglayer").length > 0), ), ]).pipe( - withLatestFrom(this.completeNgIdLabelRegionMap$), - map(([[ selectedRegions, customMapExists, nonmixableLayerExists ], completeNgIdLabelRegion]) => { - /** + switchMap(( [ selectedRegions, customMapExists, nonmixableLayerExists ] ) => this.completeNgIdLabelRegionMap$.pipe( + map(completeNgIdLabelRegion => { + /** * if non mixable layer exist (e.g. pmap) * and no custom color map exist * hide all segmentations @@ -263,19 +253,20 @@ export class NehubaLayerControlService implements OnDestroy{ } else { return [] } - }) + }), + )), ) /** * ngLayers controller */ - private ngLayersRegister: atlasAppearance.NgLayerCustomLayer[] = [] + private ngLayersRegister: atlasAppearance.const.NgLayerCustomLayer[] = [] - private getUpdatedCustomLayer(isSameLayer: (o: atlasAppearance.NgLayerCustomLayer, n: atlasAppearance.NgLayerCustomLayer) => boolean){ + private getUpdatedCustomLayer(isSameLayer: (o: atlasAppearance.const.NgLayerCustomLayer, n: atlasAppearance.const.NgLayerCustomLayer) => boolean){ return this.store$.pipe( select(atlasAppearance.selectors.customLayers), - map(customLayers => customLayers.filter(l => l.clType === "customlayer/nglayer") as atlasAppearance.NgLayerCustomLayer[]), + map(customLayers => customLayers.filter(l => l.clType === "customlayer/nglayer") as atlasAppearance.const.NgLayerCustomLayer[]), pairwise(), map(([ oldCustomLayers, newCustomLayers ]) => { return newCustomLayers.filter(n => oldCustomLayers.some(o => o.id === n.id && !isSameLayer(o, n))) @@ -288,7 +279,7 @@ export class NehubaLayerControlService implements OnDestroy{ private updateCustomLayerColorMap$ = this.getUpdatedCustomLayer((o, n) => o.shader === n.shader) private ngLayers$ = this.customLayers$.pipe( - map(customLayers => customLayers.filter(l => l.clType === "customlayer/nglayer") as atlasAppearance.NgLayerCustomLayer[]), + map(customLayers => customLayers.filter(l => l.clType === "customlayer/nglayer") as atlasAppearance.const.NgLayerCustomLayer[]), distinctUntilChanged( arrayEqual((o, n) => o.id === n.id) ), @@ -374,7 +365,7 @@ export class NehubaLayerControlService implements OnDestroy{ ), this.ngLayers$.pipe( map(({ customLayers }) => customLayers), - startWith([] as atlasAppearance.NgLayerCustomLayer[]), + startWith([] as atlasAppearance.const.NgLayerCustomLayer[]), map(customLayers => { /** * pmap control has its own visibility controller diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts index 1fa9f52ff34373fdb76dd62ddeb7acffc71ff091..6c7c7cd24d389a426f6b17ef134a495dedf11061 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts @@ -43,10 +43,10 @@ export interface INgLayerCtrl { names: string[] } add: { - [key: string]: atlasAppearance.NgLayerCustomLayer + [key: string]: atlasAppearance.const.NgLayerCustomLayer } update: { - [key: string]: Partial<atlasAppearance.NgLayerCustomLayer> + [key: string]: Partial<atlasAppearance.const.NgLayerCustomLayer> } setLayerTransparency: { [key: string]: number diff --git a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts index 10d7ba68683f16c5a09b0df46a954380480fe645..53c0afb97bb0933a327c68ff675d773cc4f008c2 100644 --- a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts +++ b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts @@ -147,10 +147,12 @@ export class NehubaViewerUnit implements OnDestroy { if (this.nehubaViewer$) { this.nehubaViewer$.next(this) } + getImportNehubaPr() - .then(() => { + .then(() => getExportNehuba()) + .then(exportNehuba => { this.nehubaLoaded = true - this.exportNehuba = getExportNehuba() + this.exportNehuba = exportNehuba const fixedZoomPerspectiveSlices = this.config && this.config.layout && this.config.layout.useNehubaPerspective && this.config.layout.useNehubaPerspective.fixedZoomPerspectiveSlices if (fixedZoomPerspectiveSlices) { const { sliceZoom, sliceViewportWidth, sliceViewportHeight } = fixedZoomPerspectiveSlices diff --git a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerContainer.component.ts b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerContainer.component.ts index f3c063b51ffb646e2eb9a9be701b3670ee00585d..2c0184e79b7304b440473addb8e3e0f71c9814f5 100644 --- a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerContainer.component.ts +++ b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerContainer.component.ts @@ -6,7 +6,6 @@ import { NehubaLayerControlService } from "../layerCtrl.service"; import { NehubaViewerContainerDirective } from "./nehubaViewerInterface.directive"; import { Store } from "@ngrx/store"; import { atlasSelection } from "src/state"; -import { SAPI } from "src/atlasComponents/sapi"; @Component({ selector: `sxplr-nehuba-viewer-container`, diff --git a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts index 36738660446e4cb2017bd630889c376d286de6c8..6454ea378c353eab8487b686f2009759eb95e819 100644 --- a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts +++ b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts @@ -10,7 +10,6 @@ import { INavObj, NehubaNavigationService } from "../navigation.service"; import { NehubaConfig, defaultNehubaConfig, getNehubaConfig } from "../config.service"; import { atlasAppearance, atlasSelection, userPreference } from "src/state"; import { SxplrAtlas, SxplrParcellation, SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes"; -import { NgLayerCustomLayer } from "src/state/atlasAppearance"; import { arrayEqual } from "src/util/array"; import { cvtNavigationObjToNehubaConfig } from "../config.service/util"; import { LayerCtrlEffects } from "../layerCtrl.service/layerCtrl.effects"; @@ -178,7 +177,7 @@ export class NehubaViewerContainerDirective implements OnDestroy{ switchMap((ATP: { atlas: SxplrAtlas, parcellation: SxplrParcellation, template: SxplrTemplate }) => this.store$.pipe( select(atlasAppearance.selectors.customLayers), debounceTime(16), - map(cl => cl.filter(l => l.clType === "baselayer/nglayer") as NgLayerCustomLayer[]), + map(cl => cl.filter(l => l.clType === "baselayer/nglayer") as atlasAppearance.const.NgLayerCustomLayer[]), distinctUntilChanged(arrayEqual((oi, ni) => oi.id === ni.id)), filter(layers => layers.length > 0), map(ngBaseLayers => { diff --git a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerTouch.directive.ts b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerTouch.directive.ts index 0fbd2562a1d4f50bc39e6157aefe322747afe475..6223e187fe07f0bd19f2f5774c793594ff17851f 100644 --- a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerTouch.directive.ts +++ b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerTouch.directive.ts @@ -43,13 +43,7 @@ export class NehubaViewerTouchDirective implements OnDestroy{ return this.htmlElementIndexMap.get(panel) } - private _exportNehuba: any - private get exportNehuba(){ - if (!this._exportNehuba) { - this._exportNehuba = getExportNehuba() - } - return this._exportNehuba - } + private exportNehuba: any private s: Subscription[] = [] private nehubaSub: Subscription[] = [] @@ -67,6 +61,7 @@ export class NehubaViewerTouchDirective implements OnDestroy{ }) ) } + getExportNehuba().then(exportNehuba => this.exportNehuba = exportNehuba) /** * Touchend also needs to be listened to, as user could start * with multitouch, and end up as single touch @@ -262,8 +257,9 @@ export class NehubaViewerTouchDirective implements OnDestroy{ if (isNaN(deltaX) || isNaN(deltaX)) return const { position } = this.ngViewer.navigationState const pos = position.spatialCoordinates - this.exportNehuba.vec3.set(pos, deltaX, deltaY, 0) - this.exportNehuba.vec3.transformMat4(pos, pos, this.viewportToData[panelIndex]) + const { vec3 } = this.exportNehuba + vec3.set(pos, deltaX, deltaY, 0) + vec3.transformMat4(pos, pos, this.viewportToData[panelIndex]) position.changed.dispatch() }) diff --git a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.component.ts b/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.component.ts index ea321e012cccbbe69e27f0e8467b14b945876e00..cce89a171435f72d6c17f112548f92477ad47dc1 100644 --- a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.component.ts +++ b/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.component.ts @@ -73,6 +73,8 @@ export class NgLayerCtrlCmp implements OnChanges, OnDestroy{ visible: boolean = true private viewer: NehubaViewerUnit + private exportNehuba: any + constructor( private store: Store<any>, @Inject(NEHUBA_INSTANCE_INJTKN) nehubaViewer$: Observable<NehubaViewerUnit> @@ -81,6 +83,8 @@ export class NgLayerCtrlCmp implements OnChanges, OnDestroy{ this.onDestroyCb.push( () => sub.unsubscribe() ) + + getExportNehuba().then(exportNehuba => this.exportNehuba = exportNehuba) } ngOnDestroy(): void { @@ -121,7 +125,7 @@ export class NgLayerCtrlCmp implements OnChanges, OnDestroy{ } setOrientation(): void { - const { mat4, quat, vec3 } = getExportNehuba() + const { mat4, quat, vec3 } = this.exportNehuba /** * glMatrix seems to store the matrix in transposed format diff --git a/src/viewerModule/nehuba/store/type.ts b/src/viewerModule/nehuba/store/type.ts index d45347a166b1630d9ecc47cebbfaa48a3a95985b..a55be8ced700fb825deb317904573577a0c8c7fb 100644 --- a/src/viewerModule/nehuba/store/type.ts +++ b/src/viewerModule/nehuba/store/type.ts @@ -12,7 +12,7 @@ export interface IAuxMesh { export interface INehubaFeature { - layers: atlasAppearance.NgLayerCustomLayer[] + layers: atlasAppearance.const.NgLayerCustomLayer[] panelMode: string panelOrder: string octantRemoval: boolean diff --git a/src/viewerModule/nehuba/userLayers/service.ts b/src/viewerModule/nehuba/userLayers/service.ts index 607f01558d663ad9c5502ad4a25b7d926b40cc32..042cc54851be1e31cf457926d81768dfcbc2a5a8 100644 --- a/src/viewerModule/nehuba/userLayers/service.ts +++ b/src/viewerModule/nehuba/userLayers/service.ts @@ -10,8 +10,7 @@ import { } from "src/atlasComponents/sapi/core/space/interspaceLinearXform" import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service" import { RouterService } from "src/routerModule/router.service" -import { atlasAppearance } from "src/state" -import { NgLayerCustomLayer } from "src/state/atlasAppearance" +import * as atlasAppearance from "src/state/atlasAppearance" import { EnumColorMapName } from "src/util/colorMaps" import { getShader } from "src/util/constants" import { getExportNehuba, getUuid } from "src/util/fn" @@ -46,7 +45,7 @@ export class UserLayerService implements OnDestroy { async getCvtFileToUrl(file: File): Promise<{ url: string meta: Meta - options?: Omit<NgLayerCustomLayer, OmitKeys> + options?: Omit<atlasAppearance.const.NgLayerCustomLayer, OmitKeys> }> { /** * if extension is .swc, process as if swc @@ -90,7 +89,8 @@ export class UserLayerService implements OnDestroy { const buf = await file.arrayBuffer() let outbuf try { - outbuf = getExportNehuba().pako.inflate(buf).buffer + const { pako } = await getExportNehuba() + outbuf = pako.inflate(buf).buffer } catch (e) { console.log("unpack error", e) outbuf = buf @@ -128,14 +128,14 @@ export class UserLayerService implements OnDestroy { addUserLayer( url: string, meta: Meta, - options: Omit<NgLayerCustomLayer, OmitKeys> = {} + options: Omit<atlasAppearance.const.NgLayerCustomLayer, OmitKeys> = {} ) { this.verifyUrl(url) if (this.userLayerUrlToIdMap.has(url)) { throw new Error(`url ${url} already added`) } const id = getUuid() - const layer: NgLayerCustomLayer = { + const layer: atlasAppearance.const.NgLayerCustomLayer = { id, clType: "customlayer/nglayer", source: url, diff --git a/src/viewerModule/nehuba/util.ts b/src/viewerModule/nehuba/util.ts index 1073c1bde8e78db2c490b44ab6bc17c65b8aab92..81aed3560abe6fe1f41b9faf13211d1002702591 100644 --- a/src/viewerModule/nehuba/util.ts +++ b/src/viewerModule/nehuba/util.ts @@ -181,8 +181,8 @@ export const isIdentityQuat = (ori: number[]): boolean => Math.abs(ori[0]) < 1e- export const importNehubaFactory = (appendSrc: (src: string) => Promise<void>): () => Promise<void> => { let pr: Promise<void> - return () => { - if (getExportNehuba()) return Promise.resolve() + return async () => { + if (!!(window as any).export_nehuba) return if (pr) return pr pr = appendSrc('main.bundle.js') diff --git a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts index a03768e58f463917aae98d0ba492ef238d96e62f..7d6634342b3daf28b948e97a7d6fcca9d6b9310f 100644 --- a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts +++ b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts @@ -1,7 +1,7 @@ import { Component, Output, EventEmitter, ElementRef, OnDestroy, AfterViewInit, Inject, Optional, ChangeDetectionStrategy } from "@angular/core"; import { EnumViewerEvt, IViewer, TViewerEvent } from "src/viewerModule/viewer.interface"; -import { combineLatest, forkJoin, from, merge, Observable, Subject } from "rxjs"; -import { debounceTime, distinctUntilChanged, filter, map, scan, shareReplay, switchMap } from "rxjs/operators"; +import { combineLatest, from, merge, NEVER, Observable, Subject } from "rxjs"; +import { catchError, debounceTime, distinctUntilChanged, filter, map, scan, shareReplay, switchMap } from "rxjs/operators"; import { ComponentStore } from "src/viewerModule/componentStore"; import { select, Store } from "@ngrx/store"; import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util"; @@ -145,15 +145,12 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, AfterViewInit select(atlasSelection.selectors.selectedParcAllRegions), ) ]).pipe( - switchMap(([ { atlas, parcellation, template }, regions]) => { - const returnObj = { - 'left': {} as Record<number, SxplrRegion>, - 'right': {} as Record<number, SxplrRegion> - } + switchMap(([ { parcellation, template }, regions]) => { return merge( ...regions.map(region => from(this.sapi.getRegionLabelIndices(template, parcellation, region)).pipe( - map(label => ({ region, label })) + map(label => ({ region, label })), + catchError(() => NEVER) ) ) ).pipe( diff --git a/src/viewerModule/viewer.common.effects.ts b/src/viewerModule/viewer.common.effects.ts new file mode 100644 index 0000000000000000000000000000000000000000..cc888f129935050edd49a5238764a672f3d00d66 --- /dev/null +++ b/src/viewerModule/viewer.common.effects.ts @@ -0,0 +1,53 @@ +import { Injectable } from "@angular/core"; +import { createEffect } from "@ngrx/effects"; +import { select, Store } from "@ngrx/store"; +import { forkJoin, of } from "rxjs"; +import { distinctUntilChanged, map, switchMap } from "rxjs/operators"; +import * as atlasSelection from "src/state/atlasSelection"; +import * as atlasAppearance from "src/state/atlasAppearance" +import { SAPI } from "src/atlasComponents/sapi"; + +@Injectable() +export class ViewerCommonEffects { + onATPSetUseViewer$ = createEffect(() => this.store.pipe( + select(atlasSelection.selectors.standaloneVolumes), + distinctUntilChanged((o, n) => o?.length === n?.length), + switchMap(volumes => volumes?.length > 0 + ? of( atlasAppearance.const.useViewer.NEHUBA ) + : this.store.pipe( + select(atlasSelection.selectors.selectedTemplate), + distinctUntilChanged((o, n) => o?.id === n?.id), + switchMap(template => { + if (!template) { + return of(null as atlasAppearance.const.UseViewer) + } + return forkJoin({ + voxel: this.sapi.getVoxelTemplateImage(template), + surface: this.sapi.getSurfaceTemplateImage(template) + }).pipe( + map(vols => { + if (!vols) return null + const { voxel, surface } = vols + if (voxel.length > 0 && surface.length > 0) { + console.error(`both voxel and surface length are > 0, this should not happen.`) + return atlasAppearance.const.useViewer.NOT_SUPPORTED + } + if (voxel.length > 0) { + return atlasAppearance.const.useViewer.NEHUBA + } + if (surface.length > 0) { + return atlasAppearance.const.useViewer.THREESURFER + } + return atlasAppearance.const.useViewer.NOT_SUPPORTED + }) + ) + }) + ) + ), + map(viewer => atlasAppearance.actions.setUseViewer({ viewer })) + )) + + constructor(private store: Store, private sapi: SAPI){ + + } +} diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts index db9b4f7e518b9ecb288f041d5ce9c8a1f46653ee..25c57644e627082617ed60ce3d5262def63be2d8 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.component.ts +++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, TemplateRef, ViewChild, ViewContainerRef } from "@angular/core"; import { select, Store } from "@ngrx/store"; -import { combineLatest, forkJoin, Observable, of, Subscription } from "rxjs"; -import { debounceTime, distinctUntilChanged, map, shareReplay, startWith, switchMap } from "rxjs/operators"; +import { combineLatest, Observable, Subscription } from "rxjs"; +import { debounceTime, map, shareReplay, startWith } from "rxjs/operators"; import { CONST, ARIA_LABELS, QUICKTOUR_DESC } from 'common/constants' import { animate, state, style, transition, trigger } from "@angular/animations"; import { IQuickTourData } from "src/ui/quickTour"; @@ -9,8 +9,8 @@ import { EnumViewerEvt, TContextArg, TSupportedViewers, TViewerEvent } from "../ import { ContextMenuService, TContextMenuReg } from "src/contextMenuModule"; import { DialogService } from "src/services/dialogService.service"; import { SAPI } from "src/atlasComponents/sapi"; -import { Feature, NgLayerSpec, SxplrAtlas, SxplrRegion, TThreeMesh } from "src/atlasComponents/sapi/sxplrTypes" -import { atlasSelection, userInteraction } from "src/state"; +import { Feature, SxplrAtlas, SxplrRegion } from "src/atlasComponents/sapi/sxplrTypes" +import { atlasAppearance, atlasSelection, userInteraction } from "src/state"; import { environment } from "src/environments/environment" // import { SapiViewsFeaturesVoiQuery } from "src/atlasComponents/sapiViews/features"; @@ -115,58 +115,19 @@ export class ViewerCmp implements OnDestroy { select(atlasSelection.selectors.selectedParcAllRegions) ) - public isStandaloneVolumes$ = this.store$.pipe( - select(atlasSelection.selectors.standaloneVolumes), - map(v => v.length > 0) - ) - public viewerMode$: Observable<string> = this.store$.pipe( select(atlasSelection.selectors.viewerMode), shareReplay(1), ) - public useViewer$: Observable<TSupportedViewers | 'notsupported'> = combineLatest([ - this.store$.pipe( - atlasSelection.fromRootStore.distinctATP(), - switchMap(({ template }) => template - ? forkJoin({ - voxel: this.sapi.getVoxelTemplateImage(template), - surface: this.sapi.getSurfaceTemplateImage(template), - }) - : of(null as { voxel: NgLayerSpec[], surface: TThreeMesh[] }) - ), - map(vols => { - if (!vols) return null - const flags = { - isNehuba: false, - isThreeSurfer: false - } - if (vols.voxel.length > 0) { - flags.isNehuba = true - } - - if (vols.surface.length > 0) { - flags.isThreeSurfer = true - } - return flags - }) - ), - this.isStandaloneVolumes$, - ]).pipe( - distinctUntilChanged(([ prevFlags, prevIsSv ], [ currFlags, currIsSv ]) => { - const same = prevIsSv === currIsSv - && prevFlags?.isNehuba === currFlags?.isNehuba - && prevFlags?.isThreeSurfer === currFlags?.isThreeSurfer - return same - }), - map<unknown, TSupportedViewers | 'notsupported'>(([flags, isSv]) => { - if (isSv) return 'nehuba' - if (!flags) return null - if (flags.isNehuba) return 'nehuba' - if (flags.isThreeSurfer) return 'threeSurfer' - return 'notsupported' - }), - shareReplay(1), + public useViewer$: Observable<TSupportedViewers | 'notsupported'> = this.store$.pipe( + select(atlasAppearance.selectors.useViewer), + map(useviewer => { + if (useviewer === "NEHUBA") return "nehuba" + if (useviewer === "THREESURFER") return "threeSurfer" + if (useviewer === "NOT_SUPPORTED") return "notsupported" + return null + }) ) public viewerCtx$ = this.ctxMenuSvc.context$