diff --git a/src/ui/databrowserModule/databrowser.service.ts b/src/ui/databrowserModule/databrowser.service.ts index 65be98caebc16638f21d51d78a1f54469cd716a7..24b811e80dc8983d10183d068677123e2a8c3473 100644 --- a/src/ui/databrowserModule/databrowser.service.ts +++ b/src/ui/databrowserModule/databrowser.service.ts @@ -4,8 +4,9 @@ import { ViewerConfiguration } from "src/services/state/viewerConfig.store"; import { SELECT_REGIONS, extractLabelIdx, CHANGE_NAVIGATION, DataEntry, File, safeFilter, isDefined, getLabelIndexMap, FETCHED_DATAENTRIES, SELECT_PARCELLATION, ADD_NG_LAYER, NgViewerStateInterface, REMOVE_NG_LAYER } from "src/services/stateStore.service"; import { WidgetServices } from "src/atlasViewer/widgetUnit/widgetService.service"; import { map, distinctUntilChanged, filter, debounceTime } from "rxjs/operators"; -import { Subscription, combineLatest, Observable, BehaviorSubject } from "rxjs"; +import { Subscription, combineLatest, Observable, BehaviorSubject, fromEvent } from "rxjs"; import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service"; +import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service"; export function temporaryFilterDataentryName(name: string):string{ return /autoradiography/.test(name) @@ -27,6 +28,9 @@ export class DatabrowserService implements OnDestroy{ public selectedRegions$: Observable<any[]> public selectedRegions: any[] = [] + public rebuiltSelectedRegions: any[] = [] + public rebuiltSomeSelectedRegions: any[] = [] + public regionsLabelIndexMap: Map<number, any> = new Map() public fetchingFlag: boolean = false @@ -42,7 +46,8 @@ export class DatabrowserService implements OnDestroy{ constructor( private constantService: AtlasViewerConstantsServices, private store: Store<ViewerConfiguration>, - private widgetService: WidgetServices + private widgetService: WidgetServices, + private workerService: AtlasWorkerService ){ this.subscriptions.push( @@ -86,7 +91,15 @@ export class DatabrowserService implements OnDestroy{ ) this.subscriptions.push( - this.selectedRegions$.subscribe(r => this.selectedRegions = r) + this.selectedRegions$.subscribe(r => { + this.selectedRegions = r + console.log(r) + this.workerService.worker.postMessage({ + type: 'BUILD_REGION_SELECTION_TREE', + selectedRegions: r, + regions: this.selectedParcellation.regions + }) + }) ) this.fetchDataObservable$ = combineLatest( @@ -110,6 +123,21 @@ export class DatabrowserService implements OnDestroy{ debounceTime(16) ).subscribe((param : [string, string, null] ) => this.fetchData(param[0], param[1])) ) + + this.subscriptions.push( + fromEvent(this.workerService.worker, 'message').pipe( + filter((message:MessageEvent) => message && message.data && message.data.type === 'RETURN_REBUILT_REGION_SELECTION_TREE'), + map(message => message.data), + ).subscribe((payload:any) => { + /** + * rebuiltSelectedRegion contains super region that are + * selected as a result of all of its children that are selectted + */ + const { rebuiltSelectedRegions, rebuiltSomeSelectedRegions } = payload + this.rebuiltSomeSelectedRegions = rebuiltSomeSelectedRegions + this.rebuiltSelectedRegions = rebuiltSelectedRegions + }) + ) } ngOnDestroy(){ @@ -117,12 +145,25 @@ export class DatabrowserService implements OnDestroy{ } public updateRegionSelection(regions: any[]) { + const filteredRegion = regions.filter(r => r.labelIndex !== null && typeof r.labelIndex !== 'undefined') this.store.dispatch({ type: SELECT_REGIONS, - selectRegions: regions + selectRegions: filteredRegion }) } + public deselectRegion(region) { + const regionsToDelect = [] + const recursiveFlatten = (region:any) => { + regionsToDelect.push(region) + if (region.children && region.children.map) + region.children.map(recursiveFlatten) + } + recursiveFlatten(region) + const selectedRegions = this.selectedRegions.filter(r => !regionsToDelect.some(deR => deR.name === r.name)) + this.updateRegionSelection(selectedRegions) + } + public changeParcellation({ current, previous }){ if (previous && current && current.name === previous.name) return @@ -134,13 +175,13 @@ export class DatabrowserService implements OnDestroy{ public singleClickRegion(region) { const selectedSet = new Set(extractLabelIdx(region)) - const intersection = new Set([...this.selectedRegions.map(r => r.labelIndex)].filter(v => selectedSet.has(v))) - this.store.dispatch({ - type: SELECT_REGIONS, - selectRegions: intersection.size > 0 - ? this.selectedRegions.filter(v => !intersection.has(v.labelIndex)) - : this.selectedRegions.concat([...selectedSet].map(idx => this.regionsLabelIndexMap.get(idx))) - }) + const filteredSelectedRegion = this.selectedRegions.filter(r => r.labelIndex) + const intersection = new Set([...filteredSelectedRegion.map(r => r.labelIndex)].filter(v => selectedSet.has(v))) + this.updateRegionSelection( + intersection.size > 0 + ? filteredSelectedRegion.filter(v => !intersection.has(v.labelIndex)) + : filteredSelectedRegion.concat([...selectedSet].map(idx => this.regionsLabelIndexMap.get(idx))) + ) } public doubleClickRegion(region) { diff --git a/src/ui/databrowserModule/databrowser/databrowser.component.ts b/src/ui/databrowserModule/databrowser/databrowser.component.ts index 07890a3b0221645facf01409ebddd9c25b36ad2f..182a0cea7e9e06115ff764424b94808e9133231b 100644 --- a/src/ui/databrowserModule/databrowser/databrowser.component.ts +++ b/src/ui/databrowserModule/databrowser/databrowser.component.ts @@ -3,6 +3,7 @@ import { DataEntry } from "src/services/stateStore.service"; import { Subscription, merge } from "rxjs"; import { DatabrowserService } from "../databrowser.service"; import { ModalityPicker } from "../modalityPicker/modalityPicker.component"; +import { skip } from "rxjs/operators"; @Component({ selector : 'data-browser', @@ -23,6 +24,10 @@ export class DataBrowser implements OnDestroy,OnInit{ return this.dbService.selectedRegions } + get rebuiltSomeSelectedRegions(){ + return this.dbService.rebuiltSomeSelectedRegions + } + get selectedParcellation(){ return this.dbService.selectedParcellation } @@ -65,7 +70,7 @@ export class DataBrowser implements OnDestroy,OnInit{ ngOnInit(){ this.subscriptions.push( merge( - this.dbService.selectedRegions$, + // this.dbService.selectedRegions$, this.dbService.fetchDataObservable$ ).subscribe(() => { this.resetCurrentPage() @@ -96,14 +101,11 @@ export class DataBrowser implements OnDestroy,OnInit{ public showParcellationList: boolean = false + /** + * when user clicks x on region selector + */ deselectRegion(region:any){ - /** - * when user clicks x on region selector - */ - - this.dbService.updateRegionSelection( - this.selectedRegions.filter(r => r.name !== region.name) - ) + this.dbService.deselectRegion(region) } uncheckModality(modality:string){ diff --git a/src/ui/databrowserModule/databrowser/databrowser.template.html b/src/ui/databrowserModule/databrowser/databrowser.template.html index 4653194a66a2cb74139b1cfe4d2aaf2376a1752a..825ebced1fb43775b8ad60a7ac8910cc41d1585d 100644 --- a/src/ui/databrowserModule/databrowser/databrowser.template.html +++ b/src/ui/databrowserModule/databrowser/databrowser.template.html @@ -91,6 +91,7 @@ <ng-template #showData> <!-- datawrapper --> <div + *ngIf="dbService.fetchedDataEntries$ | async | filterDataEntriesByMethods : modalityFilter | filterDataEntriesByRegion : rebuiltSomeSelectedRegions as filteredDataEntry" [ngStyle]="filePreviewName ? {'transform': 'translateX(-50%)'} : {}" class="dataEntryWrapper"> @@ -100,8 +101,8 @@ <i *ngIf="dbService.fetchedDataEntries$ | async"> {{ (dbService.fetchedDataEntries$ | async).length }} total results. <span - *ngIf="selectedRegions.length + modalityFilter.length > 0 "> - {{ (dbService.fetchedDataEntries$ | async | filterDataEntriesByMethods : modalityFilter | filterDataEntriesByRegion : selectedRegions).length }} + *ngIf="rebuiltSomeSelectedRegions.length + modalityFilter.length > 0 "> + {{ filteredDataEntry.length }} filtered results. <a href="#" @@ -117,7 +118,7 @@ <div *ngIf="dbService.fetchedDataEntries$ | async"> <dataset-viewer class="mt-1" - *ngFor="let dataset of dbService.fetchedDataEntries$ | async | filterDataEntriesByMethods : modalityFilter | filterDataEntriesByRegion : selectedRegions | searchResultPagination : currentPage : hitsPerPage" + *ngFor="let dataset of filteredDataEntry | searchResultPagination : currentPage : hitsPerPage" (showPreviewDataset)="onShowPreviewDataset($event)" [dataset]="dataset"> </dataset-viewer> @@ -127,7 +128,7 @@ *ngIf="dbService.fetchedDataEntries$ | async" (paginationChange)="currentPage = $event" [hitsPerPage]="hitsPerPage" - [total]="(dbService.fetchedDataEntries$ | async | filterDataEntriesByMethods : modalityFilter | filterDataEntriesByRegion : selectedRegions).length" + [total]="filteredDataEntry.length" [currentPage]="currentPage"> </pagination-component> </div> diff --git a/src/ui/databrowserModule/fileviewer/fileviewer.component.ts b/src/ui/databrowserModule/fileviewer/fileviewer.component.ts index a044db495e675dfcfc22a264c06c9e0109b365e2..1048721014ffc5d81ff849ec2ccb635c2611e6ee 100644 --- a/src/ui/databrowserModule/fileviewer/fileviewer.component.ts +++ b/src/ui/databrowserModule/fileviewer/fileviewer.component.ts @@ -53,8 +53,6 @@ export class FileViewer implements OnChanges,OnDestroy,OnInit{ switchMap(()=>from(new Promise((rs,rj)=>{ if(!this.childChart) return rj('chart not defined after 500ms') - - debugger this.childChart.canvas.nativeElement.toBlob((blob)=>{ blob ? rs(blob) : rj('blob is undefined') diff --git a/src/ui/databrowserModule/util/filterDataEntriesByMethods.pipe.ts b/src/ui/databrowserModule/util/filterDataEntriesByMethods.pipe.ts index 3e010621c216d8ec9846f8273b127beedc40e5c0..b6ffded850a9f1765b736142c2eb7a1071c6b44e 100644 --- a/src/ui/databrowserModule/util/filterDataEntriesByMethods.pipe.ts +++ b/src/ui/databrowserModule/util/filterDataEntriesByMethods.pipe.ts @@ -8,7 +8,7 @@ import { temporaryFilterDataentryName } from '../databrowser.service' export class FilterDataEntriesbyMethods implements PipeTransform{ public transform(dataEntries:DataEntry[],showDataMethods:string[]):DataEntry[]{ - return showDataMethods.length > 0 + return dataEntries && showDataMethods && showDataMethods.length > 0 ? dataEntries.filter(dataEntry => { return dataEntry.activity.some(a => a.methods.some(m => showDataMethods.findIndex(dm => dm === temporaryFilterDataentryName(m)) >= 0)) }) diff --git a/src/ui/databrowserModule/util/filterDataEntriesByRegion.pipe.ts b/src/ui/databrowserModule/util/filterDataEntriesByRegion.pipe.ts index 1924a87ccae557fbf348092dad369e1c7e67ce68..9c6208dcf613643f7618dcb3379a12a49de85c1c 100644 --- a/src/ui/databrowserModule/util/filterDataEntriesByRegion.pipe.ts +++ b/src/ui/databrowserModule/util/filterDataEntriesByRegion.pipe.ts @@ -7,7 +7,7 @@ import { DataEntry } from "src/services/stateStore.service"; export class FilterDataEntriesByRegion implements PipeTransform{ public transform(dataentries: DataEntry[], selectedRegions: any[]) { - return selectedRegions.length > 0 + return dataentries && selectedRegions && selectedRegions.length > 0 ? dataentries.filter(de => de.parcellationRegion.some(pr => { @@ -15,7 +15,7 @@ export class FilterDataEntriesByRegion implements PipeTransform{ * TODO: temporary hack, some dataset region name is not exactly the same as region */ /* https://stackoverflow.com/a/9310752/6059235 */ - const regex = new RegExp(pr.name.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')) + const regex = new RegExp(pr.name.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'), 'i') return selectedRegions.some(sr => regex.test(sr.name)) /** * more correct, but probably should use UUID in the future diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index b9215eadfef0737d0e0ccf48594e784f212350ae..0cb0bf48b152b3642fddc7aaee99185b500bdfd7 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -756,6 +756,10 @@ export class NehubaContainer implements OnInit, OnDestroy{ filter(results => results[1] === null), map(results => results[0]) ).subscribe((region:any) => { + /** + * TODO + * region may have labelIndex as well as children + */ this.selectedRegionIndexSet.has(region.labelIndex) ? this.store.dispatch({ type : SELECT_REGIONS, diff --git a/src/util/pipes/groupDataEntriesByRegion.pipe.ts b/src/util/pipes/groupDataEntriesByRegion.pipe.ts index 6778b49808d0483a0a357ae417d5cbf89ae794ae..f4c326b4c253340fa04c819af0b637b85c974a60 100644 --- a/src/util/pipes/groupDataEntriesByRegion.pipe.ts +++ b/src/util/pipes/groupDataEntriesByRegion.pipe.ts @@ -5,8 +5,7 @@ import { DataEntry } from "../../services/stateStore.service"; name : 'groupDatasetByRegion' }) export class GroupDatasetByRegion implements PipeTransform{ - public transform(datasets:DataEntry[], regions:any[]): {region:any|null,searchResults:DataEntry[]}[] - { + public transform(datasets:DataEntry[], regions:any[]): {region:any|null,searchResults:DataEntry[]}[]{ return datasets.reduce((acc,curr)=>{ return (curr.parcellationRegion && curr.parcellationRegion.length > 0) diff --git a/src/util/worker.js b/src/util/worker.js index 2d804bf5f8388c5b715655f97f74374bd5b6272d..ba6b1627b9adf00e861f08ba494594118bc0dc2b 100644 --- a/src/util/worker.js +++ b/src/util/worker.js @@ -1,5 +1,5 @@ -const validTypes = ['CHECK_MESHES', 'GET_LANDMARKS_VTK', 'GET_USERLANDMARKS_VTK'] -const validOutType = ['CHECKED_MESH', 'ASSEMBLED_LANDMARKS_VTK', 'ASSEMBLED_USERLANDMARKS_VTK'] +const validTypes = ['CHECK_MESHES', 'GET_LANDMARKS_VTK', 'GET_USERLANDMARKS_VTK', 'BUILD_REGION_SELECTION_TREE'] +const validOutType = ['CHECKED_MESH', 'ASSEMBLED_LANDMARKS_VTK', 'ASSEMBLED_USERLANDMARKS_VTK', 'RETURN_REBUILT_REGION_SELECTION_TREE'] const checkMeshes = (action) => { @@ -219,9 +219,42 @@ const getuserLandmarksVtk = (action) => { }) } +const rebuildSelectedRegion = (payload) => { + const { selectedRegions, regions } = payload + + /** + * active tree branch + * branch is active if ALL children are active + */ + const activeTreeBranch = [] + const isRegionActive = (region) => selectedRegions.some(r => r.name === region.name) + || region.children && region.children.length > 0 && region.children.every(isRegionActive) + + /** + * some active tree branch + * branch is active if SOME children are active + */ + const someActiveTreeBranch = [] + const isSomeRegionActive = (region) => selectedRegions.some(r => r.name === region.name) + || region.children && region.children.length > 0 && region.children.some(isSomeRegionActive) + + const handleRegion = (r) => { + isRegionActive(r) ? activeTreeBranch.push(r) : {} + isSomeRegionActive(r) ? someActiveTreeBranch.push(r) : {} + if (r.children && r.children.length > 0) + r.children.forEach(handleRegion) + } + regions.forEach(handleRegion) + postMessage({ + type: 'RETURN_REBUILT_REGION_SELECTION_TREE', + rebuiltSelectedRegions: activeTreeBranch, + rebuiltSomeSelectedRegions: someActiveTreeBranch + }) +} + onmessage = (message) => { - if(validTypes.findIndex(type => type === message.data.type)>=0){ + if(validTypes.findIndex(type => type === message.data.type) >= 0){ switch(message.data.type){ case 'CHECK_MESHES': checkMeshes(message.data) @@ -232,6 +265,9 @@ onmessage = (message) => { case 'GET_USERLANDMARKS_VTK': getuserLandmarksVtk(message.data) return + case 'BUILD_REGION_SELECTION_TREE': + rebuildSelectedRegion(message.data) + return default: console.warn('unhandled worker action', message) }