diff --git a/docs/releases/v2.14.5.md b/docs/releases/v2.14.5.md index 8ebec4a4287fd3b225e23e2a906f83a8e30cd5e0..233bc752233142b59218fbd48d29db3177033a02 100644 --- a/docs/releases/v2.14.5.md +++ b/docs/releases/v2.14.5.md @@ -14,6 +14,7 @@ - (experimental) Added code snippet to limited panels - (experimental) allow addition of custom linear coordinate space - (experimental) show BigBrain slice number +- (experimental) allow big brain template to be downloaded ## Bugfix @@ -27,3 +28,4 @@ - Removed reference to JSC OKD instance, as the instance is no longer available - Updated google-site-verification - Allow inter-space transform to be configured at runtime +- Fetch `/meta.json` for additional metadata related to a volume diff --git a/src/atlas-download/atlas-download.directive.ts b/src/atlas-download/atlas-download.directive.ts index 637e9ded3765e946bb23a397461ccb9ba22b34ff..f6c123f5673b71f6473775ef0c202de86a206452 100644 --- a/src/atlas-download/atlas-download.directive.ts +++ b/src/atlas-download/atlas-download.directive.ts @@ -25,6 +25,11 @@ export class AtlasDownloadDirective { take(1) ).toPromise() + const bbox = await this.store.pipe( + select(selectors.currentViewport), + take(1), + ).toPromise() + const selectedRegions = await this.store.pipe( select(selectors.selectedRegions), take(1) @@ -44,6 +49,11 @@ export class AtlasDownloadDirective { parcellation_id: parcellation.id, space_id: template.id, } + + if (bbox) { + query['bbox'] = JSON.stringify([bbox.minpoint, bbox.maxpoint]) + } + if (selectedRegions.length === 1) { query['region_id'] = selectedRegions[0].name } diff --git a/src/atlasComponents/sapi/sapi.service.ts b/src/atlasComponents/sapi/sapi.service.ts index b914e41dbc658bb35a5312ef3e92585fdbb89d34..afe997bb5439ef2331d03e3f29f72f3e9d08955a 100644 --- a/src/atlasComponents/sapi/sapi.service.ts +++ b/src/atlasComponents/sapi/sapi.service.ts @@ -20,7 +20,7 @@ export const useViewer = { } as const export const SIIBRA_API_VERSION_HEADER_KEY='x-siibra-api-version' -export const EXPECTED_SIIBRA_API_VERSION = '0.3.17' +export const EXPECTED_SIIBRA_API_VERSION = '0.3.16' type PaginatedResponse<T> = { items: T[] @@ -286,9 +286,11 @@ export class SAPI{ tap(() => { const respVersion = SAPI.API_VERSION if (respVersion !== EXPECTED_SIIBRA_API_VERSION) { - this.snackbar.open(`Expecting ${EXPECTED_SIIBRA_API_VERSION}, got ${respVersion}. Some functionalities may not work as expected.`, 'Dismiss', { - duration: 5000 - }) + // TODO temporarily disable snackbar. Enable once siibra-api version stabilises + console.log(`Expecting ${EXPECTED_SIIBRA_API_VERSION}, got ${respVersion}. Some functionalities may not work as expected.`) + // this.snackbar.open(`Expecting ${EXPECTED_SIIBRA_API_VERSION}, got ${respVersion}. Some functionalities may not work as expected.`, 'Dismiss', { + // duration: 5000 + // }) } }), shareReplay(1), diff --git a/src/atlasComponents/sapi/translateV3.ts b/src/atlasComponents/sapi/translateV3.ts index b0eb95d8be166dd449c86cadb2662bc2964749d9..f30e9d16a5e9fc26e4472ada956e372bc252a48c 100644 --- a/src/atlasComponents/sapi/translateV3.ts +++ b/src/atlasComponents/sapi/translateV3.ts @@ -555,6 +555,13 @@ class TranslateV3 { } async fetchMeta(url: string): Promise<MetaV1Schema|null> { + // TODO move to neuroglancer-data-vm + // difumo + if (url.startsWith("https://object.cscs.ch/v1/AUTH_08c08f9f119744cbbf77e216988da3eb/")) { + return { + version: 1 + } + } if (url in TMP_META_REGISTRY) { return TMP_META_REGISTRY[url] } @@ -580,14 +587,15 @@ class TranslateV3 { /** * TODO ensure all /meta endpoints are populated */ - // try{ - // const resp = await this.cFetch(`${url}/meta`) - // if (resp.status === 200) { - // return resp.json() - // } - // } catch (e) { + try{ + const resp = await this.cFetch(`${url}/meta.json`) + if (resp.status === 200) { + return resp.json() + } + // eslint-disable-next-line no-empty + } catch (e) { - // } + } return null } diff --git a/src/state/atlasSelection/actions.ts b/src/state/atlasSelection/actions.ts index d8f0d01d1b6703691fa07e7614422d64ef8ba7bb..2920b2f949c0472a443ae3a988168061986b5d41 100644 --- a/src/state/atlasSelection/actions.ts +++ b/src/state/atlasSelection/actions.ts @@ -1,5 +1,5 @@ import { createAction, props } from "@ngrx/store"; -import { SxplrAtlas, SxplrParcellation, SxplrRegion, SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes"; +import { BoundingBox, SxplrAtlas, SxplrParcellation, SxplrRegion, SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes"; import { BreadCrumb, nameSpace, ViewerMode, AtlasSelectionState } from "./const" import { TFace, TSandsPoint } from "src/util/types"; @@ -189,3 +189,10 @@ export const selectPoint = createAction( export const clearSelectedPoint = createAction( `${nameSpace} clearPoint` ) + +export const setViewport = createAction( + `${nameSpace} setViewport`, + props<{ + viewport: BoundingBox + }>() +) diff --git a/src/state/atlasSelection/const.ts b/src/state/atlasSelection/const.ts index bc8fa3156c229ea5ade5c4dad9171270be334a57..277bc83755065e08e1be666ec438d94c08b44340 100644 --- a/src/state/atlasSelection/const.ts +++ b/src/state/atlasSelection/const.ts @@ -1,4 +1,4 @@ -import { SxplrAtlas, SxplrTemplate, SxplrParcellation, SxplrRegion } from "src/atlasComponents/sapi/sxplrTypes" +import { SxplrAtlas, SxplrTemplate, SxplrParcellation, SxplrRegion, BoundingBox } from "src/atlasComponents/sapi/sxplrTypes" import { TSandsPoint, TFace } from "src/util/types" export const nameSpace = `[state.atlasSelection]` @@ -14,6 +14,8 @@ export type AtlasSelectionState = { selectedParcellation: SxplrParcellation selectedParcellationAllRegions: SxplrRegion[] + currentViewport: BoundingBox + selectedRegions: SxplrRegion[] standAloneVolumes: string[] diff --git a/src/state/atlasSelection/effects.ts b/src/state/atlasSelection/effects.ts index c0ca77e8e006d1ac95b78fb0e68b55d40d812252..c7cd686d5a585ea13281b30a43d46b82772ffbcb 100644 --- a/src/state/atlasSelection/effects.ts +++ b/src/state/atlasSelection/effects.ts @@ -1,19 +1,25 @@ import { Injectable } from "@angular/core"; import { Actions, createEffect, ofType } from "@ngrx/effects"; -import { forkJoin, from, NEVER, Observable, of, throwError } from "rxjs"; -import { catchError, filter, map, mapTo, switchMap, take, withLatestFrom } from "rxjs/operators"; +import { combineLatest, concat, forkJoin, from, NEVER, Observable, of, throwError } from "rxjs"; +import { catchError, debounceTime, distinctUntilChanged, filter, map, mapTo, switchMap, take, withLatestFrom } from "rxjs/operators"; import { IDS, SAPI } from "src/atlasComponents/sapi"; import * as mainActions from "../actions" import { select, Store } from "@ngrx/store"; import { selectors, actions, fromRootStore } from '.' import { AtlasSelectionState } from "./const" -import { atlasAppearance, atlasSelection } from ".."; +import { atlasAppearance, atlasSelection, generalActions } from ".."; import { InterSpaceCoordXformSvc } from "src/atlasComponents/sapi/core/space/interSpaceCoordXform.service"; import { SxplrAtlas, SxplrParcellation, SxplrRegion, SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes"; import { DecisionCollapse } from "src/atlasComponents/sapi/decisionCollapse.service"; import { DialogFallbackCmp } from "src/ui/dialogInfo"; import { MatDialog } from 'src/sharedModules/angularMaterial.exports' +import { ResizeObserverService } from "src/util/windowResize/windowResize.service"; +import { TViewerEvtCtxData } from "src/viewerModule/viewer.interface"; +import { ContextMenuService } from "src/contextMenuModule"; +import { NehubaVCtxToBbox } from "src/viewerModule/pipes/nehubaVCtxToBbox.pipe"; + +const NEHUBA_CTX_BBOX = new NehubaVCtxToBbox() type OnTmplParcHookArg = { previous: { @@ -448,6 +454,48 @@ export class Effect { map(() => actions.clearSelectedRegions()) )) + onViewportChanges = createEffect(() => this.store.pipe( + select(atlasAppearance.selectors.useViewer), + distinctUntilChanged(), + switchMap(useViewer => { + if (useViewer !== "NEHUBA") { + return of(generalActions.noop()) + } + return this.store.pipe( + select(selectors.selectedTemplate), + switchMap(selectedTemplate => combineLatest([ + concat( + of(null), + this.resize.windowResize, + ), + this.ctxMenuSvc.context$ + ]).pipe( + debounceTime(160), + map(([_, ctx]) => { + + const { width, height } = window.screen + const size = Math.max(width, height) + + const result = NEHUBA_CTX_BBOX.transform(ctx, [size, size, size]) + if (!result) { + return generalActions.noop() + } + const [ min, max ] = result + return actions.setViewport({ + viewport: { + spaceId: selectedTemplate.id, + space: selectedTemplate, + minpoint: min, + maxpoint: max, + center: min.map((v, idx) => (v + max[idx])/2) as [number, number, number] + } + }) + }) + )) + ) + }) + )) + constructor( private action: Actions, private sapiSvc: SAPI, @@ -455,6 +503,9 @@ export class Effect { private interSpaceCoordXformSvc: InterSpaceCoordXformSvc, private collapser: DecisionCollapse, private dialog: MatDialog, + private resize: ResizeObserverService, + /** potential issue with circular import. generic should not import specific */ + private ctxMenuSvc: ContextMenuService<TViewerEvtCtxData<'threeSurfer' | 'nehuba'>>, ){ } } \ No newline at end of file diff --git a/src/state/atlasSelection/selectors.ts b/src/state/atlasSelection/selectors.ts index 44f122b362d28da0335db6c726e5b49f207a0613..90d14407ef9799f72504a348341aa31096fcee52 100644 --- a/src/state/atlasSelection/selectors.ts +++ b/src/state/atlasSelection/selectors.ts @@ -69,3 +69,8 @@ export const relevantSelectedPoint = createSelector( return null } ) + +export const currentViewport = createSelector( + selectStore, + store => store.currentViewport +) diff --git a/src/state/atlasSelection/store.ts b/src/state/atlasSelection/store.ts index 036fae219ba525dbb6afaadcb5acc20c154b6e5d..4eb6071ffdf18f086cd6f451c71d2723186b30ff 100644 --- a/src/state/atlasSelection/store.ts +++ b/src/state/atlasSelection/store.ts @@ -13,6 +13,7 @@ export const defaultState: AtlasSelectionState = { viewerMode: null, breadcrumbs: [], selectedPoint: null, + currentViewport: null, } const reducer = createReducer( @@ -145,6 +146,15 @@ const reducer = createReducer( selectedPoint: null } } + ), + on( + actions.setViewport, + (state, { viewport }) => { + return { + ...state, + currentViewport: viewport + } + } ) ) diff --git a/src/viewerModule/pipes/nehubaVCtxToBbox.pipe.ts b/src/viewerModule/pipes/nehubaVCtxToBbox.pipe.ts index 37a68c4d3aef4acd4ef3474f2e0e4ad4fae1c5cb..98ac669554bc2abb407a3d23f56e27d4b5276550 100644 --- a/src/viewerModule/pipes/nehubaVCtxToBbox.pipe.ts +++ b/src/viewerModule/pipes/nehubaVCtxToBbox.pipe.ts @@ -12,17 +12,23 @@ const MAGIC_RADIUS = 256 }) export class NehubaVCtxToBbox implements PipeTransform{ - public transform(event: TViewerEvtCtxData<'nehuba' | 'threeSurfer'>, unit: string = "mm"): BBox{ + public transform(event: TViewerEvtCtxData<'nehuba' | 'threeSurfer'>, boxDims: [number, number, number]=null, unit: string = "mm"): BBox{ if (!event) { return null } if (event.viewerType === 'threeSurfer') { return null } + if (!boxDims) { + boxDims = [MAGIC_RADIUS, MAGIC_RADIUS, MAGIC_RADIUS] + } + let divisor = 1 - if (unit === "mm") { - divisor = 1e6 + if (unit !== "mm") { + console.warn(`unit other than mm is not yet supported`) + return null } + divisor = 1e6 const { payload } = event as TViewerEvtCtxData<'nehuba'> if (!payload.nav) return null @@ -30,8 +36,8 @@ export class NehubaVCtxToBbox implements PipeTransform{ const { position, zoom } = payload.nav // position is in nm // zoom can be directly applied as a multiple - const min = position.map(v => (v - (MAGIC_RADIUS * zoom)) / divisor) as Point - const max = position.map(v => (v + (MAGIC_RADIUS * zoom)) / divisor) as Point + const min = position.map((v, idx) => (v - (boxDims[idx] * zoom)) / divisor) as Point + const max = position.map((v, idx) => (v + (boxDims[idx] * zoom)) / divisor) as Point return [min, max] } }