diff --git a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.template.html b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.template.html index c3acb2dfb3de57075448fc2e25615408f24bf6f5..a8814bb6a035b6a5135a5167cc7730df51f8e05a 100644 --- a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.template.html +++ b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.template.html @@ -83,6 +83,7 @@ </ng-template> <sxplr-feature-entry + [template]="template" [parcellation]="parcellation" [region]="region"> </sxplr-feature-entry> diff --git a/src/environments/environment.common.ts b/src/environments/environment.common.ts index a8511509d93b2e4b80abadfc5b814006f4a56611..75284adf0db83a1213cfb5f2b94293b46330e9b5 100644 --- a/src/environments/environment.common.ts +++ b/src/environments/environment.common.ts @@ -2,7 +2,7 @@ export const environment = { GIT_HASH: 'unknown hash', VERSION: 'unknown version', - PRODUCTION: true, + PRODUCTION: false, BACKEND_URL: null, SIIBRA_API_ENDPOINTS: 'https://siibra-api-latest.apps-dev.hbp.eu/v3_0', //'https://siibra-api-stable.apps.hbp.eu/v2_0,https://siibra-api-stable.apps.jsc.hbp.eu/v2_0,https://siibra-api-stable-ns.apps.hbp.eu/v2_0', SPATIAL_TRANSFORM_BACKEND: 'https://hbp-spatial-backend.apps.hbp.eu', diff --git a/src/features/category-acc.directive.ts b/src/features/category-acc.directive.ts index b998efb0a8cd27b8260424ee8928bf7a3f2efbd9..d578d36dabcfa260822a9208e4e276637f32f117 100644 --- a/src/features/category-acc.directive.ts +++ b/src/features/category-acc.directive.ts @@ -9,8 +9,6 @@ import { ListComponent } from './list/list.component'; }) export class CategoryAccDirective implements AfterContentInit, OnDestroy { - constructor() { } - public isBusy$ = new BehaviorSubject<boolean>(false) public total$ = new BehaviorSubject<number>(0) diff --git a/tmpFeatures/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts b/src/features/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts similarity index 86% rename from tmpFeatures/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts rename to src/features/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts index 084a7ed862896c530146930484e131b488b06470..8f67e9bc822dfc07d3a954b2ac5daf0e18ce04e0 100644 --- a/tmpFeatures/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts +++ b/src/features/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts @@ -7,8 +7,8 @@ import {MockStore, provideMockStore} from "@ngrx/store/testing"; import {Observable, of} from "rxjs"; import {SAPI} from "src/atlasComponents/sapi"; import {AngularMaterialModule} from "src/sharedModules"; -import { SapiAtlasModel, SapiModalityModel, SapiParcellationFeatureModel, SapiParcellationModel } from "src/atlasComponents/sapi/type"; import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing"; +import { SxplrAtlas, SxplrParcellation } from "src/atlasComponents/sapi/sxplrTypes"; /** * injecting databrowser module is bad idea @@ -41,7 +41,7 @@ describe('ConnectivityComponent', () => { let httpTestingController: HttpTestingController; let req - const types: SapiModalityModel[] = [{ + const types: any[] = [{ name: 'StreamlineCounts', types: ['siibra/features/connectivity/streamlineCounts'] },{ @@ -52,19 +52,19 @@ describe('ConnectivityComponent', () => { types: ['siibra/features/connectivity/functional'] }] - let datasetList: SapiParcellationFeatureModel[] = [ + let datasetList: SxplrParcellation[] = [ { - '@id': 'id1', + id: 'id1', name: 'id1', cohort: 'HCP', subject: '100', '@type': 'siibra/features/connectivity/streamlineCounts', - } as SapiParcellationFeatureModel, { - '@id': 'id2', + } as any, { + id: 'id2', name: 'id2', cohort: '1000BRAINS', subject: 'average', - } as SapiParcellationFeatureModel + } as any ] beforeEach(async () => { @@ -113,11 +113,11 @@ describe('ConnectivityComponent', () => { const parcellation = 'minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-290' const endp = await SAPI.BsEndpoint$.toPromise() - component.atlas = { '@id': atlas } as SapiAtlasModel - component.parcellation = { '@id': parcellation } as SapiParcellationModel + component.atlas = { id: atlas } as SxplrAtlas + component.parcellation = { id: parcellation } as any component.types = types - const url = `${endp}/atlases/${encodeURIComponent(atlas)}/parcellations/${encodeURIComponent(parcellation)}/features?type=${component.selectedTypeId}&size=${100}&page=${1}` + const url = `${endp}/atlases/${encodeURIComponent(atlas)}/parcellations/${encodeURIComponent(parcellation)}/features?type=${component.selectedType.id}&size=${100}&page=${1}` req = httpTestingController.expectOne(`${url}`); @@ -137,9 +137,9 @@ describe('ConnectivityComponent', () => { expect(datasetList).toEqual(component.fetchedItems) }) - it('> Cohorts are set correctly', () => { - expect(datasetList.map(d => d.cohort)).toEqual(component.cohorts) - }) + // it('> Cohorts are set correctly', () => { + // expect(datasetList.map(d => d.cohort)).toEqual(component.cohorts) + // }) }) }); diff --git a/src/features/connectivity/connectivityBrowser/connectivityBrowser.component.ts b/src/features/connectivity/connectivityBrowser/connectivityBrowser.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..9ad528f325b6a0c1b109c98b010ded4e03d65a81 --- /dev/null +++ b/src/features/connectivity/connectivityBrowser/connectivityBrowser.component.ts @@ -0,0 +1,392 @@ +import {AfterViewInit, Component, ElementRef, OnDestroy, ViewChild, Input, ChangeDetectorRef} from "@angular/core"; +import {select, Store} from "@ngrx/store"; +import {fromEvent, Subscription, BehaviorSubject, Observable} from "rxjs"; +import {catchError, take, switchMap} from "rxjs/operators"; + +import { atlasAppearance } from "src/state"; +import {SAPI} from "src/atlasComponents/sapi/sapi.service"; +import { of } from "rxjs"; +import {CustomLayer} from "src/state/atlasAppearance"; +import { HttpClient } from "@angular/common/http"; +import { SxplrAtlas, SxplrParcellation, SxplrRegion, SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes"; +import { actions, selectors } from "src/state/atlasSelection"; +import { translateV3Entities } from "src/atlasComponents/sapi/translateV3"; + +@Component({ + selector: 'sxplr-features-connectivity-browser', + templateUrl: './connectivityBrowser.template.html', + styleUrls: ['./connectivityBrowser.style.scss'] +}) +export class ConnectivityBrowserComponent implements AfterViewInit, OnDestroy { + + + @Input('sxplr-features-connectivity-browser-atlas') + atlas: SxplrAtlas + + @Input('sxplr-features-connectivity-browser-template') + template: SxplrTemplate + + @Input('sxplr-features-connectivity-browser-parcellation') + parcellation: SxplrParcellation + + /** + * accordion expansion should only toggle the clearviewqueue state + * which should be the single source of truth + * setcolormaps$ is set by the presence/absence of clearviewqueue[CONNECTIVITY_NAME_PLATE] + */ + private _isFirstUpdate = true + @Input() + set accordionExpanded(flag: boolean) { + /** + * ignore first update + */ + if (this._isFirstUpdate) { + this._isFirstUpdate = false + return + } + + if (this.types.length && !this.selectedType) this.selectType(this._types[0]) + + if (flag) { + if (this.selectedSubjectIndex >= 0 && this.allRegions.length) { + this.setCustomLayer() + } else { + this.setCustomLayerOnLoad = true + } + } else { + this.removeCustomLayer() + } + + } + + private _region: SxplrRegion + @Input() + set region(region) { + this._region = region + this.regionName = region.name + } + + get region() { + return this._region + } + + + private _types: any[] = [] + @Input() + set types(val) { + this._types = val.map(t => ({...t, shortName: t.name.split('.').pop()})) + } + get types() { + return this._types + } + + + public selectedType: any + public selectedCohort: string + + public cohortDatasets: any[] + + public selectedSubjectIndex = null + public selectedCohortDatasetIndex: any + public selectedCohortSubjects: any + public fetchedItems: any[] = [] + public cohorts: string[] + public selectedView: 'subject' | 'average' | null + public averageDisabled: boolean = true + public subjectsDisabled: boolean = true + + public regionName: string + + public selectedDataset: any + public connectionsString: string + public pureConnections: { [key: string]: number } + public connectedAreas: BehaviorSubject<ConnectedArea[]> = new BehaviorSubject([]) + public noConnectivityForRegion: boolean + private subscriptions: Subscription[] = [] + public allRegions: SxplrRegion[] = [] + private regionIndexInMatrix = -1 + public defaultColorMap: Map<string, Map<number, { red: number, green: number, blue: number }>> + public matrixString: string + public fetchingPreData: boolean + public fetching: boolean + public connectivityLayerId = 'connectivity-colormap-id' + private setCustomLayerOnLoad = false + private customLayerEnabled: boolean + + public logDisabled: boolean = true + public logChecked: boolean = true + + private endpoint: string + + + @ViewChild('connectivityComponent') public connectivityComponentElement: ElementRef<any> + @ViewChild('fullConnectivityGrid') public fullConnectivityGridElement: ElementRef<any> + + constructor( + private store$: Store, + private http: HttpClient, + private changeDetectionRef: ChangeDetectorRef, + protected sapi: SAPI + ) { + SAPI.BsEndpoint$.pipe(take(1)).subscribe(en => this.endpoint = `${en}/feature/RegionalConnectivity`) + } + + public ngAfterViewInit(): void { + this.subscriptions.push( + + this.store$.pipe( + select(selectors.selectedParcAllRegions) + ).subscribe(flattenedRegions => { + this.defaultColorMap = null + this.allRegions = flattenedRegions + if (this.setCustomLayerOnLoad) { + this.setCustomLayer() + this.setCustomLayerOnLoad = false + } + }), + ) + + this.subscriptions.push( + fromEvent(this.connectivityComponentElement.nativeElement, 'connectedRegionClicked', {capture: true}) + .subscribe((e: CustomEvent) => { + this.navigateToRegion(e.detail.name) + }), + ) + } + + setCustomLayer() { + if (this.customLayerEnabled) { + this.removeCustomLayer() + } + const map = new Map<SxplrRegion, number[]>() + const areas = this.connectedAreas.value + for (const region of this.allRegions) { + const area = areas.find(a => a.name === region.name) + if (area) { + map.set(region, Object.values(area.color)) + } else { + map.set(region, [255,255,255,0.1]) + } + } + this.customLayerEnabled = true + const customLayer: CustomLayer = { + clType: 'customlayer/colormap', + id: this.connectivityLayerId, + colormap: map + } + + this.store$.dispatch( + atlasAppearance.actions.addCustomLayer({customLayer}) + ) + } + + removeCustomLayer() { + this.store$.dispatch( + atlasAppearance.actions.removeCustomLayer({ + id: this.connectivityLayerId + }) + ) + } + + clearCohortSelection() { + this.fetchedItems = [] + this.selectedCohort = null + this.cohorts = [] + this.selectedCohort = null + this.selectedCohortDatasetIndex = null + this.selectedCohortSubjects = null + + this.selectedSubjectIndex = null + } + + selectType(type) { + this.clearCohortSelection() + this.selectedType = type + this.removeCustomLayer() + this.getModality() + } + + + getModality() { + this.fetchingPreData = true + this.fetchModality().subscribe((res: any) => { + + this.fetchedItems.push(...res.items) + + this.cohorts = [...new Set(this.fetchedItems.map(item => item.cohort))] + this.fetchingPreData = false + this.changeDetectionRef.detectChanges() + this.selectCohort(this.cohorts[0]) + + }) + } + + public fetchModality = (): Observable<any> => { + const url = `${this.endpoint}?parcellation_id=${encodeURIComponent(this.parcellation.id)}&type=${encodeURIComponent(this.selectedType.shortName)}` + return this.http.get(url) + } + + selectCohort(cohort: string) { + this.selectedCohort = cohort + this.averageDisabled = !this.fetchedItems.find(s => s.cohort === this.selectedCohort && !s.subjects.length) + this.subjectsDisabled = !this.fetchedItems.find(s => s.cohort === this.selectedCohort && s.subjects.length > 0) + this.selectedView = !this.averageDisabled? 'average' : 'subject' + + this.cohortDatasets = this.fetchedItems.filter(i => this.selectedCohort === i.cohort) + + this.selectedCohortDatasetChanged(0) + } + + selectedCohortDatasetChanged(index) { + this.selectedCohortDatasetIndex = index + this.selectedCohortSubjects = this.cohortDatasets[index].subjects + + this.selectedDataset = this.cohortDatasets[index].datasets[0] + + + const keepSubject = this.selectedSubjectIndex >= 0 && this.cohortDatasets[this.selectedCohortDatasetIndex].subjects + .includes(this.selectedCohortSubjects[this.selectedSubjectIndex]) + + this.subjectSliderChanged(keepSubject? this.selectedSubjectIndex : 0) + } + + subjectSliderChanged(index: number) { + this.selectedSubjectIndex = index + this.fetchConnectivity() + } + + fetchConnectivity() { + const subject = this.selectedCohortSubjects[this.selectedSubjectIndex] + const dataset = this.cohortDatasets[this.selectedCohortDatasetIndex] + + this.fetching = true + const url = `${this.endpoint}/${dataset.id}?parcellation_id=${this.parcellation.id}&subject=${subject}&type=${this.selectedType.shortName}` + + this.http.get(url).pipe(catchError(() => { + this.fetching = false + return of(null) + })).subscribe(ds => { + this.fetching = false + this.setMatrixData(ds.matrices[subject]) + }) + } + + setMatrixData(data) { + this.regionIndexInMatrix = data.columns.findIndex(re => this.region.id === re['@id']) + + if (this.regionIndexInMatrix < 0) { + this.noConnectivityForRegion = true + this.changeDetectionRef.detectChanges() + return + } else if (this.noConnectivityForRegion) { + this.noConnectivityForRegion = false + } + + const regionProfile = data.data[this.regionIndexInMatrix] + + const maxStrength = Math.max(...regionProfile) + + this.logChecked = maxStrength > 1 + this.logDisabled = maxStrength <= 1 + const areas = regionProfile.reduce((p, c, i) => { + return { + ...p, + [data.columns[i].name]: c + } + }, {}) + + this.pureConnections = areas + this.connectionsString = JSON.stringify(areas) + this.connectedAreas.next(this.formatConnections(areas)) + + this.setCustomLayer() + this.matrixString = JSON.stringify(data.columns.map((mc, i) => ([mc.name, ...data.data[i]]))) + this.changeDetectionRef.detectChanges() + + + return data + } + + + changeLog(checked: boolean) { + this.logChecked = checked + this.connectedAreas.next(this.formatConnections(this.pureConnections)) + this.connectivityComponentElement.nativeElement.toggleShowLog() + this.setCustomLayer() + } + + navigateToRegion(regionName: string) { + this.sapi.v3Get("/regions/{region_id}", { + path: {region_id: regionName}, + query: { + parcellation_id: this.parcellation.id, + space_id: this.template.id + } + }).pipe( + switchMap(r => translateV3Entities.translateRegion(r)) + ).subscribe(region => { + const centroid = region.centroid?.loc + if (centroid) { + this.store$.dispatch( + actions.navigateTo({ + navigation: { + position: centroid.map(v => v*1e6), + }, + animation: true + }) + ) + } + }) + } + + getRegionWithName(region: string) { + return this.allRegions.find(r => r.name === region) + } + + exportConnectivityProfile() { + const a = document.querySelector('hbp-connectivity-matrix-row'); + (a as any).downloadCSV() + } + + public exportFullConnectivity() { + this.fullConnectivityGridElement?.nativeElement['downloadCSV']() + } + + public ngOnDestroy(): void { + this.removeCustomLayer() + this.subscriptions.forEach(s => s.unsubscribe()) + } + + private formatConnections(areas: { [key: string]: number }) { + const cleanedObj = Object.keys(areas) + .map(key => ({name: key, numberOfConnections: areas[key]})) + .filter(f => f.numberOfConnections > 0) + .sort((a, b) => +b.numberOfConnections - +a.numberOfConnections) + + const logMax = this.logChecked ? Math.log(cleanedObj[0].numberOfConnections) : cleanedObj[0].numberOfConnections + const colorAreas = [] + + cleanedObj.forEach((a) => { + colorAreas.push({ + ...a, + color: { + r: this.colormap_red((this.logChecked ? Math.log(a.numberOfConnections) : a.numberOfConnections) / logMax ), + g: this.colormap_green((this.logChecked ? Math.log(a.numberOfConnections) : a.numberOfConnections) / logMax ), + b: this.colormap_blue((this.logChecked ? Math.log(a.numberOfConnections) : a.numberOfConnections) / logMax ) + }, + }) + }) + return colorAreas + } + private clamp = val => Math.round(Math.max(0, Math.min(1.0, val)) * 255) + private colormap_red = x => x < 0.7? this.clamp(4.0 * x - 1.5) : this.clamp(-4.0 * x + 4.5) + private colormap_green = x => x < 0.5? this.clamp(4.0 * x - 0.5) : this.clamp(-4.0 * x + 3.5) + private colormap_blue = x => x < 0.3? this.clamp(4.0 * x + 0.5) : this.clamp(-4.0 * x + 2.5) + +} + +type ConnectedArea = { + color: {r: number, g: number, b: number} + name: string + numberOfConnections: number +} diff --git a/tmpFeatures/connectivity/connectivityBrowser/connectivityBrowser.stories.ts b/src/features/connectivity/connectivityBrowser/connectivityBrowser.stories.ts similarity index 97% rename from tmpFeatures/connectivity/connectivityBrowser/connectivityBrowser.stories.ts rename to src/features/connectivity/connectivityBrowser/connectivityBrowser.stories.ts index 870492b96ef2ef98bc6657c01e3b78652a23c945..2ce655aa057240574a16d6f4b8b1493cb03185bd 100644 --- a/tmpFeatures/connectivity/connectivityBrowser/connectivityBrowser.stories.ts +++ b/src/features/connectivity/connectivityBrowser/connectivityBrowser.stories.ts @@ -8,10 +8,10 @@ import { SAPI, SapiAtlasModel, SapiParcellationModel } from "src/atlasComponents import { getJba29Features, getHumanAtlas, getJba29 } from "src/atlasComponents/sapi/stories.base" import {SapiParcellationFeatureMatrixModel, SapiParcellationFeatureModel} from "src/atlasComponents/sapi/type" import { AngularMaterialModule } from "src/sharedModules" -import {ConnectivityBrowserComponent} from "src/atlasComponents/sapiViews/features/connectivity"; import {PARSE_TYPEDARRAY} from "src/atlasComponents/sapi/sapi.service"; import {catchError, take} from "rxjs/operators" import {of} from "rxjs"; +import { ConnectivityBrowserComponent } from "./connectivityBrowser.component" @Component({ selector: 'autoradiograph-wrapper-cmp', diff --git a/tmpFeatures/connectivity/connectivityBrowser/connectivityBrowser.style.scss b/src/features/connectivity/connectivityBrowser/connectivityBrowser.style.scss similarity index 100% rename from tmpFeatures/connectivity/connectivityBrowser/connectivityBrowser.style.scss rename to src/features/connectivity/connectivityBrowser/connectivityBrowser.style.scss diff --git a/src/features/connectivity/connectivityBrowser/connectivityBrowser.template.html b/src/features/connectivity/connectivityBrowser/connectivityBrowser.template.html new file mode 100644 index 0000000000000000000000000000000000000000..87e51584a0f6b4daf50190cf72221eec4ba185d2 --- /dev/null +++ b/src/features/connectivity/connectivityBrowser/connectivityBrowser.template.html @@ -0,0 +1,104 @@ +<div class="w-100 h-100 d-block d-flex flex-column sxplr-pb-2"> + <div> + <div *ngIf="types && types.length" + class="flex-grow-0 flex-shrink-0 d-flex flex-row flex-nowrap d-flex flex-column"> + <mat-form-field class="flex-grow-1 flex-shrink-1 w-100"> + <mat-label> + Modality + </mat-label> + <mat-select [value]="selectedType" (selectionChange)="selectType($event.value)"> + <mat-option *ngFor="let type of types" [value]="type"> + {{ type.shortName }} + </mat-option> + </mat-select> + </mat-form-field> + + <mat-form-field *ngIf="!fetchingPreData && selectedType" class="flex-grow-1 flex-shrink-1 w-100"> + <mat-label> + Cohort + </mat-label> + <mat-select [value]="selectedCohort" (selectionChange)="selectCohort($event.value)"> + <mat-option *ngFor="let cohort of cohorts" [value]="cohort"> + {{ cohort }} + </mat-option> + </mat-select> + </mat-form-field> + </div> + + <mat-radio-group *ngIf="selectedCohort" [(ngModel)]="selectedView"> + <mat-radio-button value="average" class="m-2" [disabled]="averageDisabled" color="primary"> + Average + </mat-radio-button> + <mat-radio-button value="subject" class="m-2" [disabled]="subjectsDisabled" color="primary"> + Subject + </mat-radio-button> + </mat-radio-group> + + <div *ngIf="cohortDatasets && cohortDatasets.length > 1" + class="flex-grow-0 flex-shrink-0 d-flex flex-nowrap align-items-center"> + <div class="flex-grow-1 flex-shrink-1 w-100"> + <mat-label> + Dataset + </mat-label> + <mat-slider [min]="0" [max]="cohortDatasets.length - 1" (change)="selectedCohortDatasetChanged($event.value)" + [value]="selectedCohortDatasetIndex" thumbLabel step="1" class="w-100"> + </mat-slider> + </div> + </div> + + <div *ngIf="selectedCohortDatasetIndex >= 0 && selectedCohortSubjects" + class="flex-grow-0 flex-shrink-0 d-flex flex-nowrap align-items-center"> + <div class="flex-grow-1 flex-shrink-1 w-100"> + <mat-label> + Subject + </mat-label> + <mat-slider [min]="0" [max]="selectedCohortSubjects.length - 1" + (change)="subjectSliderChanged($event.value)" [value]="selectedSubjectIndex" + thumbLabel step="1" class="w-100"> + </mat-slider> + </div> + </div> + </div> + + <div class="d-flex justify-content-center"> + <mat-spinner *ngIf="fetching"></mat-spinner> + </div> + + <div *ngIf="regionName && !fetching" + [style.visibility]="selectedCohort && (selectedSubjectIndex >= 0 || !averageDisabled)? 'visible' : 'hidden'" + class="d-flex align-items-center"> + <mat-checkbox class="mr-2" [checked]="logChecked" (change)="changeLog($event.checked)" + [disabled]="logDisabled || noConnectivityForRegion">Log 10</mat-checkbox> + <button mat-button [matMenuTriggerFor]="exportMenu" [disabled]="!connectedAreas.value"> + <i class="fas fa-download mr-2"></i> + <span>Export</span> + </button> + <button *ngIf="selectedDataset" iav-stop="mousedown click" class="icons" mat-icon-button sxplr-dialog + [sxplr-dialog-size]="null" [sxplr-dialog-data]="{ + title: selectedDataset?.name, + descMd: selectedDataset?.description + '' + (selectedDataset?.authors && selectedDataset?.authors.join()), + actions: [selectedDataset.ebrains_page] + }"> + <i class="fas fa-info"></i> + </button> + </div> + + <hbp-connectivity-matrix-row #connectivityComponent + [style.visibility]="regionName && !fetching && !noConnectivityForRegion && selectedCohort + && (selectedSubjectIndex >= 0 || !averageDisabled)? 'visible' : 'hidden'" + [region]="regionName" + [connections]="connectionsString" + show-export="true" hide-export-view="true" theme="dark"> + </hbp-connectivity-matrix-row> + <div *ngIf="noConnectivityForRegion">No connectivity for the region.</div> + + <full-connectivity-grid #fullConnectivityGrid [matrix]="matrixString" [datasetName]="selectedDataset?.name" + [datasetDescription]="selectedDataset?.description" only-export="true"> + </full-connectivity-grid> + + <mat-menu #exportMenu="matMenu"> + <button mat-menu-item [disabled]="noConnectivityForRegion" + (click)="exportConnectivityProfile()">Regional</button> + <button mat-menu-item (click)="exportFullConnectivity()">Dataset</button> + </mat-menu> +</div> \ No newline at end of file diff --git a/src/features/connectivity/excludeConnectivity.pipe.ts b/src/features/connectivity/excludeConnectivity.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..829679fa1f2fc5bd2bdcd6c82b2ccd70ed752894 --- /dev/null +++ b/src/features/connectivity/excludeConnectivity.pipe.ts @@ -0,0 +1,15 @@ +import { Pipe, PipeTransform } from "@angular/core" +import { Input } from "postcss" + +@Pipe({ + name: 'isConnectivity', + pure: true +}) +export class ExcludeConnectivityPipe implements PipeTransform { + + public transform(datasets: any[], isConnectivity: boolean): any[] { + return datasets? isConnectivity? [datasets.find(d => d.key === 'connectivity')] + : datasets.filter(d => d.key !== 'connectivity') + : null + } +} diff --git a/tmpFeatures/connectivity/index.ts b/src/features/connectivity/index.ts similarity index 100% rename from tmpFeatures/connectivity/index.ts rename to src/features/connectivity/index.ts diff --git a/tmpFeatures/connectivity/module.ts b/src/features/connectivity/module.ts similarity index 63% rename from tmpFeatures/connectivity/module.ts rename to src/features/connectivity/module.ts index d796c5f22ce7b132574eadbcda22f8a4f672d949..96f346ac8a8938f8a2405315ce91874d4a3d231d 100644 --- a/tmpFeatures/connectivity/module.ts +++ b/src/features/connectivity/module.ts @@ -1,12 +1,13 @@ import { CommonModule } from "@angular/common"; import {CUSTOM_ELEMENTS_SCHEMA, NgModule} from "@angular/core"; import { SAPI } from "src/atlasComponents/sapi"; -import {ConnectivityBrowserComponent} from "src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component"; -import {HasConnectivity} from "src/atlasComponents/sapiViews/features/connectivity/hasConnectivity.directive"; + import {AngularMaterialModule} from "src/sharedModules"; import {FormsModule} from "@angular/forms"; import { DialogModule } from "src/ui/dialogInfo"; -import { ConnectivityDoiPipe } from "./connectivityDoi.pipe"; + +import { ConnectivityBrowserComponent } from "./connectivityBrowser/connectivityBrowser.component"; +import { ExcludeConnectivityPipe } from "./excludeConnectivity.pipe"; @NgModule({ imports: [ @@ -17,12 +18,11 @@ import { ConnectivityDoiPipe } from "./connectivityDoi.pipe"; ], declarations: [ ConnectivityBrowserComponent, - HasConnectivity, - ConnectivityDoiPipe + ExcludeConnectivityPipe ], exports: [ ConnectivityBrowserComponent, - HasConnectivity + ExcludeConnectivityPipe ], providers: [ SAPI, diff --git a/src/features/entry/entry.component.html b/src/features/entry/entry.component.html index bd413fc42578e9e81770dcfb62a677fcd70da7f8..97802ff5f431881e22e9e20ba78169cd48db088f 100644 --- a/src/features/entry/entry.component.html +++ b/src/features/entry/entry.component.html @@ -1,6 +1,6 @@ <mat-card> <mat-accordion> - <mat-expansion-panel *ngFor="let keyvalue of (cateogryCollections$ | async | keyvalue)" + <mat-expansion-panel *ngFor="let keyvalue of (cateogryCollections$ | async | keyvalue | isConnectivity : false)" sxplrCategoryAcc #categoryAcc="categoryAcc" [ngClass]="{ @@ -23,23 +23,18 @@ <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> <sxplr-feature-list [template]="template" @@ -51,15 +46,35 @@ #list="featureList" > </sxplr-feature-list> - <spinner-cmp *ngIf="(list.state$ | async) === 'busy'"></spinner-cmp> - </mat-card-content> </mat-card> </div> </div> </mat-expansion-panel> - </mat-accordion> + + <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> </mat-card> diff --git a/src/features/entry/entry.component.ts b/src/features/entry/entry.component.ts index e2bb436d012072e10c44060a60f4d8f4c2bf7096..691f65ff4e64b7b7ae427267c7b5cbad9912e07b 100644 --- a/src/features/entry/entry.component.ts +++ b/src/features/entry/entry.component.ts @@ -5,6 +5,7 @@ import { SAPI } from 'src/atlasComponents/sapi'; import { Feature } from 'src/atlasComponents/sapi/sxplrTypes'; import { FeatureBase } from '../base'; import * as userInteraction from "src/state/userInteraction" +import { atlasSelection } from 'src/state'; const categoryAcc = <T extends Record<string, unknown>>(categories: T[]) => { const returnVal: Record<string, T[]> = {} @@ -31,6 +32,8 @@ export class EntryComponent extends FeatureBase { super() } + public atlas = this.store.select(atlasSelection.selectors.selectedAtlas) + private featureTypes$ = this.sapi.v3Get("/feature/_types", {}).pipe( switchMap(resp => this.sapi.iteratePages( diff --git a/src/features/fetch.directive.ts b/src/features/fetch.directive.ts index eb40e4fd0b1c5bd5c8f33156272eab8598f7c3a2..d020b3caf173893ca79dc9b7061f3210da7cb032 100644 --- a/src/features/fetch.directive.ts +++ b/src/features/fetch.directive.ts @@ -6,7 +6,7 @@ import { SxplrParcellation, SxplrRegion, SxplrTemplate } from 'src/atlasComponen 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 Function +type ObservableOf<Obs extends Observable<unknown>> = Parameters<Obs['subscribe']>[0] extends () => void ? Parameters<Parameters<Obs['subscribe']>[0]>[0] : never diff --git a/src/features/module.ts b/src/features/module.ts index 16214d8b14c72d81c28ffc8d3b1e5cc74eace3d0..7de3d6ea36a0404f869ca7a8a0b8663815b1e750 100644 --- a/src/features/module.ts +++ b/src/features/module.ts @@ -12,6 +12,7 @@ import { FeatureNamePipe } from "./featureName.pipe"; import { FetchDirective } from "./fetch.directive"; import { ListComponent } from './list/list.component'; import { CategoryAccDirective } from './category-acc.directive'; +import { SapiViewsFeatureConnectivityModule } from "./connectivity"; @NgModule({ imports: [ @@ -23,6 +24,7 @@ import { CategoryAccDirective } from './category-acc.directive'; MatTooltipModule, UtilModule, MatRippleModule, + SapiViewsFeatureConnectivityModule ], declarations: [ EntryComponent, diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts index 39ddf875d79ecfdec692831dfacfaef290b6505e..a12d8fcfd2ad87cb8c2e545f7c43e657b4fcd70c 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts @@ -26,9 +26,9 @@ export class LayerCtrlEffects { * TODO implement */ throw new Error(`IMPLEMENT ME`) - for (const volumeFormat in volumeModel.providedVolumes) { + // for (const volumeFormat in volumeModel.providedVolumes) { - } + // } return [] } diff --git a/tmpFeatures/connectivity/connectivityBrowser/connectivityBrowser.component.ts b/tmpFeatures/connectivity/connectivityBrowser/connectivityBrowser.component.ts deleted file mode 100644 index 579e34570f4bb3d2abf69fe7af7d60ea9eefe433..0000000000000000000000000000000000000000 --- a/tmpFeatures/connectivity/connectivityBrowser/connectivityBrowser.component.ts +++ /dev/null @@ -1,417 +0,0 @@ -import {AfterViewInit, Component, ElementRef, OnDestroy, ViewChild, Input, ChangeDetectorRef} from "@angular/core"; -import {select, Store} from "@ngrx/store"; -import {fromEvent, Subscription, BehaviorSubject, Observable} from "rxjs"; -import {catchError, take} from "rxjs/operators"; -import { - SAPI, - SapiAtlasModel, - SapiParcellationModel, - SapiRegionModel -} from "src/atlasComponents/sapi"; -import { atlasAppearance, atlasSelection } from "src/state"; -import {PARSE_TYPEDARRAY} from "src/atlasComponents/sapi/sapi.service"; -import {SapiModalityModel, SapiParcellationFeatureMatrixModel, SapiParcellationFeatureModel} from "src/atlasComponents/sapi/type"; -import { of } from "rxjs"; -import {CustomLayer} from "src/state/atlasAppearance"; -import { HttpClient } from "@angular/common/http"; - -@Component({ - selector: 'sxplr-sapiviews-features-connectivity-browser', - templateUrl: './connectivityBrowser.template.html', - styleUrls: ['./connectivityBrowser.style.scss'] -}) -export class ConnectivityBrowserComponent implements AfterViewInit, OnDestroy { - - @Input('sxplr-sapiviews-features-connectivity-browser-atlas') - atlas: SapiAtlasModel - - @Input('sxplr-sapiviews-features-connectivity-browser-parcellation') - parcellation: SapiParcellationModel - - /** - * accordion expansion should only toggle the clearviewqueue state - * which should be the single source of truth - * setcolormaps$ is set by the presence/absence of clearviewqueue[CONNECTIVITY_NAME_PLATE] - */ - private _isFirstUpdate = true - - - @Input() - set accordionExpanded(flag: boolean) { - /** - * ignore first update - */ - if (this._isFirstUpdate) { - this._isFirstUpdate = false - return - } - - if (flag) { - if (this.selectedSubjectDatasetIndex >= 0 && this.allRegions.length) { - this.setCustomLayer() - } else { - this.setCustomLayerOnLoad = true - } - } else { - this.removeCustomLayer() - } - - } - - public selectedType: string - public selectedTypeId: string - public selectedCohort: string - public cohortSubjects: string[] - public selectedSubjectIndex: number - public selectedSubjectsDatasets: string[] - public selectedSubjectDatasetIndex: number - public fetchedItems: SapiParcellationFeatureModel[] = [] - public cohorts: string[] - public selectedView: 'subject' | 'average' | null - public averageDisabled: boolean = true - public subjectsDisabled: boolean = true - - @Input() - set region(val) { - const newRegionName = val && val.name - - if (val.status - && !val.name.includes('left hemisphere') - && !val.name.includes('right hemisphere')) { - this.regionHemisphere = val.status - } - - this.regionName = newRegionName - } - - public regionName: string - public regionHemisphere: string = null - public selectedDataset: SapiParcellationFeatureModel - public connectionsString: string - public pureConnections: { [key: string]: number } - public connectedAreas: BehaviorSubject<ConnectedArea[]> = new BehaviorSubject([]) - public noConnectivityForRegion: boolean - private subscriptions: Subscription[] = [] - public allRegions: SapiRegionModel[] = [] - private regionIndexInMatrix = -1 - public defaultColorMap: Map<string, Map<number, { red: number, green: number, blue: number }>> - public matrixString: string - public fetching: boolean - public connectivityLayerId = 'connectivity-colormap-id' - private setCustomLayerOnLoad = false - private customLayerEnabled: boolean - - public logDisabled: boolean = true - public logChecked: boolean = true - - private _types: SapiModalityModel[] = [] - @Input() - set types(val) { - this._types = val - if (val && val.length) this.selectType(val[0].name) - } - get types() { - return this._types - } - - @ViewChild('connectivityComponent', {read: ElementRef}) public connectivityComponentElement: ElementRef<any> - @ViewChild('fullConnectivityGrid') public fullConnectivityGridElement: ElementRef<any> - - constructor( - private store$: Store, - private sapi: SAPI, - private http: HttpClient, - private changeDetectionRef: ChangeDetectorRef, - ) {} - - public ngAfterViewInit(): void { - this.subscriptions.push( - - this.store$.pipe( - select(atlasSelection.selectors.selectedParcAllRegions) - ).subscribe(flattenedRegions => { - this.defaultColorMap = null - this.allRegions = flattenedRegions - if (this.setCustomLayerOnLoad) { - this.setCustomLayer() - this.setCustomLayerOnLoad = false - } - }), - ) - - this.subscriptions.push( - fromEvent(this.connectivityComponentElement?.nativeElement, 'customToolEvent', {capture: true}) - .subscribe((e: CustomEvent) => { - if (e.detail.name === 'export csv') { - // ToDo Fix in future to use component - const a = document.querySelector('hbp-connectivity-matrix-row'); - (a as any).downloadCSV() - } - }), - fromEvent(this.connectivityComponentElement?.nativeElement, 'connectedRegionClicked', {capture: true}) - .subscribe((e: CustomEvent) => { - this.navigateToRegion(this.getRegionWithName(e.detail.name)) - }), - ) - } - - setCustomLayer() { - if (this.customLayerEnabled) { - this.removeCustomLayer() - } - const map = new Map<SapiRegionModel, number[]>() - const areas = this.connectedAreas.value - for (const region of this.allRegions) { - const area = areas.find(a => a.name === region.name) - if (area) { - map.set(region, Object.values(area.color)) - } else { - map.set(region, [255,255,255,0.1]) - } - } - this.customLayerEnabled = true - const customLayer: CustomLayer = { - clType: 'customlayer/colormap', - id: this.connectivityLayerId, - colormap: map - } - - this.store$.dispatch( - atlasAppearance.actions.addCustomLayer({customLayer}) - ) - } - - removeCustomLayer() { - this.store$.dispatch( - atlasAppearance.actions.removeCustomLayer({ - id: this.connectivityLayerId - }) - ) - } - - clearCohortSelection() { - this.fetchedItems = [] - this.selectedCohort = null - this.cohorts = [] - this.selectedCohort = null - this.cohortSubjects = [] - this.selectedSubjectIndex = null - this.selectedSubjectsDatasets = null - this.selectedSubjectDatasetIndex = null - } - - selectType(typeName) { - this.clearCohortSelection() - this.selectedType = typeName - this.selectedTypeId = this.types.find(t => t.name === typeName).types[0] - - this.removeCustomLayer() - - this.getModality() - } - - - getModality(size: number = 100, page: number = 1) { - this.fetching = true - this.fetchModality(size, page).subscribe((res: any) => { - - this.fetchedItems.push(...res.items) - - if (res.total > size*page) { - this.getModality(100, page+1) - } else { - this.cohorts = [...new Set(this.fetchedItems.map(item => item.cohort))] - this.fetching = false - this.changeDetectionRef.detectChanges() - this.selectCohort(this.cohorts[0]) - } - }) - } - - public fetchModality = (size: number, page: number): Observable<any> => { - let endp - SAPI.BsEndpoint$.pipe(take(1)).subscribe(en => endp = en) - return this.http.get(`${endp}/atlases/${encodeURIComponent(this.atlas['@id'])}/parcellations/${encodeURIComponent(this.parcellation['@id'])}/features?type=${this.selectedTypeId}&size=${size}&page=${page}`,) - .pipe(take(1)) - } - - selectCohort(cohort: string) { - this.selectedCohort = cohort - this.averageDisabled = !this.fetchedItems.find(s => s.cohort === this.selectedCohort && s.subject === 'average') - this.subjectsDisabled = !this.fetchedItems.find(s => s.cohort === this.selectedCohort && s.subject !== 'average') - this.selectedView = !this.averageDisabled? 'average' : 'subject' - this.cohortSubjects = [...new Set( - this.fetchedItems - .filter(i => this.selectedView === 'average'? i.subject === 'average' : i.subject !== 'average') - .map(item => item.subject) - )] - this.subjectSliderChanged(0) - } - - subjectSliderChanged(index: number) { - this.selectedSubjectIndex = index - this.selectedSubjectsDatasets = this.fetchedItems - .filter(fi => fi.cohort === this.selectedCohort && fi.subject === this.cohortSubjects[this.selectedSubjectIndex]) - .map(i => i['@id']) - - this.selectedSubjectDatasetIndex = 0 - this.loadSubjectConnectivity() - } - - subjectDatasetSliderChanged(index) { - this.selectedSubjectDatasetIndex = index - this.loadSubjectConnectivity() - } - - loadSubjectConnectivity() { - this.fetching = true - this.fetchConnectivity(this.selectedSubjectsDatasets[this.selectedSubjectDatasetIndex]) - } - - // ToDo this temporary fix is for the bug existing on siibra api https://github.com/FZJ-INM1-BDA/siibra-api/issues/100 - private fixDatasetFormat = (ds) => ds.name.includes('{')? ({ - ...ds, - ...JSON.parse(ds.name.substring(ds.name.indexOf('{')).replace(/'/g, '"')) - }) : ds - - fetchConnectivity(datasetId=null) { - const parcellation = this.sapi.getParcellation(this.atlas["@id"], this.parcellation["@id"]) - if (parcellation) { - parcellation.getFeatureInstance(datasetId || this.selectedDataset['@id']) - .pipe(catchError(() => { - this.fetching = false - return of(null) - })) - .subscribe(ds => { - this.selectedDataset = this.fixDatasetFormat(ds) - this.setMatrixData(ds) - this.fetching = false - }) - } - } - - // ToDo need to be fixed on configuration side - fixHemisphereNaming(area: string) { - if (area.includes(' - left hemisphere')) { - return area.replace('- left hemisphere', 'left') - } else if (area.includes(' - right hemisphere')) { - return area.replace('- right hemisphere', 'right') - } else { - return area - } - } - - setMatrixData(data) { - const matrixData = data as SapiParcellationFeatureMatrixModel - - this.regionIndexInMatrix = (matrixData.columns as Array<string>).findIndex(md => { - return this.fixHemisphereNaming(md) === this.regionName - }) - - if (this.regionIndexInMatrix < 0) { - this.fetching = false - this.noConnectivityForRegion = true - this.changeDetectionRef.detectChanges() - return - } else if (this.noConnectivityForRegion) { - this.noConnectivityForRegion = false - } - this.sapi.processNpArrayData<PARSE_TYPEDARRAY.RAW_ARRAY>(matrixData.matrix, PARSE_TYPEDARRAY.RAW_ARRAY) - .then(matrix => { - const regionProfile = matrix.rawArray[this.regionIndexInMatrix] - - const maxStrength = Math.max(...regionProfile) - this.logChecked = maxStrength > 1 - this.logDisabled = maxStrength <= 1 - - const areas = regionProfile.reduce((p, c, i) => { - return { - ...p, - [this.fixHemisphereNaming(matrixData.columns[i])]: c - } - }, {}) - this.pureConnections = areas - - this.connectionsString = JSON.stringify(areas) - this.connectedAreas.next(this.formatConnections(areas)) - this.setCustomLayer() - - this.matrixString = JSON.stringify(matrixData.columns.map((mc, i) => ([mc, ...matrix.rawArray[i]]))) - this.changeDetectionRef.detectChanges() - - }) - } - - - changeLog(checked: boolean) { - this.logChecked = checked - this.connectedAreas.next(this.formatConnections(this.pureConnections)) - this.connectivityComponentElement.nativeElement.toggleShowLog() - this.setCustomLayer() - } - - //ToDo bestViewPoint is null for the most cases - navigateToRegion(region: SapiRegionModel) { - const regionCentroid = this.region.hasAnnotation?.bestViewPoint?.coordinates - if (regionCentroid) - this.store$.dispatch( - atlasSelection.actions.navigateTo({ - navigation: { - position: regionCentroid.map(v => v.value*1e6), - }, - animation: true - }) - ) - } - - getRegionWithName(region: string) { - return this.allRegions.find(r => r.name === region) - } - - exportConnectivityProfile() { - const a = document.querySelector('hbp-connectivity-matrix-row'); - (a as any).downloadCSV() - } - - public exportFullConnectivity() { - this.fullConnectivityGridElement?.nativeElement['downloadCSV']() - } - - public ngOnDestroy(): void { - this.removeCustomLayer() - this.subscriptions.forEach(s => s.unsubscribe()) - } - - private formatConnections(areas: { [key: string]: number }) { - const cleanedObj = Object.keys(areas) - .map(key => ({name: key, numberOfConnections: areas[key]})) - .filter(f => f.numberOfConnections > 0) - .sort((a, b) => +b.numberOfConnections - +a.numberOfConnections) - - const logMax = this.logChecked ? Math.log(cleanedObj[0].numberOfConnections) : cleanedObj[0].numberOfConnections - const colorAreas = [] - - cleanedObj.forEach((a) => { - colorAreas.push({ - ...a, - color: { - r: this.colormap_red((this.logChecked ? Math.log(a.numberOfConnections) : a.numberOfConnections) / logMax ), - g: this.colormap_green((this.logChecked ? Math.log(a.numberOfConnections) : a.numberOfConnections) / logMax ), - b: this.colormap_blue((this.logChecked ? Math.log(a.numberOfConnections) : a.numberOfConnections) / logMax ) - }, - }) - }) - return colorAreas - } - private clamp = val => Math.round(Math.max(0, Math.min(1.0, val)) * 255) - private colormap_red = x => x < 0.7? this.clamp(4.0 * x - 1.5) : this.clamp(-4.0 * x + 4.5) - private colormap_green = x => x < 0.5? this.clamp(4.0 * x - 0.5) : this.clamp(-4.0 * x + 3.5) - private colormap_blue = x => x < 0.3? this.clamp(4.0 * x + 0.5) : this.clamp(-4.0 * x + 2.5) - -} - -type ConnectedArea = { - color: {r: number, g: number, b: number} - name: string - numberOfConnections: number -} diff --git a/tmpFeatures/connectivity/connectivityBrowser/connectivityBrowser.template.html b/tmpFeatures/connectivity/connectivityBrowser/connectivityBrowser.template.html deleted file mode 100644 index df2a233d6349bfe8de3b4e06ebca492e2d4695fa..0000000000000000000000000000000000000000 --- a/tmpFeatures/connectivity/connectivityBrowser/connectivityBrowser.template.html +++ /dev/null @@ -1,131 +0,0 @@ -<div class="w-100 h-100 d-block d-flex flex-column sxplr-pb-2"> - <div> - <div *ngIf="types && types.length" - class="flex-grow-0 flex-shrink-0 d-flex flex-row flex-nowrap d-flex flex-column"> - <mat-form-field class="flex-grow-1 flex-shrink-1 w-100"> - <mat-label> - Modality - </mat-label> - - <mat-select - [value]="selectedType" - (selectionChange)="selectType($event.value)"> - <mat-option - *ngFor="let type of types" - [value]="type.name"> - {{ type.name }} - </mat-option> - </mat-select> - </mat-form-field> - - <mat-form-field *ngIf="!fetching && selectedType" class="flex-grow-1 flex-shrink-1 w-100"> - <mat-label> - Cohort - </mat-label> - - <mat-select - [value]="selectedCohort" - (selectionChange)="selectCohort($event.value)"> - <mat-option - *ngFor="let cohort of cohorts" - [value]="cohort"> - {{ cohort }} - </mat-option> - </mat-select> - </mat-form-field> - </div> - - <mat-radio-group *ngIf="selectedCohort" [(ngModel)]="selectedView"> - <mat-radio-button value="average" class="m-2" [disabled]="averageDisabled" color="primary"> - Average - </mat-radio-button> - <mat-radio-button value="subject" class="m-2" [disabled]="subjectsDisabled" color="primary"> - Subject - </mat-radio-button> - </mat-radio-group> - - <div *ngIf="selectedView !== 'average' && selectedCohort && cohortSubjects" - class="flex-grow-0 flex-shrink-0 d-flex flex-column"> - <div class="flex-grow-1 flex-shrink-1 w-100"> - <mat-label> - Subject - </mat-label> - <mat-slider [min]="0" - [max]="cohortSubjects.length - 1" - (change)="subjectSliderChanged($event.value)" - [value]="selectedSubjectIndex" - thumbLabel - step="1" - class="w-100"> - </mat-slider> - </div> - - <div *ngIf="selectedSubjectsDatasets && selectedSubjectsDatasets.length > 1" - class="flex-grow-0 flex-shrink-0 d-flex flex-nowrap align-items-center"> - <div class="flex-grow-1 flex-shrink-1 w-100"> - <mat-label> - Dataset - </mat-label> - <mat-slider [min]="0" - [max]="selectedSubjectsDatasets.length - 1" - (change)="subjectDatasetSliderChanged($event.value)" - [value]="selectedSubjectDatasetIndex" - thumbLabel - step="1" - class="w-100"> - </mat-slider> - </div> - </div> - </div> - - </div> - - <div class="d-flex justify-content-center"> - <mat-spinner *ngIf="fetching"></mat-spinner> - </div> - - <div *ngIf="regionName && !fetching" - [style.visibility]="selectedCohort && (selectedSubjectDatasetIndex >= 0 || !averageDisabled)? 'visible' : 'hidden'" - class="d-flex align-items-center"> - <mat-checkbox class="mr-2" - [checked]="logChecked" - (change)="changeLog($event.checked)" - [disabled]="logDisabled || noConnectivityForRegion">Log 10</mat-checkbox> - <button mat-button [matMenuTriggerFor]="exportMenu" - [disabled]="!connectedAreas.value"> - <i class="fas fa-download mr-2"></i> - <span>Export</span> - </button> - <button *ngIf="selectedDataset" iav-stop="mousedown click" class="icons" mat-icon-button sxplr-dialog [sxplr-dialog-size]="null" - [sxplr-dialog-data]="{ - title: selectedDataset?.name, - descMd: selectedDataset?.description + '' + selectedDataset?.authors.join(), - actions: selectedDataset | connectivityDoiPipe - }"> - <i class="fas fa-info"></i> - </button> - </div> - - <hbp-connectivity-matrix-row - #connectivityComponent - [style.visibility]="selectedCohort && (selectedSubjectDatasetIndex >= 0 || !averageDisabled)? 'visible' : 'hidden'" - *ngIf="regionName && !fetching && !noConnectivityForRegion" - [region]="regionName + (regionHemisphere? ' - ' + regionHemisphere : '')" - [connections]="connectionsString" - show-export="true" hide-export-view="true" - theme="dark"> - </hbp-connectivity-matrix-row> - <div *ngIf="noConnectivityForRegion">No connectivity for the region.</div> - - <full-connectivity-grid #fullConnectivityGrid - [matrix]="matrixString" - [datasetName]="selectedDataset?.name" - [datasetDescription]="selectedDataset?.description" - only-export="true"> - </full-connectivity-grid> - - <mat-menu #exportMenu="matMenu"> - <button mat-menu-item [disabled]="noConnectivityForRegion" (click)="exportConnectivityProfile()">Regional</button> - <button mat-menu-item (click)="exportFullConnectivity()">Dataset</button> - </mat-menu> -</div> diff --git a/tmpFeatures/connectivity/connectivityDoi.pipe.ts b/tmpFeatures/connectivity/connectivityDoi.pipe.ts deleted file mode 100644 index ee527989cefa890fb8af7f1cf2fd8bc52411912b..0000000000000000000000000000000000000000 --- a/tmpFeatures/connectivity/connectivityDoi.pipe.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core" -import { SapiParcellationFeatureModel } from "src/atlasComponents/sapi/type" - -@Pipe({ - name: 'connectivityDoiPipe', - pure: true - }) - - export class ConnectivityDoiPipe implements PipeTransform { - public transform(dataset: SapiParcellationFeatureModel): string[] { - const url = `https://search.kg.ebrains.eu/instances/${dataset['dataset_id']}` - return [url] - } - } - \ No newline at end of file diff --git a/tmpFeatures/connectivity/hasConnectivity.directive.ts b/tmpFeatures/connectivity/hasConnectivity.directive.ts deleted file mode 100644 index 89168b6ada70caa0ac9de83c32edf9a2f0c0d856..0000000000000000000000000000000000000000 --- a/tmpFeatures/connectivity/hasConnectivity.directive.ts +++ /dev/null @@ -1,106 +0,0 @@ -import {Directive, Input, OnDestroy} from "@angular/core"; -import {Subscription} from "rxjs"; -import {map, take} from "rxjs/operators"; -import {SAPI} from "src/atlasComponents/sapi/sapi.service"; -import { - SapiAtlasModel, SapiModalityModel, - SapiParcellationFeatureModel, - SapiParcellationModel, - SapiRegionModel -} from "src/atlasComponents/sapi/type"; - -@Directive({ - selector: '[sxplr-sapiviews-features-connectivity-check]', - exportAs: 'hasConnectivityDirective' -}) - -export class HasConnectivity implements OnDestroy { - - private subscriptions: Subscription[] = [] - - @Input('sxplr-sapiviews-features-connectivity-check-atlas') - atlas: SapiAtlasModel - - @Input('sxplr-sapiviews-features-connectivity-check-parcellation') - parcellation: SapiParcellationModel - - private _region: SapiRegionModel - - @Input() - set region(val: SapiRegionModel) { - this._region = val - if (val) { - if (!this.connectivityModalities.length) { - this.waitForModalities = true - } else { - this.checkConnectivity() - } - } else { - this.connectivityNumber = 0 - } - } - - get region() { - return this._region - } - - public hasConnectivity = false - public connectivityNumber = 0 - - private connectivityModalities: SapiModalityModel[] = [] - private waitForModalities = false - public defaultProfile: DefaultProfile - public availableModalities: SapiModalityModel[] = [] - public numberOfDatasets: number = 0 - - constructor(private sapi: SAPI) { - this.getModalities() - } - - getModalities() { - this.sapi.getModalities() - .pipe(map((mod: SapiModalityModel[]) => mod.filter((m: SapiModalityModel) => m.types && m.types.find(t => t.includes('siibra/features/connectivity'))))) - .subscribe(modalities => { - this.connectivityModalities = modalities - if (this.waitForModalities) { - this.waitForModalities = false - this.checkConnectivity() - } - - }) - } - - private checkConnectivity() { - if (this.region.name) { - this.connectivityModalities.forEach(m => { - const type = m.types[0] - this.sapi.getParcellation(this.atlas["@id"], this.parcellation["@id"]) - .getFeatures({type, page: 1, size: 1}).pipe(take(1)) - .subscribe((res: SapiParcellationFeatureModel[] | any) => { - if (res && res.items) { - this.availableModalities.push(m) - const firstDataset = res.items[0] - - if (firstDataset) { - this.hasConnectivity = true - } else { - this.hasConnectivity = false - this.connectivityNumber = 0 - } - } - }) - }) - } - } - - ngOnDestroy(){ - while (this.subscriptions.length > 0) this.subscriptions.pop().unsubscribe() - } -} - -type DefaultProfile = { - type: string - selectedDataset: SapiParcellationFeatureModel - matrix: SapiParcellationFeatureModel - numberOfDatasets: number -} \ No newline at end of file diff --git a/tmpFeatures/module.ts b/tmpFeatures/module.ts index 3df348c72f79be4b53c3050e30627c896e8b93f2..54651dc2803d82c80fb2f025d45dc3a0cbe11fda 100644 --- a/tmpFeatures/module.ts +++ b/tmpFeatures/module.ts @@ -9,7 +9,6 @@ import { FeatureBadgeFlagPipe } from "./featureBadgeFlag.pipe" import { FeatureBadgeNamePipe } from "./featureBadgeName.pipe" import * as ieeg from "./ieeg" import * as receptor from "./receptors" -import {SapiViewsFeatureConnectivityModule} from "src/atlasComponents/sapiViews/features/connectivity"; import * as voi from "./voi" import { OrderFilterFeaturesPipe } from "./orderFilterFeatureList.pipe" @@ -28,7 +27,6 @@ const { SapiViewsFeaturesVoiModule } = voi SxplrSapiViewsFeaturesIeegModule, AngularMaterialModule, SapiViewsFeaturesVoiModule, - SapiViewsFeatureConnectivityModule, ], declarations: [ FeatureEntryCmp, @@ -49,7 +47,6 @@ const { SapiViewsFeaturesVoiModule } = voi FeatureEntryCmp, SapiViewsFeaturesEntryListItem, SapiViewsFeaturesVoiModule, - SapiViewsFeatureConnectivityModule, OrderFilterFeaturesPipe, ] })