From a0f7cfb58cd32c24644a413c750f5a3deb0d773f Mon Sep 17 00:00:00 2001 From: Chris Jordan Date: Wed, 17 Aug 2022 14:38:08 -0400 Subject: [PATCH 1/2] getting starred segments working on their own --- .../datasource/graphene/frontend.ts | 89 ++++++---- .../datasource/nggraph/frontend.ts | 5 +- .../segmentation_display_state/backend.ts | 1 + .../segmentation_display_state/base.ts | 1 + .../segmentation_display_state/frontend.ts | 29 +++- src/neuroglancer/segmentation_graph/local.ts | 7 +- src/neuroglancer/segmentation_user_layer.css | 28 ++++ src/neuroglancer/segmentation_user_layer.ts | 52 +++++- src/neuroglancer/ui/annotations.css | 4 - src/neuroglancer/ui/segment_list.css | 15 +- src/neuroglancer/ui/segment_list.ts | 158 +++++++++++++----- src/neuroglancer/ui/segment_select_tools.ts | 5 +- src/neuroglancer/widget/icon.css | 8 + src/neuroglancer/widget/icon.ts | 13 ++ src/neuroglancer/widget/star_button.ts | 24 +++ src/neuroglancer/widget/virtual_list.ts | 2 +- 16 files changed, 330 insertions(+), 111 deletions(-) create mode 100644 src/neuroglancer/widget/star_button.ts diff --git a/src/neuroglancer/datasource/graphene/frontend.ts b/src/neuroglancer/datasource/graphene/frontend.ts index 16ee5a8309..01117d6add 100644 --- a/src/neuroglancer/datasource/graphene/frontend.ts +++ b/src/neuroglancer/datasource/graphene/frontend.ts @@ -647,6 +647,13 @@ class GraphConnection extends SegmentationGraphSourceConnection { public state: GrapheneState) { super(graph, layer.displayState.segmentationGroupState.value); const segmentsState = layer.displayState.segmentationGroupState.value; + segmentsState.selectedSegments.changed.add((segmentIds: Uint64[]|Uint64|null, add: boolean) => { + if (segmentIds !== null) { + segmentIds = Array().concat(segmentIds); + } + this.selectedSegmentsChanged(segmentIds, add); + }); + segmentsState.visibleSegments.changed.add((segmentIds: Uint64[]|Uint64|null, add: boolean) => { if (segmentIds !== null) { segmentIds = Array().concat(segmentIds); @@ -681,44 +688,35 @@ class GraphConnection extends SegmentationGraphSourceConnection { private visibleSegmentsChanged(segments: Uint64[]|null, added: boolean) { const {segmentsState} = this; - + const {focusSegment: {value: focusSegment}} = this.graph.state.multicutState; + if (focusSegment && !segmentsState.visibleSegments.has(focusSegment)) { + if (segmentsState.selectedSegments.has(focusSegment)) { + StatusMessage.showTemporaryMessage(`Can't hide active multicut segment.`, 3000); + } else { + StatusMessage.showTemporaryMessage(`Can't deselect active multicut segment.`, 3000); + } + segmentsState.selectedSegments.add(focusSegment); + segmentsState.visibleSegments.add(focusSegment); + if (segments) { + segments = segments.filter(segment => !Uint64.equal(segment, focusSegment)); + } + } if (segments === null) { - const leafSegmentCount = this.segmentsState.visibleSegments.size; + const leafSegmentCount = this.segmentsState.selectedSegments.size; this.segmentsState.segmentEquivalences.clear(); - StatusMessage.showTemporaryMessage(`Deselected all ${leafSegmentCount} segments.`, 3000); + StatusMessage.showTemporaryMessage(`Hid all ${leafSegmentCount} segments.`, 3000); return; } - for (const segmentId of segments) { - const isBaseSegment = isBaseSegmentId(segmentId, this.graph.info.graph.nBitsForLayerId); - - const segmentConst = segmentId.clone(); - - if (added) { - if (isBaseSegment) { - this.graph.getRoot(segmentConst).then(rootId => { - segmentsState.visibleSegments.delete(segmentConst); - segmentsState.visibleSegments.add(rootId); - }); - } - } else if (!isBaseSegment) { - const {focusSegment: {value: focusSegment}} = this.graph.state.multicutState; - if (focusSegment && Uint64.equal(segmentId, focusSegment)) { - segmentsState.visibleSegments.add(segmentId); - StatusMessage.showTemporaryMessage(`Can't deselect active multicut segment.`, 3000); - return; - } - + if (!added) { const segmentCount = [...segmentsState.segmentEquivalences.setElements(segmentId)].length; // Approximation - segmentsState.segmentEquivalences.deleteSet(segmentId); - if (this.lastDeselectionMessage && this.lastDeselectionMessageExists) { this.lastDeselectionMessage.dispose(); this.lastDeselectionMessageExists = false; } this.lastDeselectionMessage = - StatusMessage.showMessage(`Deselected ${segmentCount} segments.`); + StatusMessage.showMessage(`Hid ${segmentCount} segments.`); this.lastDeselectionMessageExists = true; setTimeout(() => { if (this.lastDeselectionMessageExists) { @@ -729,6 +727,30 @@ class GraphConnection extends SegmentationGraphSourceConnection { } } } + + private selectedSegmentsChanged(segments: Uint64[]|null, added: boolean) { + const {segmentsState} = this; + if (segments === null) { + const leafSegmentCount = this.segmentsState.selectedSegments.size; + StatusMessage.showTemporaryMessage(`Deselected all ${leafSegmentCount} segments.`, 3000); + return; + } + for (const segmentId of segments) { + const isBaseSegment = isBaseSegmentId(segmentId, this.graph.info.graph.nBitsForLayerId); + const segmentConst = segmentId.clone(); + if (added) { + if (isBaseSegment) { + this.graph.getRoot(segmentConst).then(rootId => { + if (segmentsState.visibleSegments.has(segmentConst)) { + segmentsState.visibleSegments.add(rootId); + } + segmentsState.selectedSegments.delete(segmentConst); + segmentsState.selectedSegments.add(rootId); + }); + } + } + } + } computeSplit(include: Uint64, exclude: Uint64): ComputedSplit|undefined { include; @@ -751,7 +773,11 @@ class GraphConnection extends SegmentationGraphSourceConnection { const focusSegment = multicutState.focusSegment.value!; multicutState.reset(); // need to clear the focus segment before deleting the multicut segment const {segmentsState} = this; - segmentsState.visibleSegments.delete(focusSegment); + segmentsState.selectedSegments.delete(focusSegment); + for (const segment of [...sinks, ...sources]) { + segmentsState.selectedSegments.delete(segment.rootId); + } + segmentsState.selectedSegments.add(splitRoots); segmentsState.visibleSegments.add(splitRoots); return true; } @@ -1261,7 +1287,7 @@ class MulticutSegmentsTool extends Tool { activation.bindAction('set-anchor', event => { event.stopPropagation(); - const currentSegmentSelection = maybeGetSelection(this, segmentationGroupState.visibleSegments); + const currentSegmentSelection = maybeGetSelection(this, segmentationGroupState.visibleSegments); // or visible segments? if (!currentSegmentSelection) return; const {rootId, segmentId} = currentSegmentSelection; const {focusSegment, segments} = multicutState; @@ -1397,9 +1423,10 @@ class MergeSegmentsTool extends Tool { const loadedSubsource = getGraphLoadedSubsource(this.layer)!; const annotationToNanometers = loadedSubsource.loadedDataSource.transform.inputSpace.value.scales.map(x => x / 1e-9); const mergedRoot = await graph.graphServer.mergeSegments(lastSegmentSelection, selection, annotationToNanometers); - const {visibleSegments} = segmentationGroupState; - visibleSegments.delete(lastSegmentSelection.rootId); - visibleSegments.delete(selection.rootId); + const {selectedSegments, visibleSegments} = segmentationGroupState; + selectedSegments.delete(lastSegmentSelection.rootId); + selectedSegments.delete(selection.rootId); + selectedSegments.add(mergedRoot); visibleSegments.add(mergedRoot); this.lastAnchorSelection.value = undefined; activation.cancel(); diff --git a/src/neuroglancer/datasource/nggraph/frontend.ts b/src/neuroglancer/datasource/nggraph/frontend.ts index e62eb2df08..4e120e3a77 100644 --- a/src/neuroglancer/datasource/nggraph/frontend.ts +++ b/src/neuroglancer/datasource/nggraph/frontend.ts @@ -218,9 +218,12 @@ class GraphConnection extends SegmentationGraphSourceConnection { try { this.ignoreVisibleSegmentsChanged = true; if (this.segmentsState.visibleSegments.has(oldId)) { - this.segmentsState.visibleSegments.delete(oldId); this.segmentsState.visibleSegments.add(newId); } + if (this.segmentsState.selectedSegments.has(oldId)) { + this.segmentsState.selectedSegments.delete(oldId); + this.segmentsState.selectedSegments.add(newId); + } if (this.segmentsState.temporaryVisibleSegments.has(oldId)) { this.segmentsState.temporaryVisibleSegments.delete(oldId); this.segmentsState.temporaryVisibleSegments.add(newId); diff --git a/src/neuroglancer/segmentation_display_state/backend.ts b/src/neuroglancer/segmentation_display_state/backend.ts index 08277090a2..223635ca8a 100644 --- a/src/neuroglancer/segmentation_display_state/backend.ts +++ b/src/neuroglancer/segmentation_display_state/backend.ts @@ -43,6 +43,7 @@ export const withSegmentationLayerBackendState = >(Base: TBase) => class SegmentationLayerState extends Base implements VisibleSegmentsState { visibleSegments: Uint64Set; + selectedSegments: Uint64Set; segmentEquivalences: SharedDisjointUint64Sets; temporaryVisibleSegments: Uint64Set; temporarySegmentEquivalences: SharedDisjointUint64Sets; diff --git a/src/neuroglancer/segmentation_display_state/base.ts b/src/neuroglancer/segmentation_display_state/base.ts index 282358276d..033ffb4d11 100644 --- a/src/neuroglancer/segmentation_display_state/base.ts +++ b/src/neuroglancer/segmentation_display_state/base.ts @@ -23,6 +23,7 @@ import {VisibleSegmentEquivalencePolicy} from 'neuroglancer/segmentation_graph/s export interface VisibleSegmentsState { visibleSegments: Uint64Set; + selectedSegments: Uint64Set; segmentEquivalences: SharedDisjointUint64Sets; // Specifies a temporary/alternative set of segments/equivalences to use for display purposes, diff --git a/src/neuroglancer/segmentation_display_state/frontend.ts b/src/neuroglancer/segmentation_display_state/frontend.ts index a3b36b43d0..024fa8611b 100644 --- a/src/neuroglancer/segmentation_display_state/frontend.ts +++ b/src/neuroglancer/segmentation_display_state/frontend.ts @@ -40,6 +40,7 @@ import {Uint64} from 'neuroglancer/util/uint64'; import {withSharedVisibility} from 'neuroglancer/visibility_priority/frontend'; import {makeCopyButton} from 'neuroglancer/widget/copy_button'; import {makeFilterButton} from 'neuroglancer/widget/filter_button'; +import {makeStarButton} from 'neuroglancer/widget/star_button'; export class Uint64MapEntry { constructor(public key: Uint64, public value?: Uint64, public label?: string|undefined) {} @@ -270,6 +271,13 @@ const segmentWidgetTemplate = (() => { idElement.classList.add('neuroglancer-segment-list-entry-id'); const idIndex = idContainer.childElementCount; idContainer.appendChild(idElement); + const starButton = makeStarButton({ + title: `Star segment`, + }); + starButton.classList.add('neuroglancer-segment-list-entry-star'); + const starIndex = stickyContainer.childElementCount; + stickyContainer.appendChild(starButton); + const nameElement = document.createElement('span'); nameElement.classList.add('neuroglancer-segment-list-entry-name'); const labelIndex = template.childElementCount; @@ -289,6 +297,7 @@ const segmentWidgetTemplate = (() => { idIndex, labelIndex, filterIndex, + starIndex, unmappedIdIndex: -1, unmappedCopyIndex: -1 }; @@ -381,8 +390,12 @@ function makeRegisterSegmentWidgetEventHandlers(displayState: SegmentationDispla const idString = entryElement.dataset.id!; const id = tempStatedColor; id.tryParseString(idString); - const {visibleSegments} = displayState.segmentationGroupState.value; - visibleSegments.set(id, !visibleSegments.has(id)); + const {selectedSegments, visibleSegments} = displayState.segmentationGroupState.value; + const shouldBeVisible = !visibleSegments.has(id); + if (shouldBeVisible) { + selectedSegments.add(id); + } + visibleSegments.set(id, shouldBeVisible); event.stopPropagation(); }; @@ -421,6 +434,16 @@ function makeRegisterSegmentWidgetEventHandlers(displayState: SegmentationDispla stickyChildren[template.visibleIndex].addEventListener('click', visibleCheckboxHandler); children[template.filterIndex].addEventListener('click', filterHandler); element.addEventListener('action:select-position', selectHandler); + + const starButton = stickyChildren[template.starIndex] as HTMLElement; + starButton.addEventListener('click', (event: MouseEvent) => { + const entryElement = getEntryElement(event); + const idString = entryElement.dataset.id!; + const id = tempStatedColor + id.tryParseString(idString); + const {selectedSegments} = displayState.segmentationGroupState.value; + selectedSegments.set(id, !selectedSegments.has(id)); + }); }; } @@ -511,6 +534,8 @@ export class SegmentWidgetFactory