Skip to content

Commit

Permalink
feat(segmentation_user_layer): add individual segment color picker tool
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisj committed Aug 13, 2024
1 parent 3efc904 commit 05c36bd
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 21 deletions.
16 changes: 16 additions & 0 deletions src/layer/segmentation/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,19 @@
+ .neuroglancer-tool-button {
margin-left: 1em;
}

.neuroglancer-segment-list-entry .neuroglancer-color-widget {
border: none;
border-color: transparent;
appearance: none;
background-color: transparent;
padding: 0;
margin: 0;
margin-left: 3px;
height: 19px;
width: 20px;
}

.neuroglancer-segment-list-entry .neuroglancer-color-widget.overridden {
background-color: white;
}
85 changes: 72 additions & 13 deletions src/segmentation_display_state/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,19 @@ import { observeWatchable, registerNestedSync } from "#src/trackable_value.js";
import { isWithinSelectionPanel } from "#src/ui/selection_details.js";
import type { Uint64Map } from "#src/uint64_map.js";
import { setClipboard } from "#src/util/clipboard.js";
import { useWhiteBackground } from "#src/util/color.js";
import {
packColor,
serializeColor,
TrackableRGB,
useWhiteBackground,
} from "#src/util/color.js";
import { RefCounted } from "#src/util/disposable.js";
import { measureElementClone } from "#src/util/dom.js";
import type { vec3 } from "#src/util/geom.js";
import { kOneVec, vec4 } from "#src/util/geom.js";
import { kOneVec, vec3, vec4 } from "#src/util/geom.js";
import { NullarySignal } from "#src/util/signal.js";
import { Uint64 } from "#src/util/uint64.js";
import { withSharedVisibility } from "#src/visibility_priority/frontend.js";
import { ColorWidget } from "#src/widget/color.js";
import { makeCopyButton } from "#src/widget/copy_button.js";
import { makeEyeButton } from "#src/widget/eye_button.js";
import { makeFilterButton } from "#src/widget/filter_button.js";
Expand Down Expand Up @@ -347,6 +352,8 @@ const segmentWidgetTemplate = (() => {
filterElement.classList.add("neuroglancer-segment-list-entry-filter");
const filterIndex = template.childElementCount;
template.appendChild(filterElement);
const colorWidgetIndex = template.childElementCount;
template.appendChild(ColorWidget.template());
return {
template,
copyContainerIndex,
Expand All @@ -357,6 +364,7 @@ const segmentWidgetTemplate = (() => {
labelIndex,
filterIndex,
starIndex,
colorWidgetIndex,
unmappedIdIndex: -1,
unmappedCopyIndex: -1,
};
Expand Down Expand Up @@ -426,7 +434,7 @@ function makeRegisterSegmentWidgetEventHandlers(
const onMouseEnter = (event: Event) => {
const entryElement = event.currentTarget as HTMLElement;
const idString = entryElement.dataset.id!;
const id = tempStatedColor;
const id = tempObjectId;
id.tryParseString(idString);
displayState.segmentSelectionState.set(id);
if (!isWithinSelectionPanel(entryElement)) {
Expand All @@ -437,7 +445,7 @@ function makeRegisterSegmentWidgetEventHandlers(
const selectHandler = (event: Event) => {
const entryElement = event.currentTarget as HTMLElement;
const idString = entryElement.dataset.id!;
const id = tempStatedColor;
const id = tempObjectId;
id.tryParseString(idString);
displayState.selectSegment(
id,
Expand Down Expand Up @@ -470,7 +478,7 @@ function makeRegisterSegmentWidgetEventHandlers(
const visibleCheckboxHandler = (event: Event) => {
const entryElement = getEntryElement(event);
const idString = entryElement.dataset.id!;
const id = tempStatedColor;
const id = tempObjectId;
id.tryParseString(idString);
const { selectedSegments, visibleSegments } =
displayState.segmentationGroupState.value;
Expand All @@ -486,7 +494,7 @@ function makeRegisterSegmentWidgetEventHandlers(
const filterHandler = (event: Event) => {
const entryElement = getEntryElement(event);
const idString = entryElement.dataset.id!;
const id = tempStatedColor;
const id = tempObjectId;
id.tryParseString(idString);
displayState.filterBySegmentLabel(id);
event.stopPropagation();
Expand All @@ -504,7 +512,7 @@ function makeRegisterSegmentWidgetEventHandlers(
}
const entryElement = event.currentTarget as HTMLElement;
const idString = entryElement.dataset.id!;
const id = tempStatedColor;
const id = tempObjectId;
id.tryParseString(idString);
displayState.moveToSegment(id);
};
Expand Down Expand Up @@ -539,11 +547,35 @@ function makeRegisterSegmentWidgetEventHandlers(
starButton.addEventListener("click", (event: MouseEvent) => {
const entryElement = getEntryElement(event);
const idString = entryElement.dataset.id!;
const id = tempStatedColor;
const id = tempObjectId;
id.tryParseString(idString);
const { selectedSegments } = displayState.segmentationGroupState.value;
selectedSegments.set(id, !selectedSegments.has(id));
});

const trackableRGB = new TrackableRGB(vec3.fromValues(0, 0, 0));
trackableRGB.changed.add(() => {
const testU = new Uint64(packColor(trackableRGB.value));
const idString = element.dataset.id!;
const id = tempObjectId;
id.tryParseString(idString);
displayState.segmentStatedColors.value.delete(id);
displayState.segmentStatedColors.value.set(id, testU);
});

// TODO, need to register disposer?
new ColorWidget(
trackableRGB,
undefined,
children[template.colorWidgetIndex] as HTMLInputElement,
() => {
const idString = element.dataset.id!;
const id = tempObjectId;
id.tryParseString(idString);
displayState.segmentStatedColors.value.delete(id);
},
false,
);
};
}

Expand Down Expand Up @@ -641,7 +673,7 @@ export class SegmentWidgetFactory<Template extends SegmentWidgetTemplate> {
}

update(container: HTMLElement) {
const id = tempStatedColor;
const id = tempObjectId;
const idString = container.dataset.id;
if (idString === undefined) return;
id.parseString(idString);
Expand Down Expand Up @@ -670,19 +702,26 @@ export class SegmentWidgetFactory<Template extends SegmentWidgetTemplate> {
const idContainer = stickyChildren[
template.idContainerIndex
] as HTMLElement;
let color = getBaseObjectColor(this.displayState, mapped) as vec3;
setSegmentIdElementStyle(
idContainer.children[template.idIndex] as HTMLElement,
getBaseObjectColor(this.displayState, mapped) as vec3,
color,
);
const isOverridden =
!!this.displayState?.segmentStatedColors.value.has(mapped);
setColorWidgetColor(
children[template.colorWidgetIndex] as HTMLInputElement,
color,
isOverridden,
);
const { unmappedIdIndex } = template;
if (unmappedIdIndex !== -1) {
let unmappedIdString: string | undefined;
let color: vec3;
if (
displayState!.baseSegmentColoring.value &&
(unmappedIdString = container.dataset.unmappedId) !== undefined
) {
const unmappedId = tempStatedColor;
const unmappedId = tempObjectId;
unmappedId.parseString(unmappedIdString);
color = getBaseObjectColor(this.displayState, unmappedId) as vec3;
} else {
Expand All @@ -692,6 +731,13 @@ export class SegmentWidgetFactory<Template extends SegmentWidgetTemplate> {
idContainer.children[unmappedIdIndex] as HTMLElement,
color,
);
const isOverridden =
!!this.displayState?.segmentStatedColors.value.has(mapped);
setColorWidgetColor(
children[template.colorWidgetIndex] as HTMLInputElement,
color,
isOverridden,
);
}
}
}
Expand All @@ -701,6 +747,15 @@ function setSegmentIdElementStyle(element: HTMLElement, color: vec3) {
element.style.color = useWhiteBackground(color) ? "white" : "black";
}

function setColorWidgetColor(
element: HTMLInputElement,
color: vec3,
isOverridden: boolean,
) {
element.value = serializeColor(color.subarray(0, 3) as vec3);
element.classList.toggle("overridden", isOverridden);
}

export class SegmentWidgetWithExtraColumnsFactory extends SegmentWidgetFactory<SegmentWidgetWithExtraColumnsTemplate> {
segmentPropertyMap: PreprocessedSegmentPropertyMap | undefined;
numericalProperties: InlineSegmentNumericalProperty[];
Expand Down Expand Up @@ -885,6 +940,9 @@ export function registerCallbackWhenSegmentationDisplayStateChanged(
displayState.baseSegmentColoring.changed.add(callback),
);
context.registerDisposer(displayState.hoverHighlight.changed.add(callback));
context.registerDisposer(
displayState.segmentStatedColors.changed.add(callback),
);
}

export function registerRedrawWhenSegmentationDisplayStateChanged(
Expand Down Expand Up @@ -941,6 +999,7 @@ export function registerRedrawWhenSegmentationDisplayState3DChanged(
* Temporary values used by getObjectColor.
*/
const tempColor = vec4.create();
const tempObjectId = new Uint64();
const tempStatedColor = new Uint64();

export function getBaseObjectColor(
Expand Down
29 changes: 21 additions & 8 deletions src/widget/color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,35 @@ import { vec3 } from "#src/util/geom.js";
export class ColorWidget<
Color extends vec3 | undefined = vec3,
> extends RefCounted {
element = document.createElement("input");
static template() {
const element = document.createElement("input");
element.classList.add("neuroglancer-color-widget");
element.type = "color";
return element;
}

constructor(
public model: WatchableValueInterface<Color>,
public getDefaultColor: () => vec3 = () => vec3.fromValues(1, 0, 0),
public element = ColorWidget.template(),
public unsetHandler = () => {},
enableWheel = true,
) {
super();
const { element } = this;
element.classList.add("neuroglancer-color-widget");
element.type = "color";
element.addEventListener("change", () => this.updateModel());
element.addEventListener("input", () => this.updateModel());
element.addEventListener("wheel", (event) => {
event.stopPropagation();
event.preventDefault();
this.adjustHueViaWheel(event);
if (enableWheel) {
element.addEventListener("wheel", (event) => {
event.stopPropagation();
event.preventDefault();
this.adjustHueViaWheel(event);
});
}
element.addEventListener("mousedown", (evt) => {
if (evt.button === 2) {
evt.stopPropagation();
unsetHandler();
}
});
this.registerDisposer(model.changed.add(() => this.updateView()));
this.updateView();
Expand Down

0 comments on commit 05c36bd

Please sign in to comment.