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 Feb 22, 2024
1 parent ecb236b commit 9a091c7
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 15 deletions.
81 changes: 70 additions & 11 deletions src/segmentation_display_state/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,12 @@ import {
import { isWithinSelectionPanel } from "#/ui/selection_details";
import { Uint64Map } from "#/uint64_map";
import { setClipboard } from "#/util/clipboard";
import { useWhiteBackground } from "#/util/color";
import {
TrackableRGB,
packColor,
serializeColor,
useWhiteBackground,
} from "#/util/color";
import { RefCounted } from "#/util/disposable";
import { measureElementClone } from "#/util/dom";
import { kOneVec, vec3, vec4 } from "#/util/geom";
Expand All @@ -60,6 +65,7 @@ import { makeCopyButton } from "#/widget/copy_button";
import { makeEyeButton } from "#/widget/eye_button";
import { makeFilterButton } from "#/widget/filter_button";
import { makeStarButton } from "#/widget/star_button";
import { ColorWidget } from "src/widget/color";

export class Uint64MapEntry {
constructor(
Expand Down Expand Up @@ -348,6 +354,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 @@ -358,6 +366,7 @@ const segmentWidgetTemplate = (() => {
labelIndex,
filterIndex,
starIndex,
colorWidgetIndex,
unmappedIdIndex: -1,
unmappedCopyIndex: -1,
};
Expand Down Expand Up @@ -427,7 +436,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 @@ -438,7 +447,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 @@ -471,7 +480,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 @@ -487,7 +496,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 @@ -505,7 +514,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 @@ -540,11 +549,34 @@ 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);
},
);
};
}

Expand Down Expand Up @@ -642,7 +674,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 @@ -671,19 +703,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 @@ -693,6 +732,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 @@ -702,6 +748,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 @@ -886,6 +941,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 @@ -942,6 +1000,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
16 changes: 16 additions & 0 deletions src/segmentation_user_layer.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;
}
18 changes: 14 additions & 4 deletions src/widget/color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,33 @@ import { vec3 } from "#/util/geom";
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 = () => {},
) {
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);
});
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 9a091c7

Please sign in to comment.