diff --git a/docs/releases/v2.13.3.md b/docs/releases/v2.13.3.md index f1ed8b123c31772f17971b20e4fe1bcf9aeed334..0dfc54a1f9d26e39fca796ff29a9f08ecaa6d266 100644 --- a/docs/releases/v2.13.3.md +++ b/docs/releases/v2.13.3.md @@ -1,5 +1,9 @@ # v2.13.3 +## Feature + +- 1um section now does not move x,z position + ## Behind the scene - user added layer now sits on index 1 \ No newline at end of file diff --git a/src/atlasComponents/sapi/sxplrTypes.ts b/src/atlasComponents/sapi/sxplrTypes.ts index 25ac84c21cba44c1ad394de9c00ba0e657bb4c55..e8d44c8dbea30d2e1d992b6da54a83f54e5d0b6a 100644 --- a/src/atlasComponents/sapi/sxplrTypes.ts +++ b/src/atlasComponents/sapi/sxplrTypes.ts @@ -74,6 +74,7 @@ export { } from "src/viewerModule/nehuba/config.service/type" import { TThreeSurferMesh, TThreeMesh, TThreeMeshLabel } from "src/viewerModule/threeSurfer/types" +import { MetaV1Schema } from "./typeV3" export { TThreeSurferMesh, TThreeMesh, TThreeMeshLabel } /** @@ -108,6 +109,7 @@ export type VoiFeature = { url: string transform: number[][] info: Record<string, any> + meta?: MetaV1Schema } } & Feature diff --git a/src/atlasComponents/sapi/translateV3.ts b/src/atlasComponents/sapi/translateV3.ts index 098aa6b4e26c07591a64f36d03e047f15d7b1712..dabec3badf48d08d9e8a739459ebe5b1a5985f97 100644 --- a/src/atlasComponents/sapi/translateV3.ts +++ b/src/atlasComponents/sapi/translateV3.ts @@ -1,12 +1,19 @@ import { SxplrAtlas, SxplrParcellation, SxplrTemplate, SxplrRegion, NgLayerSpec, NgPrecompMeshSpec, NgSegLayerSpec, VoiFeature, Point, TThreeMesh, LabelledMap, CorticalFeature, Feature, GenericInfo, BoundingBox } from "./sxplrTypes" -import { PathReturn } from "./typeV3" +import { PathReturn, MetaV1Schema } from "./typeV3" import { hexToRgb } from 'common/util' import { components } from "./schemaV3" import { defaultdict } from "src/util/fn" +const BIGBRAIN_XZ = [ + [-70.677, 62.222], + [-70.677, -58.788], + [68.533, -58.788], + [68.533, 62.222], +] + class TranslateV3 { #atlasMap: Map<string, PathReturn<"/atlases/{atlas_id}">> = new Map() @@ -118,20 +125,23 @@ class TranslateV3 { url: string transform: number[][] info: Record<string, any> + meta?: MetaV1Schema }> = {} for (const key in input) { if (key !== 'neuroglancer/precomputed') { continue } const url = input[key] - const [ transform, info ] = await Promise.all([ + const [ transform, info, meta ] = await Promise.all([ this.cFetch(`${url}/transform.json`).then(res => res.json()) as Promise<number[][]>, this.cFetch(`${url}/info`).then(res => res.json()) as Promise<Record<string, any>>, + this.fetchMeta(url), ]) returnObj[key] = { url: input[key], transform: transform, info: info, + meta, } } return returnObj @@ -354,6 +364,41 @@ class TranslateV3 { } } + async fetchMeta(url: string): Promise<MetaV1Schema|null> { + const is1umRegisteredSlices = url.startsWith("https://1um.brainatlas.eu/registered_sections/bigbrain") + if (is1umRegisteredSlices) { + const found = /B20_([0-9]{4})/.exec(url) + if (found) { + const sectionId = parseInt(found[1]) + const realYDis = (sectionId * 2e4 - 70010000) / 1e6 + return { + version: 1, + preferredColormap: ["greyscale"], + bestViewPoints: [{ + type: "enclosed", + points: BIGBRAIN_XZ.map(([x, z]) => ({ + type: "point", + value: [x, realYDis, z] + })) + }] + } + } + } + /** + * TODO ensure all /meta endpoints are populated + */ + // try{ + // const resp = await this.cFetch(`${url}/meta`) + // if (resp.status === 200) { + // return resp.json() + // } + // } catch (e) { + + // } + + return null + } + async translateSpaceToAuxMesh(template: SxplrTemplate): Promise<NgPrecompMeshSpec[]>{ if (!template) return [] const space = this.retrieveTemplate(template) diff --git a/src/atlasComponents/sapi/typeV3.ts b/src/atlasComponents/sapi/typeV3.ts index 9be5ef62376b3f67a496268220b78a5ca7f53cc8..13d2d393b2cfee465f4927ed21522832b70726d9 100644 --- a/src/atlasComponents/sapi/typeV3.ts +++ b/src/atlasComponents/sapi/typeV3.ts @@ -52,3 +52,83 @@ export type PaginatedModel<T> = { page: number size: number } + +type X4Affine = number[][] + +type ShaderEnum = + | "greyscale" + | "viridis" + | "plasma" + | "magma" + | "inferno" + | "jet" +/** + * Preferred colormap in order of preference + */ +type PreferredColormap = ShaderEnum[] +type X1Vector = [number, number, number] +/** + * Best locations to view this volume. + */ +type BestViewPoints = (PointGeometry | PlaneGeometry | EnclosedROI)[] + +export interface MetaV1Schema { + version: 1 + data?: GenericImage | SingleChannelImage | ThreeChannelImage + transform?: X4Affine + preferredColormap?: PreferredColormap + override?: Override + bestViewPoints?: BestViewPoints +} +/** + * Generic image, with arbitary dimensions. + */ +interface GenericImage { + type: "image" + range?: ValueRange[] +} +/** + * Describes the range of values + */ +interface ValueRange { + min?: number + max?: number +} +/** + * Describes an image with 1 dimension, e.g. used as greyscale image. + */ +interface SingleChannelImage { + type: "image/1d" + range?: [ValueRange] +} +/** + * Describes an image with 3 dimensions, mostly used as RGB image. + */ +interface ThreeChannelImage { + type: "image/3d" + range?: [ValueRange, ValueRange, ValueRange] +} +/** + * Overrides provide some low level/implementation hints. They are more prone to breaking, and thus should be used with the knowledge as such. + */ +interface Override { + /** + * Hints that client should use this shader for the volume in neuroglancer + */ + shader?: string +} +interface PointGeometry { + type: "point" + value?: X1Vector +} +interface PlaneGeometry { + type: "plane" +} +interface EnclosedROI { + type: "enclosed" + points: PointGeometry[] +} + +export function isEnclosed(v: BestViewPoints[number]): v is EnclosedROI { + return v.type === "enclosed" +} diff --git a/src/features/feature-view/feature-view.component.html b/src/features/feature-view/feature-view.component.html index 41a18ab45fb8ec4dea3855ffdff863b80feafe36..26a02e48c3a15673db3878ebba79fc0748d795c0 100644 --- a/src/features/feature-view/feature-view.component.html +++ b/src/features/feature-view/feature-view.component.html @@ -97,6 +97,7 @@ [ng-layer-ctl-transform]="voi.ngVolume.transform" [ng-layer-ctl-info]="voi.ngVolume.info" [ng-layer-ctl-opacity]="1.0" + [ng-layer-ctl-meta]="voi.ngVolume.meta" [ng-layer-ctrl-show]="true"> </ng-layer-ctl> </mat-tab> diff --git a/src/viewerModule/nehuba/ngLayerCtlModule/ngLayerCtl/ngLayerCtrl.component.ts b/src/viewerModule/nehuba/ngLayerCtlModule/ngLayerCtl/ngLayerCtrl.component.ts index 71d2acd47818b95728595616c3a0d53c9efddf6f..9ddf114f71c86ee55bfbcf0c9b85d186bb7c496b 100644 --- a/src/viewerModule/nehuba/ngLayerCtlModule/ngLayerCtl/ngLayerCtrl.component.ts +++ b/src/viewerModule/nehuba/ngLayerCtlModule/ngLayerCtl/ngLayerCtrl.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, Inject, Input, OnChanges, OnDestroy } from "@angular/core"; -import { Store } from "@ngrx/store"; +import { Store, select } from "@ngrx/store"; import { isMat4 } from "common/util" import { CONST } from "common/constants" import { Observable } from "rxjs"; @@ -8,6 +8,7 @@ import { NehubaViewerUnit, NEHUBA_INSTANCE_INJTKN } from "src/viewerModule/nehub import { getExportNehuba } from "src/util/fn"; import { getShader } from "src/util/constants"; import { EnumColorMapName } from "src/util/colorMaps"; +import { MetaV1Schema, isEnclosed } from "src/atlasComponents/sapi/typeV3"; type Vec4 = [number, number, number, number] type Mat4 = [Vec4, Vec4, Vec4, Vec4] @@ -50,6 +51,9 @@ export class NgLayerCtrlCmp implements OnChanges, OnDestroy{ @Input('ng-layer-ctl-shader') shader: string + @Input("ng-layer-ctl-meta") + meta: MetaV1Schema + opacity: number = 1.0 @Input('ng-layer-ctl-opacity') set _opacity(val: number | string) { @@ -80,14 +84,21 @@ export class NgLayerCtrlCmp implements OnChanges, OnDestroy{ private viewer: NehubaViewerUnit private exportNehuba: any + private currentPositionMm: number[] constructor( private store: Store<any>, @Inject(NEHUBA_INSTANCE_INJTKN) nehubaViewer$: Observable<NehubaViewerUnit> ){ const sub = nehubaViewer$.subscribe(v => this.viewer = v) + const navSub = this.store.pipe( + select(atlasSelection.selectors.navigation) + ).subscribe(nav => { + this.currentPositionMm = nav?.position.map(v => v / 1e6) + }) this.onDestroyCb.push( - () => sub.unsubscribe() + () => sub.unsubscribe(), + () => navSub.unsubscribe(), ) getExportNehuba().then(exportNehuba => { @@ -167,6 +178,28 @@ export class NgLayerCtrlCmp implements OnChanges, OnDestroy{ vec3.scale(final, final, 0.5) position = Array.from(final) } + + const enclosed = this.meta?.bestViewPoints.filter(isEnclosed).find(v => v.points.length >= 3) + if (enclosed) { + const curr = vec3.fromValues(...this.currentPositionMm) + const pt1 = vec3.fromValues(...enclosed.points[0].value) + const pt0 = vec3.fromValues(...enclosed.points[1].value) + const pt2 = vec3.fromValues(...enclosed.points[2].value) + vec3.sub(pt1, pt1, pt0) + vec3.sub(pt2, pt2, pt0) + vec3.normalize(pt1, pt1) + vec3.normalize(pt2, pt2) + + vec3.sub(curr, curr, pt0) + + vec3.mul(pt1, pt1, curr) + vec3.mul(pt2, pt2, curr) + + const resultant = vec3.add(vec3.create(), pt1, pt2) + vec3.add(resultant, resultant, pt0) + vec3.scale(resultant, resultant, 1e6) + position = Array.from(resultant) + } this.store.dispatch( diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html index dff8e4b55526b59147c566f195691a0d19746551..14259c7f42e1b25cb7455688129282dcee4a453d 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.template.html +++ b/src/viewerModule/viewerCmp/viewerCmp.template.html @@ -1048,19 +1048,17 @@ </ng-template> </ng-template> -<!-- feature tmpls --> -<ng-template #sapiBaseFeatureTmpl - let-backCb="backCb" - let-feature="feature"> - +<!-- general feature tmpl --> +<ng-template let-feature="feature" #selectedFeatureTmpl> + <!-- TODO differentiate between features (spatial, regional etc) --> + <sxplr-feature-view class="sxplr-z-2 mat-elevation-z2" [feature]="feature"> <div header> <!-- back btn --> <button mat-button - *ngIf="backCb" - (click)="backCb()" + (click)="clearSelectedFeature()" [attr.aria-label]="ARIA_LABELS.CLOSE" class="sxplr-mb-2" > @@ -1072,28 +1070,6 @@ </div> </sxplr-feature-view> - -</ng-template> - -<!-- general feature tmpl --> -<ng-template let-feature="feature" #selectedFeatureTmpl> - <!-- TODO differentiate between features (spatial, regional etc) --> - - <!-- spatial feature tmpl --> - <ng-container *ngTemplateOutlet="sapiBaseFeatureTmpl; context: { - backCb: clearSelectedFeature.bind(this), - feature: feature - }"> - </ng-container> - - <ng-layer-ctl *ngFor="let vol of feature.volumes" - class="d-block" - [ng-layer-ctl-name]="vol.metadata.fullName" - [ng-layer-ctl-src]="vol.data.url" - [ng-layer-ctl-transform]="vol.data | getProperty : 'detail' | getProperty: 'neuroglancer/precomputed' | getProperty : 'transform'"> - </ng-layer-ctl> - <ng-template #sapiVOITmpl> - </ng-template> </ng-template> <!-- general point tmpl -->