diff --git a/src/atlasComponents/sapi/core/base.ts b/src/atlasComponents/sapi/core/base.ts index 2bfaa27d605cb05633277602e9fdf5dc870c8bc5..cb7544f04111082bcf5cb845c8bfccb7b1bd1c9d 100644 --- a/src/atlasComponents/sapi/core/base.ts +++ b/src/atlasComponents/sapi/core/base.ts @@ -1,7 +1,6 @@ import { Observable } from "rxjs" import { SAPI } from "../sapi.service" import { RouteParam } from "../typeV3" -import { SapiQueryPriorityArg } from "../sxplrTypes" const AllFeatures = { CorticalProfile: "CorticalProfile", @@ -22,12 +21,12 @@ export abstract class SAPIBase<T extends AF> { private _sapi: SAPI, ){} - getFeatures(featureType: T, param: RouteParam<`/feature/${T}`> & Partial<SapiQueryPriorityArg>) { + getFeatures(featureType: T, param: RouteParam<`/feature/${T}`>) { const route = `/feature/${featureType}` as `/feature/${T}` return this._sapi.v3Get(route, param) } - getFeatureInstance(featureType: T, param: RouteParam<`/feature/${T}/{feature_id}`> & Partial<SapiQueryPriorityArg>) { + getFeatureInstance(featureType: T, param: RouteParam<`/feature/${T}/{feature_id}`>) { const route = `/feature/${featureType}/{feature_id}` as `/feature/${T}/{feature_id}` return this._sapi.v3Get(route, param) } diff --git a/src/atlasComponents/sapi/core/sapiParcellation.ts b/src/atlasComponents/sapi/core/sapiParcellation.ts index bfeb49d484b26ddca6f781a254ee4ca929c1e951..21b2b42eca63419aab37f598292c095059d97066 100644 --- a/src/atlasComponents/sapi/core/sapiParcellation.ts +++ b/src/atlasComponents/sapi/core/sapiParcellation.ts @@ -1,7 +1,6 @@ import { Observable, of } from "rxjs" import { SAPI } from "../sapi.service" import { SapiParcellationModel } from "../typeV3" -import { SapiQueryPriorityArg } from "../sxplrTypes" import { SAPIBase } from "./base" /** @@ -18,17 +17,16 @@ export class SAPIParcellation extends SAPIBase<PF>{ super(sapi) } - getDetail(queryParam?: SapiQueryPriorityArg): Observable<SapiParcellationModel>{ + getDetail(): Observable<SapiParcellationModel>{ return this.sapi.v3Get(`/parcellations/{parcellation_id}`, { path: { parcellation_id: this.id }, - priority: queryParam?.priority }) } - getLabelledMap(spaceId: string, queryParam?: SapiQueryPriorityArg) { - return this.sapi.getMap(this.id, spaceId, "LABELLED", queryParam) + getLabelledMap(spaceId: string) { + return this.sapi.getMap(this.id, spaceId, "LABELLED") } static Features$ = of(Object.keys(ParcellationFeatures) as PF[]) diff --git a/src/atlasComponents/sapi/core/sapiSpace.ts b/src/atlasComponents/sapi/core/sapiSpace.ts index c6e8741eaf2be6adf9b1a5e9af540c7ec33a0a1c..b19359e3cbc373699aa332abb3b06d424d7f0742 100644 --- a/src/atlasComponents/sapi/core/sapiSpace.ts +++ b/src/atlasComponents/sapi/core/sapiSpace.ts @@ -1,6 +1,6 @@ import { Observable, of, throwError } from "rxjs" import { SAPI } from '../sapi.service' -import { SxplrTemplate, SapiQueryPriorityArg } from "../sxplrTypes" +import { SxplrTemplate } from "../sxplrTypes" import { map, switchMap } from "rxjs/operators" import { SAPIBase } from "./base" @@ -44,22 +44,20 @@ export class SAPISpace extends SAPIBase<SF>{ private prefix$: Observable<string> - getModalities(param?: SapiQueryPriorityArg): Observable<FeatureResponse> { + getModalities(): Observable<FeatureResponse> { return this.prefix$.pipe( switchMap(prefix => this.sapi.httpGet<FeatureResponse>( `${prefix}/features`, null, - param )) ) } - getDetail(param?: SapiQueryPriorityArg): Observable<SxplrTemplate>{ + getDetail(): Observable<SxplrTemplate>{ return this.prefix$.pipe( switchMap(prefix => this.sapi.httpGet<SxplrTemplate>( `${prefix}`, null, - param )) ) } diff --git a/src/atlasComponents/sapi/sapi.service.ts b/src/atlasComponents/sapi/sapi.service.ts index 43eb63b63bcfccc3e6c453422694a7656fb3a088..3648dc0b6f518b7bf147b15fb809e954faff8375 100644 --- a/src/atlasComponents/sapi/sapi.service.ts +++ b/src/atlasComponents/sapi/sapi.service.ts @@ -8,12 +8,11 @@ import { getExportNehuba } from "src/util/fn"; import { MatSnackBar } from "@angular/material/snack-bar"; import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service"; import { EnumColorMapName } from "src/util/colorMaps"; -import { PRIORITY_HEADER } from "src/util/priority"; import { forkJoin, from, NEVER, Observable, of, throwError } from "rxjs"; import { SAPIFeature } from "./features"; import { environment } from "src/environments/environment" import { FeatureType, PathReturn, RouteParam, SapiRoute } from "./typeV3"; -import { BoundingBox, SxplrAtlas, SxplrParcellation, SxplrRegion, SxplrTemplate, VoiFeature, SapiQueryPriorityArg } from "./sxplrTypes"; +import { BoundingBox, SxplrAtlas, SxplrParcellation, SxplrRegion, SxplrTemplate, VoiFeature, Feature } from "./sxplrTypes"; export const SIIBRA_API_VERSION_HEADER_KEY='x-siibra-api-version' @@ -139,12 +138,11 @@ export class SAPI{ static ErrorMessage = null - getParcRegions(parcId: string, queryParam?: SapiQueryPriorityArg) { + getParcRegions(parcId: string) { const param = { query: { parcellation_id: parcId, }, - priority: queryParam?.priority } return this.v3Get("/regions", param).pipe( switchMap(resp => @@ -164,7 +162,7 @@ export class SAPI{ ) } - getMap(parcId: string, spaceId: string, mapType: "LABELLED" | "STATISTICAL", queryParam?: SapiQueryPriorityArg) { + getMap(parcId: string, spaceId: string, mapType: "LABELLED" | "STATISTICAL") { return this.v3Get("/map", { query: { map_type: mapType, @@ -175,10 +173,10 @@ export class SAPI{ } #isPaged<T>(resp: any): resp is PaginatedResponse<T>{ - if (!!resp.total) return true + if (!!resp.total || resp.total === 0) return true return false } - getV3Features<T extends FeatureType>(featureType: T, sapiParam: RouteParam<`/feature/${T}`>): Observable<PathReturn<`/feature/${T}/{feature_id}`>[]> { + getV3Features<T extends FeatureType>(featureType: T, sapiParam: RouteParam<`/feature/${T}`>): Observable<Feature[]> { const query = structuredClone(sapiParam) return this.v3Get<`/feature/${T}`>(`/feature/${featureType}`, { ...query @@ -201,7 +199,15 @@ export class SAPI{ } ) }), - catchError(() => of([])) + switchMap(features => features.length === 0 + ? of([]) + : forkJoin( + features.map(feat => translateV3Entities.translateFeature(feat) ) + ) + ), + catchError((err) => { + console.error("Error fetching features", err) + return of([])}), ) } @@ -211,12 +217,15 @@ export class SAPI{ }) } - getV3FeatureDetailWithId(id: string) { + getV3FeatureDetailWithId(id: string, params: Record<string, string> = {}) { return this.v3Get("/feature/{feature_id}", { path: { feature_id: id - } - }) + }, + query_param: params + } as any).pipe( + switchMap(val => translateV3Entities.translateFeature(val)) + ) } getFeature(featureId: string, opts: Record<string, string> = {}) { @@ -246,13 +255,10 @@ export class SAPI{ * @param sapiParam * @returns */ - v3Get<T extends SapiRoute>(route: T, sapiParam: RouteParam<T> & Partial<{ priority: number }>){ + v3Get<T extends SapiRoute>(route: T, sapiParam: RouteParam<T>){ return SAPI.BsEndpoint$.pipe( switchMap(endpoint => { const headers: Record<string, string> = {} - if (sapiParam?.priority) { - headers[PRIORITY_HEADER] = sapiParam.priority.toString() - } const { path, params } = this.v3GetRoute(route, sapiParam) return this.http.get<PathReturn<T>>( `${endpoint}${path}`, @@ -272,11 +278,8 @@ export class SAPI{ * @param sapiParam * @returns */ - httpGet<T>(url: string, params?: Record<string, string>, sapiParam?: SapiQueryPriorityArg){ + httpGet<T>(url: string, params?: Record<string, string>){ const headers: Record<string, string> = {} - if (sapiParam?.priority) { - headers[PRIORITY_HEADER] = sapiParam.priority.toString() - } return this.http.get<T>( url, { @@ -417,7 +420,7 @@ export class SAPI{ space.id, "LABELLED" ).pipe( - catchError((err, obs) => of(null)), + catchError((err, obs) => of(null as SxplrTemplate)), map(_map => _map && space) ) ) @@ -475,6 +478,27 @@ export class SAPI{ public async getTranslatedLabelledNgMap(parcellation: SxplrParcellation, template: SxplrTemplate) { if (!parcellation || !template) return {} const map = await this.getLabelledMap(parcellation, template) + + for (const regionname in map.indices) { + for (const { volume: volumeIdx, fragment, label } of map.indices[regionname]) { + const { providedVolumes } = map.volumes[volumeIdx] + if (!("neuroglancer/precomputed" in providedVolumes)) { + continue + } + const provider = providedVolumes["neuroglancer/precomputed"] + + const src = fragment + ? provider[fragment] + : provider + + const match = /https?:\/\/.*?\/(.*?)$/.exec(src) + const regionFragment = match + ? match[1] + : src + translateV3Entities.mapTPRToFrag[template.id][parcellation.id][regionname] = regionFragment + } + } + return await translateV3Entities.translateLabelledMapToNgSegLayers(map) } diff --git a/src/atlasComponents/sapi/schemaV3.ts b/src/atlasComponents/sapi/schemaV3.ts index 98036ee8f5e613ec49583d5d79cce51f1f35db6d..5bb8bbb1a33fcc50af6c1ec4dccc11d1f18b68ba 100644 --- a/src/atlasComponents/sapi/schemaV3.ts +++ b/src/atlasComponents/sapi/schemaV3.ts @@ -74,7 +74,12 @@ export interface paths { get: operations["get_all_connectivity_features_feature_RegionalConnectivity_get"] } "/feature/RegionalConnectivity/{feature_id}": { - /** Get Single Connectivity Feature */ + /** + * Get Single Connectivity Feature + * @description subject is an optional param. + * If provided, the specific matrix will be return. + * If not provided, the matrix averaged between subjects will be returned under the key _average. + */ get: operations["get_single_connectivity_feature_feature_RegionalConnectivity__feature_id__get"] } "/feature/CorticalProfile": { @@ -476,11 +481,11 @@ export interface components { /** @Type */ "@type": string /** Index */ - index: (Record<string, never>)[] + index: (string)[] /** Dtype */ dtype: string /** Columns */ - columns: (Record<string, never>)[] + columns: (string)[] /** Ndim */ ndim: number /** Data */ @@ -945,19 +950,6 @@ export interface components { /** Quantitativeoverlap */ quantitativeOverlap: components["schemas"]["QuantitativeOverlapItem"] | components["schemas"]["QuantitativeOverlapItem1"] } - /** SeriesModel */ - SeriesModel: { - /** @Type */ - "@type": string - /** Name */ - name?: string - /** Dtype */ - dtype: string - /** Index */ - index: (string | number | number)[] - /** Data */ - data: (number)[] - } /** SiibraAnchorModel */ SiibraAnchorModel: { /** @Type */ @@ -1003,6 +995,7 @@ export interface components { /** Datasets */ datasets: (components["schemas"]["EbrainsDatasetModel"])[] anchor?: components["schemas"]["SiibraAnchorModel"] + data?: components["schemas"]["DataFrameModel"] /** Unit */ unit?: string /** Boundary Positions */ @@ -1011,7 +1004,6 @@ export interface components { } /** Boundaries Mapped */ boundaries_mapped: boolean - data?: components["schemas"]["SeriesModel"] } /** SiibraParcellationModel */ SiibraParcellationModel: { @@ -1023,6 +1015,8 @@ export interface components { name: string /** Modality */ modality?: string + /** Datasets */ + datasets: (components["schemas"]["EbrainsDatasetModel"])[] /** Brainatlasversions */ brainAtlasVersions: (components["schemas"]["BrainAtlasVersionModel"])[] version?: components["schemas"]["SiibraParcellationVersionModel"] @@ -1602,11 +1596,16 @@ export interface operations { } } get_single_connectivity_feature_feature_RegionalConnectivity__feature_id__get: { - /** Get Single Connectivity Feature */ + /** + * Get Single Connectivity Feature + * @description subject is an optional param. + * If provided, the specific matrix will be return. + * If not provided, the matrix averaged between subjects will be returned under the key _average. + */ parameters: { query: { parcellation_id: string - subject: string + subject?: string type?: components["schemas"]["ConnectivityTypes"] } path: { diff --git a/src/atlasComponents/sapi/sxplrTypes.ts b/src/atlasComponents/sapi/sxplrTypes.ts index 28622c040ca66f8161688f3f9c184a138d4812e4..53320ec5645f56806c0098f88266c3020efed002 100644 --- a/src/atlasComponents/sapi/sxplrTypes.ts +++ b/src/atlasComponents/sapi/sxplrTypes.ts @@ -121,10 +121,6 @@ export type TabularFeature<T extends TabularDataType> = { data?: T[][] } & Feature - -/** - * Support types - */ -export type SapiQueryPriorityArg = { - priority: number -} +export type GenericInfo = { + name: string +} & AdditionalInfo diff --git a/src/atlasComponents/sapi/translateV3.ts b/src/atlasComponents/sapi/translateV3.ts index 63390575627568844ac47cd019858510d34a3c7d..82584e1193b23c56d4066786b2050d8af49c57c7 100644 --- a/src/atlasComponents/sapi/translateV3.ts +++ b/src/atlasComponents/sapi/translateV3.ts @@ -1,9 +1,10 @@ import { - SxplrAtlas, SxplrParcellation, SxplrTemplate, SxplrRegion, NgLayerSpec, NgPrecompMeshSpec, NgSegLayerSpec, VoiFeature, Point, TemplateDefaultImage, TThreeSurferMesh, TThreeMesh, LabelledMap, CorticalFeature + SxplrAtlas, SxplrParcellation, SxplrTemplate, SxplrRegion, NgLayerSpec, NgPrecompMeshSpec, NgSegLayerSpec, VoiFeature, Point, TemplateDefaultImage, TThreeSurferMesh, TThreeMesh, LabelledMap, CorticalFeature, Feature, TabularFeature, GenericInfo } from "./sxplrTypes" import { PathReturn } from "./typeV3" import { hexToRgb } from 'common/util' import { components } from "./schemaV3" +import { defaultdict } from "src/util/fn" class TranslateV3 { @@ -21,16 +22,30 @@ class TranslateV3 { } } + async translateDs(ds: PathReturn<"/parcellations/{parcellation_id}">['datasets'][number]): Promise<GenericInfo> { + return { + name: ds.name, + desc: ds.description, + link: ds.urls.map(v => ({ + href: v.url, + text: 'Link' + })) + } + } + #parcellationMap: Map<string, PathReturn<"/parcellations/{parcellation_id}">> = new Map() retrieveParcellation(parcellation: SxplrParcellation): PathReturn<"/parcellations/{parcellation_id}"> { return this.#parcellationMap.get(parcellation.id) } async translateParcellation(parcellation:PathReturn<"/parcellations/{parcellation_id}">): Promise<SxplrParcellation> { + const ds = await Promise.all((parcellation.datasets || []).map(ds => this.translateDs(ds))) + const { name, ...rest } = ds[0] || {} return { id: parcellation["@id"], name: parcellation.name, modality: parcellation.modality, - type: "SxplrParcellation" + type: "SxplrParcellation", + ...rest } } @@ -221,6 +236,8 @@ class TranslateV3 { return threeLabelMap } + mapTPRToFrag = defaultdict(() => defaultdict(() => defaultdict(() => null as string))) + #wkmpLblMapToNgSegLayers = new WeakMap() async translateLabelledMapToNgSegLayers(map:PathReturn<"/map">): Promise<Record<string,{layer:NgSegLayerSpec, region: LabelledMap[]}>> { if (this.#wkmpLblMapToNgSegLayers.has(map)) { @@ -355,6 +372,44 @@ class TranslateV3 { } } + async translateFeature(feat: PathReturn<"/feature/{feature_id}">): Promise<TabularFeature<number|string|number[]>|Feature> { + if (this.#isTabular(feat)) { + return await this.translateTabularFeature(feat) + } + return await this.translateBaseFeature(feat) + } + + async translateBaseFeature(feat: PathReturn<"/feature/{feature_id}">): Promise<Feature>{ + const { id, name, category, description, datasets } = feat + const dsDescs = datasets.map(ds => ds.description) + const urls = datasets.flatMap(ds => ds.urls).map(v => ({ + href: v.url, + text: 'link to dataset' + })) + return { + id, + name, + category, + desc: dsDescs[0] || description, + link: urls, + } + } + + #isTabular(feat: unknown): feat is PathReturn<"/feature/Tabular/{feature_id}"> { + return feat["@type"].includes("feature/tabular") + } + async translateTabularFeature(feat: unknown): Promise<TabularFeature<number | string| number[]>> { + if (!this.#isTabular(feat)) throw new Error(`Feature is not of tabular type`) + const superObj = await this.translateBaseFeature(feat) + const { data: _data } = feat + const { index, columns, data } = _data || {} + return { + ...superObj, + columns, + index, + data + } + } async translateCorticalProfile(feat: PathReturn<"/feature/CorticalProfile/{feature_id}">): Promise<CorticalFeature<number>> { return { diff --git a/src/atlasComponents/sapiViews/core/parcellation/parcellationDoi.pipe.ts b/src/atlasComponents/sapiViews/core/parcellation/parcellationDoi.pipe.ts index 3b624511a109dc4a9a372cefc9fa020ddd706f7b..dad016dc1eb4e383a42bd4054162783c77b4ea6d 100644 --- a/src/atlasComponents/sapiViews/core/parcellation/parcellationDoi.pipe.ts +++ b/src/atlasComponents/sapiViews/core/parcellation/parcellationDoi.pipe.ts @@ -1,6 +1,5 @@ import { Pipe, PipeTransform } from "@angular/core"; import { SxplrParcellation } from "src/atlasComponents/sapi/sxplrTypes"; -import { translateV3Entities } from "src/atlasComponents/sapi/translateV3" @Pipe({ name: 'parcellationDoiPipe', @@ -9,12 +8,6 @@ import { translateV3Entities } from "src/atlasComponents/sapi/translateV3" export class ParcellationDoiPipe implements PipeTransform { public transform(_parc: SxplrParcellation): string[] { - const parc = translateV3Entities.retrieveParcellation(_parc) - const urls = (parc?.brainAtlasVersions || []).filter( - v => v.digitalIdentifier && v.digitalIdentifier['@type'] === 'https://openminds.ebrains.eu/core/DOI' - ).map( - v => v.digitalIdentifier['@id'] as string - ) - return Array.from(new Set(urls)) + return (_parc.link || []).map(v => v.href) } } diff --git a/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.component.ts b/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.component.ts index b2230eccbe10b41aae3a535d103fa1f39f9e4c05..15de8e31fe6a57d4545f2ab50f8fbcbef914376b 100644 --- a/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.component.ts +++ b/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.component.ts @@ -6,17 +6,6 @@ import { debounceTime, distinctUntilChanged, map, startWith } from "rxjs/operato import { MatAutocompleteSelectedEvent } from "@angular/material/autocomplete"; import { SapiViewsCoreRichRegionListTemplateDirective } from "./regionListSearchTmpl.directive"; -/** - * Filter function, which determines whether the region will be included in the list of autocompleted search. - * Ideally, only the selectable regions are included in the result. - * - * @param region input region - * @returns {boolean} whether or not to include the region in the list search - */ -const filterRegionForListSearch = (region: SxplrRegion): boolean => { - return !!region.color -} - const filterRegionViaSearch = (searchTerm: string) => (region:SxplrRegion) => { return region.name.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase()) } @@ -41,8 +30,8 @@ export class SapiViewsCoreRichRegionListSearch { return this._regions } @Input('sxplr-sapiviews-core-rich-regionlistsearch-regions') - set regions(val: SxplrRegion[]) { - this._regions = val.filter(filterRegionForListSearch) + set regions(reg: SxplrRegion[]) { + this._regions = reg.filter(r => !reg.some(c => c.parentIds.includes(r.id))) } @ContentChild(SapiViewsCoreRichRegionListTemplateDirective) diff --git a/src/atlasComponents/sapiViews/features/features.module.ts b/src/atlasComponents/sapiViews/features/features.module.ts deleted file mode 100644 index c05518863875722d5a7f11dcf77938a2188c1866..0000000000000000000000000000000000000000 --- a/src/atlasComponents/sapiViews/features/features.module.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FeatureViewComponent } from './feature-view/feature-view.component'; -import { MatCardModule } from '@angular/material/card'; -import { MatIconModule } from '@angular/material/icon'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatTableModule } from "@angular/material/table" -import { MarkdownModule } from 'src/components/markdown'; -import { TransformPdToDsPipe } from './transform-pd-to-ds.pipe'; -import { SpinnerModule } from 'src/components/spinner'; - - -@NgModule({ - declarations: [ - FeatureViewComponent, - TransformPdToDsPipe, - ], - imports: [ - CommonModule, - MatCardModule, - MatIconModule, - MatDividerModule, - MarkdownModule, - MatTableModule, - SpinnerModule, - ], - exports: [ - FeatureViewComponent, - ] -}) -export class FeaturesModule { } diff --git a/src/atlasComponents/sapiViews/module.ts b/src/atlasComponents/sapiViews/module.ts index 7e62c1e29a0be3110390d263fb2bc6e0c2b7d37d..aa5faf15b444b6a3158c09c0af2d05ac2b52a4de 100644 --- a/src/atlasComponents/sapiViews/module.ts +++ b/src/atlasComponents/sapiViews/module.ts @@ -1,14 +1,11 @@ import { NgModule } from "@angular/core"; import { SapiViewsCoreModule } from "./core"; -import { FeaturesModule } from "./features/features.module"; @NgModule({ imports: [ - FeaturesModule, SapiViewsCoreModule, ], exports: [ - FeaturesModule, SapiViewsCoreModule, ] }) diff --git a/src/features/entry/entry.component.html b/src/features/entry/entry.component.html index c7fb2d5dc679952e9d9cf7714e24d7119539b7dd..b8e7b76ffc61a41837680db6baab7ed1edf26407 100644 --- a/src/features/entry/entry.component.html +++ b/src/features/entry/entry.component.html @@ -1,82 +1,84 @@ -<mat-card> - <mat-accordion> - <mat-expansion-panel *ngFor="let keyvalue of (cateogryCollections$ | async | keyvalue | isConnectivity : false)" - sxplrCategoryAcc - #categoryAcc="categoryAcc" - [ngClass]="{ - 'sxplr-d-none': !(categoryAcc.isBusy$ | async) && (categoryAcc.total$ | async) === 0 - }"> +<mat-accordion> + <mat-expansion-panel *ngFor="let keyvalue of (cateogryCollections$ | async | keyvalue | isConnectivity : false)" + sxplrCategoryAcc + #categoryAcc="categoryAcc" + [ngClass]="{ + 'sxplr-d-none': !(categoryAcc.isBusy$ | async) && (categoryAcc.total$ | async) === 0 + }"> - <mat-expansion-panel-header> + <mat-expansion-panel-header> - <mat-panel-title> - {{ keyvalue.key }} - </mat-panel-title> + <mat-panel-title> + {{ keyvalue.key }} + </mat-panel-title> + + <mat-panel-description> + <spinner-cmp *ngIf="categoryAcc.isBusy$ | async"></spinner-cmp> + <ng-template [ngIf]="categoryAcc.total$ | async" let-total> + <span> + {{ total }} + </span> + </ng-template> + </mat-panel-description> + </mat-expansion-panel-header> + + <div class="c3-outer"> + <div class="c3-inner"> - <mat-panel-description> - <spinner-cmp *ngIf="categoryAcc.isBusy$ | async"></spinner-cmp> - <ng-template [ngIf]="categoryAcc.total$ | async" let-total> - <span> - {{ total }} - </span> - </ng-template> - </mat-panel-description> - </mat-expansion-panel-header> + <mat-card class="c3 mat-elevation-z4" + *ngFor="let feature of keyvalue.value" + [ngClass]="{ + 'sxplr-d-none': (list.state$ | async) === 'noresult' + }"> + <mat-card-header> + + <mat-card-title> + <span class="category-title sxplr-white-space-nowrap"> + {{ feature.name | featureNamePipe }} + </span> + </mat-card-title> + + </mat-card-header> + - <div class="c3-outer"> - <div class="c3-inner"> - <mat-card class="c3 mat-elevation-z4" - *ngFor="let feature of keyvalue.value" - [ngClass]="{ - 'sxplr-d-none': (list.state$ | async) === 'noresult' - }"> - <mat-card-header> - <mat-card-title> - <span class="category-title sxplr-white-space-nowrap"> - {{ feature.name | featureNamePipe }} - </span> - </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" - [region]="region" - [queryParams]="queryParams | mergeObj : { type: (feature.name | featureNamePipe) }" - [featureRoute]="feature.path" - (onClickFeature)="onClickFeature($event)" - #list="featureList" - > - </sxplr-feature-list> - </mat-card-content> - </mat-card> - </div> + <mat-card-content> + <spinner-cmp *ngIf="(list.state$ | async) === 'busy'"></spinner-cmp> + <sxplr-feature-list + [template]="template" + [parcellation]="parcellation" + [region]="region" + [queryParams]="queryParams | mergeObj : { type: (feature.name | featureNamePipe) }" + [featureRoute]="feature.path" + (onClickFeature)="onClickFeature($event)" + #list="featureList" + > + </sxplr-feature-list> + + + </mat-card-content> + </mat-card> </div> - </mat-expansion-panel> - - <mat-expansion-panel sxplr-sapiviews-features-connectivity-check - #connectivityAccordion - *ngIf="(cateogryCollections$ | async | keyvalue | isConnectivity : true) as connectivity"> - <mat-expansion-panel-header> - <mat-panel-title> - {{ connectivity[0].key }} - </mat-panel-title> - </mat-expansion-panel-header> - - <sxplr-features-connectivity-browser class="pe-all flex-shrink-1" - [region]="region" - [sxplr-features-connectivity-browser-atlas]="atlas | async" - [sxplr-features-connectivity-browser-template]="template" - [sxplr-features-connectivity-browser-parcellation]="parcellation" - [accordionExpanded]="connectivityAccordion.expanded" - [types]="connectivity[0].value"> - </sxplr-features-connectivity-browser> - - </mat-expansion-panel> - - - </mat-accordion> + </div> + </mat-expansion-panel> + + <mat-expansion-panel sxplr-sapiviews-features-connectivity-check + #connectivityAccordion + *ngIf="(cateogryCollections$ | async | keyvalue | isConnectivity : true) as connectivity"> + <mat-expansion-panel-header> + <mat-panel-title> + {{ connectivity[0].key }} + </mat-panel-title> + </mat-expansion-panel-header> + + <sxplr-features-connectivity-browser class="pe-all flex-shrink-1" + [region]="region" + [sxplr-features-connectivity-browser-atlas]="atlas | async" + [sxplr-features-connectivity-browser-template]="template" + [sxplr-features-connectivity-browser-parcellation]="parcellation" + [accordionExpanded]="connectivityAccordion.expanded" + [types]="connectivity[0].value"> + </sxplr-features-connectivity-browser> -</mat-card> + </mat-expansion-panel> +</mat-accordion> diff --git a/src/features/entry/entry.component.scss b/src/features/entry/entry.component.scss index 44bcd0aaaf08d7a4741746162402595f23e08b63..666bfbb6d1f2abfabfb44a4d6fc2095f15693295 100644 --- a/src/features/entry/entry.component.scss +++ b/src/features/entry/entry.component.scss @@ -1,9 +1,3 @@ -:host > mat-card -{ - padding-left: 0; - padding-right: 0; -} - mat-list-item { text-overflow: ellipsis; diff --git a/src/features/entry/entry.component.ts b/src/features/entry/entry.component.ts index 691f65ff4e64b7b7ae427267c7b5cbad9912e07b..ad314cb5befeaefca8b22d729589e338e2d12a65 100644 --- a/src/features/entry/entry.component.ts +++ b/src/features/entry/entry.component.ts @@ -55,9 +55,9 @@ export class EntryComponent extends FeatureBase { ...(v.query_params || []), ] return [ - params.includes("space_id") === (!!template), - params.includes("parcellation_id") === (!!parcellation), - params.includes("region_id") === (!!region), + params.includes("space_id") === (!!template) && !!template, + params.includes("parcellation_id") === (!!parcellation) && !!parcellation, + params.includes("region_id") === (!!region) && !!region, ].some(val => val) }) return categoryAcc(filteredFeatures) diff --git a/src/atlasComponents/sapiViews/features/feature-view/feature-view.component.html b/src/features/feature-view/feature-view.component.html similarity index 89% rename from src/atlasComponents/sapiViews/features/feature-view/feature-view.component.html rename to src/features/feature-view/feature-view.component.html index 203d751df7c6adf7f028f84016545e126450117c..b91b00901deb47df55e74bf79da97ee14aaba437 100644 --- a/src/atlasComponents/sapiViews/features/feature-view/feature-view.component.html +++ b/src/features/feature-view/feature-view.component.html @@ -33,16 +33,16 @@ <spinner-cmp></spinner-cmp> </ng-template> - <!-- <a mat-icon-button sxplr-hide-when-local *ngFor="let url of dataset.link" [href]="url.doi | parseDoi" target="_blank"> + <a mat-icon-button sxplr-hide-when-local *ngFor="let url of feature.link" [href]="url.href" target="_blank"> <i class="fas fa-external-link-alt"></i> - </a> --> + </a> </mat-card-subtitle> </mat-card> <mat-card *ngIf="feature" class="sxplr-z-0"> <mat-card-content> <!-- TODO fix feature typing! with proper translate fn --> - <markdown-dom class="sxplr-muted" [markdown]="feature['description']"> + <markdown-dom class="sxplr-muted" [markdown]="feature.desc"> </markdown-dom> </mat-card-content> </mat-card> diff --git a/src/atlasComponents/sapiViews/features/feature-view/feature-view.component.scss b/src/features/feature-view/feature-view.component.scss similarity index 100% rename from src/atlasComponents/sapiViews/features/feature-view/feature-view.component.scss rename to src/features/feature-view/feature-view.component.scss diff --git a/src/atlasComponents/sapiViews/features/feature-view/feature-view.component.spec.ts b/src/features/feature-view/feature-view.component.spec.ts similarity index 100% rename from src/atlasComponents/sapiViews/features/feature-view/feature-view.component.spec.ts rename to src/features/feature-view/feature-view.component.spec.ts diff --git a/src/atlasComponents/sapiViews/features/feature-view/feature-view.component.ts b/src/features/feature-view/feature-view.component.ts similarity index 74% rename from src/atlasComponents/sapiViews/features/feature-view/feature-view.component.ts rename to src/features/feature-view/feature-view.component.ts index 59dda8f6107c4e2dadf173fc0bbf43b95857a72a..7c66b0270412086ae7b154581bd9a0d8632a45ec 100644 --- a/src/atlasComponents/sapiViews/features/feature-view/feature-view.component.ts +++ b/src/features/feature-view/feature-view.component.ts @@ -4,8 +4,8 @@ import { map } from 'rxjs/operators'; import { SAPI } from 'src/atlasComponents/sapi/sapi.service'; import { Feature, TabularFeature } from 'src/atlasComponents/sapi/sxplrTypes'; -function isTabularData(feature: unknown): feature is { data: TabularFeature<number|string|number[]> } { - return feature['@type'].includes("siibra-0.4/feature/tabular") +function isTabularData(feature: unknown): feature is TabularFeature<number|string|number[]> { + return !!feature['index'] && !!feature['columns'] } @Component({ @@ -32,16 +32,13 @@ export class FeatureViewComponent implements OnChanges { ngOnChanges(): void { this.tabular$.next(null) this.busy$.next(true) - this.sapi.v3Get("/feature/{feature_id}", { - path: { - feature_id: this.feature.id - } - }).subscribe( + this.sapi.getV3FeatureDetailWithId(this.feature.id).subscribe( val => { this.busy$.next(false) - - if (!isTabularData(val)) return - this.tabular$.next(val.data) + + if (isTabularData(val)) { + this.tabular$.next(val) + } }, () => this.busy$.next(false) ) diff --git a/src/features/fetch.directive.ts b/src/features/fetch.directive.ts index d020b3caf173893ca79dc9b7061f3210da7cb032..e481f1d36bfc7d748ec428debfff547f05660511 100644 --- a/src/features/fetch.directive.ts +++ b/src/features/fetch.directive.ts @@ -1,19 +1,9 @@ -import { Directive, Input, OnChanges, Output, SimpleChanges, EventEmitter } from '@angular/core'; -import { BehaviorSubject, Observable, Subject } from 'rxjs'; -import { switchMap } from 'rxjs/operators'; +import { Directive, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; import { SAPI } from 'src/atlasComponents/sapi'; import { SxplrParcellation, SxplrRegion, SxplrTemplate } from 'src/atlasComponents/sapi/sxplrTypes'; -import { FeatureType, SapiRoute } from 'src/atlasComponents/sapi/typeV3'; import { Feature, CorticalFeature, VoiFeature, TabularFeature } from "src/atlasComponents/sapi/sxplrTypes" -type ObservableOf<Obs extends Observable<unknown>> = Parameters<Obs['subscribe']>[0] extends () => void -? Parameters<Parameters<Obs['subscribe']>[0]>[0] -: never - -const b = new Subject<{ t: string }>() - -type typeOfB = ObservableOf<typeof b> - type FeatureMap = { "RegionalConnectivity": Feature "CorticalProfile": CorticalFeature<number> diff --git a/src/features/list/list.component.scss b/src/features/list/list.component.scss index 97873a44ba16b4cd541b11a786a29cf1fc979125..ad15ab8305026cc915bde506021c9b8c558aa2d3 100644 --- a/src/features/list/list.component.scss +++ b/src/features/list/list.component.scss @@ -23,4 +23,5 @@ .virtual-scroll-item { height:36px; + display: block; } diff --git a/src/features/list/list.component.ts b/src/features/list/list.component.ts index 9d81ab404dceaa2904b9435bbb0861ac1a32e3ca..cc06e706efab87233eaa7f6691deb832bec1c579 100644 --- a/src/features/list/list.component.ts +++ b/src/features/list/list.component.ts @@ -51,8 +51,7 @@ export class ListComponent extends FeatureBase { ...this.queryParams, ...query, } as any - }).pipe( - ) + }) }), catchError(() => { this.state$.next("noresult") diff --git a/src/features/module.ts b/src/features/module.ts index 958956bea943a9f27ebc2c8c0fc07d76661ce30a..1aedc1d8a8695cf13cb2b9830d05438129837f1b 100644 --- a/src/features/module.ts +++ b/src/features/module.ts @@ -15,6 +15,12 @@ import { CategoryAccDirective } from './category-acc.directive'; import { SapiViewsFeatureConnectivityModule } from "./connectivity"; import { ScrollingModule } from "@angular/cdk/scrolling"; import { MatButtonModule } from "@angular/material/button" +import { MatIconModule } from "@angular/material/icon"; +import { MatDividerModule } from "@angular/material/divider"; +import { MarkdownModule } from "src/components/markdown"; +import { MatTableModule } from "@angular/material/table"; +import { FeatureViewComponent } from "./feature-view/feature-view.component"; +import { TransformPdToDsPipe } from "./transform-pd-to-ds.pipe"; @NgModule({ imports: [ @@ -29,18 +35,25 @@ import { MatButtonModule } from "@angular/material/button" SapiViewsFeatureConnectivityModule, ScrollingModule, MatButtonModule, + MatIconModule, + MatDividerModule, + MarkdownModule, + MatTableModule, ], declarations: [ EntryComponent, ListComponent, + FeatureViewComponent, FetchDirective, CategoryAccDirective, FeatureNamePipe, + TransformPdToDsPipe, ], exports: [ EntryComponent, + FeatureViewComponent, ] }) export class FeatureModule{} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/features/transform-pd-to-ds.pipe.spec.ts b/src/features/transform-pd-to-ds.pipe.spec.ts similarity index 100% rename from src/atlasComponents/sapiViews/features/transform-pd-to-ds.pipe.spec.ts rename to src/features/transform-pd-to-ds.pipe.spec.ts diff --git a/src/atlasComponents/sapiViews/features/transform-pd-to-ds.pipe.ts b/src/features/transform-pd-to-ds.pipe.ts similarity index 100% rename from src/atlasComponents/sapiViews/features/transform-pd-to-ds.pipe.ts rename to src/features/transform-pd-to-ds.pipe.ts diff --git a/src/main.module.ts b/src/main.module.ts index a0ddf6d025324c91da5836c9b9412c5f9541e04f..735f7f66dea3d897c4c71f9186a20e7ab2cde9f4 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -167,7 +167,7 @@ import { ViewerCommonEffects } from './viewerModule'; provide: DARKTHEME, useFactory: (store: Store) => store.pipe( select(atlasSelection.selectors.selectedTemplate), - map(tmpl => !!(tmpl && tmpl["@id"] !== 'minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588')), + map(tmpl => !!(tmpl && tmpl.id !== 'minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588')), ), deps: [ Store ] }, diff --git a/src/mouseoverModule/mouseOverCvt.pipe.ts b/src/mouseoverModule/mouseOverCvt.pipe.ts index f325feacc32b30c67e39451bac74cff846782185..05be3ae63bda1d1c48d6a55ae9acf1ba927fe888 100644 --- a/src/mouseoverModule/mouseOverCvt.pipe.ts +++ b/src/mouseoverModule/mouseOverCvt.pipe.ts @@ -11,7 +11,7 @@ function render<T extends keyof TOnHoverObj>(key: T, value: TOnHoverObj[T]){ fontSet: 'fas', fontIcon: 'fa-brain' }, - text: seg.name + text: seg?.name || "Unknown" } }) } diff --git a/src/plugin/service.ts b/src/plugin/service.ts index e4fe49a725535ac36d54daf87b4f79a0aae3f698..cded33fb1c981c0478e6f3cda4755286a4c96a04 100644 --- a/src/plugin/service.ts +++ b/src/plugin/service.ts @@ -6,7 +6,8 @@ import { WidgetPortal } from "src/widget/widgetPortal/widgetPortal.component"; import { setPluginSrc, SET_PLUGIN_NAME } from "./const"; import { PluginPortal } from "./pluginPortal/pluginPortal.component"; import { environment } from "src/environments/environment" -import { startWith } from "rxjs/operators"; +import { catchError, startWith } from "rxjs/operators"; +import { of } from "rxjs"; @Injectable({ providedIn: 'root' @@ -27,7 +28,8 @@ export class PluginService { name: string iframeUrl: string }[]>(`${environment.BACKEND_URL || ''}plugins/manifests`).pipe( - startWith([]) + startWith([]), + catchError(() => of([])) ) async launchPlugin(htmlSrc: string){ diff --git a/src/routerModule/routeStateTransform.service.ts b/src/routerModule/routeStateTransform.service.ts index 86cad2fd1291c9aaff05285005e1319c605ef311..767856cdbedaf345113100c35307786efa22bf98 100644 --- a/src/routerModule/routeStateTransform.service.ts +++ b/src/routerModule/routeStateTransform.service.ts @@ -1,12 +1,11 @@ import { Injectable } from "@angular/core"; import { UrlSegment, UrlTree } from "@angular/router"; -import { forkJoin } from "rxjs"; -import { map, switchMap } from "rxjs/operators"; +import { map } from "rxjs/operators"; import { SAPI } from "src/atlasComponents/sapi"; import { translateV3Entities } from "src/atlasComponents/sapi/translateV3"; import { SxplrRegion } from "src/atlasComponents/sapi/sxplrTypes" import { atlasSelection, defaultState, MainState, plugins, userInteraction } from "src/state"; -import { getParcNgId, } from "src/viewerModule/nehuba/config.service"; +import { getParcNgId } from "src/viewerModule/nehuba/config.service"; import { decodeToNumber, encodeNumber, encodeURIFull, separator } from "./cipher"; import { TUrlAtlas, TUrlPathObj, TUrlStandaloneVolume } from "./type"; import { decodePath, encodeId, decodeId, endcodePath } from "./util"; @@ -55,25 +54,7 @@ export class RouteStateTransformSvc { }).pipe( map(val => translateV3Entities.translateParcellation(val)) ).toPromise(), - this.sapi.v3Get("/regions", { - query: { - parcellation_id: selectedParcellationId, - } - }).pipe( - switchMap(v => - this.sapi.iteratePages(v, (page) => this.sapi.v3Get("/regions", { - query: { - parcellation_id: selectedParcellationId, - page - } - })) - ), - switchMap(regions => - forkJoin( - regions.map(region => translateV3Entities.translateRegion(region)) - ) - ) - ).toPromise(), + this.sapi.getParcRegions(selectedParcellationId).toPromise(), ]) diff --git a/src/share/saneUrl/saneUrl.service.ts b/src/share/saneUrl/saneUrl.service.ts index c5d9849f760ed90b7197ef8206b53db55ab1025d..d78a8ac903089062ba3cc86ca009e627947f70c6 100644 --- a/src/share/saneUrl/saneUrl.service.ts +++ b/src/share/saneUrl/saneUrl.service.ts @@ -4,7 +4,6 @@ import { throwError } from "rxjs"; import { catchError, mapTo } from "rxjs/operators"; import { BACKENDURL } from 'src/util/constants' import { IKeyValStore, NotFoundError } from '../type' -import { DISABLE_PRIORITY_HEADER } from "src/util/priority" @Injectable({ providedIn: 'root' @@ -24,7 +23,7 @@ export class SaneUrlSvc implements IKeyValStore{ getKeyVal(key: string) { return this.http.get<Record<string, any>>( `${this.saneUrlRoot}${key}`, - { responseType: 'json', headers: { [DISABLE_PRIORITY_HEADER]: '1' } } + { responseType: 'json' } ).pipe( catchError((err, obs) => { const { status } = err @@ -40,7 +39,6 @@ export class SaneUrlSvc implements IKeyValStore{ return this.http.post( `${this.saneUrlRoot}${key}`, value, - { headers: { [DISABLE_PRIORITY_HEADER]: '1' } } ).pipe( mapTo(`${this.saneUrlRoot}${key}`) ) diff --git a/src/util/fn.ts b/src/util/fn.ts index 271c783f68237e7cce54b4b6e5ebbcd901fe9372..7e7d0549b9320205934b465cc8e36cac7d298c04 100644 --- a/src/util/fn.ts +++ b/src/util/fn.ts @@ -372,3 +372,15 @@ export function bufferUntil<T>(opts: ISwitchMapWaitFor) { ) }) } + +export function defaultdict<T>(fn: () => T): Record<string, T> { + const obj = {} + return new Proxy(obj, { + get(target, prop, rec) { + if (!(prop in target)){ + target[prop] = fn() + } + return obj[prop] + }, + }) +} diff --git a/src/util/priority.ts b/src/util/priority.ts index 24cdd94725c8774bc937c5f0098218d3e7d07151..6b290b83d1c4c0453f51bc5eefcf4971b48e26fc 100644 --- a/src/util/priority.ts +++ b/src/util/priority.ts @@ -1,10 +1,8 @@ import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" -import { interval, merge, Observable, of, Subject, timer } from "rxjs" +import { interval, merge, Observable, of, Subject, throwError, timer } from "rxjs" import { catchError, filter, finalize, map, switchMapTo, take, takeWhile } from "rxjs/operators" -export const PRIORITY_HEADER = 'x-sxplr-http-priority' - type ResultBase = { urlWithParams: string } @@ -19,25 +17,21 @@ type ErrorResult = { type Queue = { urlWithParams: string - priority: number req: HttpRequest<unknown> next: HttpHandler } -export const DISABLE_PRIORITY_HEADER = 'x-sxplr-disable-priority' - @Injectable({ providedIn: 'root' }) export class PriorityHttpInterceptor implements HttpInterceptor{ private retry = 0 - private disablePriority = false private priorityQueue: Queue[] = [] private currentJob: Set<string> = new Set() - private archive: Map<string, HttpResponse<unknown>> = new Map() + private archive: Map<string, (HttpResponse<unknown>|Error)> = new Map() private queue$: Subject<Queue> = new Subject() private result$: Subject<Result<unknown>> = new Subject() private error$: Subject<ErrorResult> = new Subject() @@ -80,6 +74,7 @@ export class PriorityHttpInterceptor implements HttpInterceptor{ }), ).subscribe(val => { if (val instanceof Error) { + this.archive.set(urlWithParams, val) this.error$.next({ urlWithParams, error: val @@ -96,40 +91,19 @@ export class PriorityHttpInterceptor implements HttpInterceptor{ }) } - updatePriority(urlWithParams: string, newPriority: number) { - - if (this.currentJob.has(urlWithParams)) return - - const foundIdx = this.priorityQueue.findIndex(v => v.urlWithParams === urlWithParams) - if (foundIdx < 0) return false - const [ item ] = this.priorityQueue.splice(foundIdx, 1) - item.priority = newPriority - - this.insert(item) - this.forceCheck$.next(true) - return true - } - private insert(obj: Queue) { - const { priority, urlWithParams, req } = obj + const { urlWithParams, req } = obj if (this.archive.has(urlWithParams)) return if (this.currentJob.has(urlWithParams)) return - obj.req = req.clone({ - headers: req.headers.delete(PRIORITY_HEADER) - }) + obj.req = req.clone() const existing = this.priorityQueue.find(q => q.urlWithParams === urlWithParams) if (existing) { - if (existing.priority < priority) { - this.updatePriority(urlWithParams, priority) - } return } - const foundIdx = this.priorityQueue.findIndex(q => q.priority <= priority) - const useIndex = foundIdx >= 0 ? foundIdx : this.priorityQueue.length - this.priorityQueue.splice(useIndex, 0, obj) + this.priorityQueue.push(obj) } intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { @@ -138,23 +112,22 @@ export class PriorityHttpInterceptor implements HttpInterceptor{ * Since the way in which serialization occurs is via path and query param... * body is not used. */ - if (this.disablePriority || req.method !== 'GET' || !!req.headers.get(DISABLE_PRIORITY_HEADER)) { - const newReq = req.clone({ - headers: req.headers.delete(DISABLE_PRIORITY_HEADER) - }) - return next.handle(newReq) + if (req.method !== 'GET') { + return next.handle(req) } const { urlWithParams } = req - if (this.archive.has(urlWithParams)) { - return of( - this.archive.get(urlWithParams).clone() - ) + const archive = this.archive.get(urlWithParams) + if (archive) { + if (archive instanceof Error) { + return throwError(archive) + } + if (archive instanceof HttpResponse) { + return of( archive.clone() ) + } } - const priority = Number(req.headers.get(PRIORITY_HEADER) || 0) const objToInsert: Queue = { - priority, req, next, urlWithParams diff --git a/src/viewerModule/module.ts b/src/viewerModule/module.ts index 597e574ea77d5f1d1a350e9ae236663d09e25f2a..96cdcfe3911b20d5c08f963a3d338dda26d834f6 100644 --- a/src/viewerModule/module.ts +++ b/src/viewerModule/module.ts @@ -30,6 +30,7 @@ import { LeapModule } from "./leap/module"; import { environment } from "src/environments/environment" import { ATPSelectorModule } from "src/atlasComponents/sapiViews/core/rich/ATPSelector"; +import { FeatureModule } from "src/features"; @NgModule({ imports: [ @@ -52,6 +53,7 @@ import { ATPSelectorModule } from "src/atlasComponents/sapiViews/core/rich/ATPSe MouseoverModule, ShareModule, ATPSelectorModule, + FeatureModule, ...(environment.ENABLE_LEAP_MOTION ? [LeapModule] : []) ], declarations: [ diff --git a/src/viewerModule/nehuba/base.service/base.service.ts b/src/viewerModule/nehuba/base.service/base.service.ts index d53c8efea87daa0fe87abdea24c36d330fd478f2..bb42ed31f608691ce4b91084927b9a44d0a8a558 100644 --- a/src/viewerModule/nehuba/base.service/base.service.ts +++ b/src/viewerModule/nehuba/base.service/base.service.ts @@ -48,11 +48,10 @@ export class BaseService { for (const { name, label } of region) { - const actualRegion = regionmap.get(name) - if (!actualRegion) { - console.warn(`region with name ${name} cannot be found, skipping`) - continue - } + const actualRegion = regionmap.get(name) || (() => { + console.log(`region with name ${name} cannot be found. Viewer may not behave properly`) + return { name, id: '', parentIds: [], type: 'SxplrRegion' } + })() const ngId = getParcNgId(atlas, template, parcellation, actualRegion) if (!returnVal[ngId]) { returnVal[ngId] = {} diff --git a/src/viewerModule/nehuba/config.service/index.ts b/src/viewerModule/nehuba/config.service/index.ts index c4587bc97f641a10dc5d27261ca6e6a59738813d..ab727ea26f32afb1cb337a21ba275c8cffcec305 100644 --- a/src/viewerModule/nehuba/config.service/index.ts +++ b/src/viewerModule/nehuba/config.service/index.ts @@ -9,7 +9,6 @@ export { export { getParcNgId, - getRegionLabelIndex, getNehubaConfig, defaultNehubaConfig, } from "./util" diff --git a/src/viewerModule/nehuba/config.service/util.ts b/src/viewerModule/nehuba/config.service/util.ts index 055c8ed531cde2c96bea43e31ca569984dd7f764..8e21802d3e504ecd25fbcf4d4388d203613ffe7d 100644 --- a/src/viewerModule/nehuba/config.service/util.ts +++ b/src/viewerModule/nehuba/config.service/util.ts @@ -7,6 +7,7 @@ import { NgConfig, RecursivePartial, } from "./type" +import { translateV3Entities } from "src/atlasComponents/sapi/translateV3" // fsaverage uses threesurfer, which, whilst do not use ngId, uses 'left' and 'right' as keys const fsAverageKeyVal = { [IDS.PARCELLATION.JBA29]: { @@ -165,8 +166,10 @@ export function getParcNgId(atlas: SxplrAtlas, tmpl: SxplrTemplate, parc: SxplrP * for JBA29 in big brain, there exist several volumes. (e.g. v1, v2, v5, interpolated, etc) */ if (tmpl.id === IDS.TEMPLATES.BIG_BRAIN && parc.id === IDS.PARCELLATION.JBA29) { - // laterality = region.hasAnnotation.visualizedIn?.['@id'] - throw new Error(`FIXME figure out what region.hasAnnotation id is`) + const frag = translateV3Entities.mapTPRToFrag[tmpl.id][parc.id][region.name] + return frag + ? `_${MultiDimMap.GetKey(frag)}` + : null } if (!laterality) { @@ -180,30 +183,6 @@ export function getParcNgId(atlas: SxplrAtlas, tmpl: SxplrTemplate, parc: SxplrP return ngId } -const labelIdxRegex = /siibra_python_ng_precomputed_labelindex:\/\/(.*?)#([0-9]+)$/ - -/** - * @deprecated - * @param _atlas - * @param tmpl - * @param parc - * @param region - */ -export function getRegionLabelIndex(_atlas: SxplrAtlas, tmpl: SxplrTemplate, parc: SxplrParcellation, region: SxplrRegion): number { - throw new Error(`TODO fix me`) - // const overwriteLabelIndex = region.hasAnnotation.inspiredBy.map(({ "@id": id }) => labelIdxRegex.exec(id)).filter(v => !!v) - // if (overwriteLabelIndex.length > 0) { - // const match = overwriteLabelIndex[0] - // const volumeId = match[1] - // const labelIndex = match[2] - // const _labelIndex = Number(labelIndex) - // if (!isNaN(_labelIndex)) return _labelIndex - // } - // const lblIdx = Number(region?.hasAnnotation?.internalIdentifier) - // if (isNaN(lblIdx)) return null - // return lblIdx -} - export const defaultNehubaConfig: NehubaConfig = { "configName": "", "globals": { @@ -300,8 +279,8 @@ export const spaceMiscInfoMap = new Map([ export function getNehubaConfig(space: SxplrTemplate): NehubaConfig { - const darkTheme = space["@id"] !== "minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588" - const { scale } = spaceMiscInfoMap.get(space["@id"]) || { scale: 1 } + const darkTheme = space.id !== "minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588" + const { scale } = spaceMiscInfoMap.get(space.id) || { scale: 1 } const backgrd = darkTheme ? [0,0,0,1] : [1,1,1,1] diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts index 0253e4eef876e510be04ab6effb78244c2d1adc1..20288e1d0ad406f02646aceba89e26ae530ac226 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts @@ -101,13 +101,13 @@ export class LayerCtrlEffects { map(([ prev, curr ]) => { const removeLayers: atlasAppearance.const.NgLayerCustomLayer[] = [] const addLayers: atlasAppearance.const.NgLayerCustomLayer[] = [] - if (prev?.["@type"].includes("feature/volume_of_interest")) { + if (prev?.["@type"]?.includes("feature/volume_of_interest")) { const prevVoi = prev as SapiSpatialFeatureModel removeLayers.push( ...LayerCtrlEffects.TransformVolumeModel(prevVoi.volume) ) } - if (curr?.["@type"].includes("feature/volume_of_interest")) { + if (curr?.["@type"]?.includes("feature/volume_of_interest")) { const currVoi = curr as SapiSpatialFeatureModel addLayers.push( ...LayerCtrlEffects.TransformVolumeModel(currVoi.volume) diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html index de9fccef730d0289016cb1eb6ea7d7974453c41a..2c7d4b7089a4d6752d667eed3594cafe6c84e567 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.template.html +++ b/src/viewerModule/viewerCmp/viewerCmp.template.html @@ -231,11 +231,11 @@ <ng-container *ngTemplateOutlet="autocompleteTmpl; context: { showTour: true }"> </ng-container> - <!-- <ng-template [ngIf]="VOI_QUERY_FLAG"> - <div *ngIf="!((selectedRegions$ | async)[0])" class="sxplr-p-2 w-100"> - <ng-container *ngTemplateOutlet="spatialFeatureListViewTmpl"></ng-container> + <ng-template [ngIf]="VOI_QUERY_FLAG"> + <div *ngIf="!((selectedRegions$ | async)[0])" class="sxplr-p-1 w-100"> + <ng-container *ngTemplateOutlet="spatialFeatureListTmpl"></ng-container> </div> - </ng-template> --> + </ng-template> </div> <!-- such a gross implementation --> @@ -977,6 +977,13 @@ </ng-template> +<ng-template #spatialFeatureListTmpl> + <sxplr-feature-entry + class="sxplr-pe-all sxplr-d-block mat-elevation-z8" + [template]="templateSelected$ | async"> + </sxplr-feature-entry> +</ng-template> + <!-- <ng-template #spatialFeatureListViewTmpl> <div *ngIf="voiQueryDirective && (voiQueryDirective.busy$ | async); else notBusyTmpl" class="fs-200"> <spinner-cmp></spinner-cmp>