From 8398ac52e85f5f7bb36d0a7c542f55ab135589d9 Mon Sep 17 00:00:00 2001 From: Chris Jordan Date: Thu, 28 Sep 2023 18:43:05 -0400 Subject: [PATCH] feat(annotation): added hashColor function that returns a RGB for a given value. Exposed annotation propery enum labels in shader as an optional replacement to the enum value. Added UI that shows the list of enum labels colored using it's hashColor. --- src/neuroglancer/annotation/type_handler.ts | 11 ++++++- src/neuroglancer/annotation/user_layer.css | 16 ++++++++++ src/neuroglancer/annotation/user_layer.ts | 28 +++++++++++++++-- src/neuroglancer/segment_color.ts | 35 +++++++++++++++++++++ 4 files changed, 87 insertions(+), 3 deletions(-) diff --git a/src/neuroglancer/annotation/type_handler.ts b/src/neuroglancer/annotation/type_handler.ts index 51239dc34..e1fe2369f 100644 --- a/src/neuroglancer/annotation/type_handler.ts +++ b/src/neuroglancer/annotation/type_handler.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import {Annotation, AnnotationPropertySpec, AnnotationType, annotationTypeHandlers, getPropertyOffsets, propertyTypeDataType} from 'neuroglancer/annotation'; +import {Annotation, AnnotationNumericPropertySpec, AnnotationPropertySpec, AnnotationType, annotationTypeHandlers, getPropertyOffsets, propertyTypeDataType} from 'neuroglancer/annotation'; import {AnnotationLayer} from 'neuroglancer/annotation/renderlayer'; import {PerspectiveViewRenderContext} from 'neuroglancer/perspective_view/render_layer'; import {ChunkDisplayTransformParameters} from 'neuroglancer/render_coordinate_transform'; @@ -29,6 +29,7 @@ import {ParameterizedContextDependentShaderGetter, parameterizedEmitterDependent import {defineInvlerpShaderFunction, enableLerpShaderFunction} from 'neuroglancer/webgl/lerp'; import {ShaderBuilder, ShaderModule, ShaderProgram} from 'neuroglancer/webgl/shader'; import {addControlsToBuilder, setControlsInShader, ShaderControlsBuilderState, ShaderControlState} from 'neuroglancer/webgl/shader_ui_controls'; +import {BasicHashColorShaderManager} from 'neuroglancer/segment_color'; const DEBUG_HISTOGRAMS = false; @@ -197,6 +198,8 @@ export abstract class AnnotationRenderHelper extends AnnotationRenderHelperBase pickIdsPerInstance: number; targetIsSliceView: boolean; + protected hashColorShaderManager = new BasicHashColorShaderManager('hashColor'); + constructor( gl: GL, annotationType: AnnotationType, rank: number, properties: readonly Readonly[], @@ -225,8 +228,14 @@ export abstract class AnnotationRenderHelper extends AnnotationRenderHelperBase const referencedProperties: number[] = []; const controlsReferencedProperties = parameters.referencedProperties; const processedCode = parameters.parseResult.code; + this.hashColorShaderManager.defineShader(builder); for (let i = 0, numProperties = properties.length; i < numProperties; ++i) { const property = properties[i]; + const enumLabels = (property as AnnotationNumericPropertySpec).enumLabels || []; + const enumValues = (property as AnnotationNumericPropertySpec).enumValues || []; + for (let i = 0; i < enumLabels.length && i < enumValues.length; i++) { + builder.addVertexCode(`#define prop_${property.identifier}_${enumLabels[i]} uint(${enumValues[i]})\n`); + } const functionName = `prop_${property.identifier}`; if (!controlsReferencedProperties.includes(property.identifier) && !processedCode.match(new RegExp(`\\b${functionName}\\b`))) { diff --git a/src/neuroglancer/annotation/user_layer.css b/src/neuroglancer/annotation/user_layer.css index 04a94118d..9e2970ed2 100644 --- a/src/neuroglancer/annotation/user_layer.css +++ b/src/neuroglancer/annotation/user_layer.css @@ -50,3 +50,19 @@ content: "()"; color: #999; } + +.neuroglancer-annotation-shader-property-type:hover > .neuroglancer-annotation-shader-property-enum-labels, +.neuroglancer-annotation-shader-property-enum-labels:hover { + display: initial; +} + +.neuroglancer-annotation-shader-property-enum-labels { + z-index: 100; + position: absolute; + background: black; + display: none; + user-select: text; + padding: 2px; + border: 1px solid white; + color: deepskyblue; +} diff --git a/src/neuroglancer/annotation/user_layer.ts b/src/neuroglancer/annotation/user_layer.ts index 8a5b49219..bdf930e13 100644 --- a/src/neuroglancer/annotation/user_layer.ts +++ b/src/neuroglancer/annotation/user_layer.ts @@ -16,7 +16,7 @@ import './user_layer.css'; -import {AnnotationPropertySpec, annotationPropertySpecsToJson, AnnotationType, LocalAnnotationSource, parseAnnotationPropertySpecs} from 'neuroglancer/annotation'; +import {AnnotationNumericPropertySpec, AnnotationPropertySpec, annotationPropertySpecsToJson, AnnotationType, LocalAnnotationSource, parseAnnotationPropertySpecs} from 'neuroglancer/annotation'; import {AnnotationDisplayState, AnnotationLayerState} from 'neuroglancer/annotation/annotation_layer_state'; import {MultiscaleAnnotationSource} from 'neuroglancer/annotation/frontend_source'; import {CoordinateTransformSpecification, makeCoordinateSpace} from 'neuroglancer/coordinate_transform'; @@ -44,6 +44,9 @@ import {RenderScaleWidget} from 'neuroglancer/widget/render_scale_widget'; import {ShaderCodeWidget} from 'neuroglancer/widget/shader_code_widget'; import {registerLayerShaderControlsTool, ShaderControls} from 'neuroglancer/widget/shader_controls'; import {Tab} from 'neuroglancer/widget/tab_view'; +import {getCssColor, SegmentColorHash} from 'neuroglancer/segment_color'; +import {useWhiteBackground} from 'neuroglancer/util/color'; +import {vec3} from 'neuroglancer/util/geom'; const POINTS_JSON_KEY = 'points'; const ANNOTATIONS_JSON_KEY = 'annotations'; @@ -572,6 +575,8 @@ class RenderingOptionsTab extends Tab { codeWidget = this.registerDisposer(makeShaderCodeWidget(this.layer)); constructor(public layer: AnnotationUserLayer) { super(); + const segmentColorHash = SegmentColorHash.getDefault(); + let tempColor = new Float32Array(3) as vec3; const {element} = this; element.classList.add('neuroglancer-annotation-rendering-tab'); element.appendChild( @@ -588,7 +593,26 @@ class RenderingOptionsTab extends Tab { div.classList.add('neuroglancer-annotation-shader-property'); const typeElement = document.createElement('span'); typeElement.classList.add('neuroglancer-annotation-shader-property-type'); - typeElement.textContent = property.type; + const enumLabels = (property as AnnotationNumericPropertySpec).enumLabels; + const enumValues = (property as AnnotationNumericPropertySpec).enumValues; + if (enumLabels && enumValues) { + typeElement.textContent = 'enum'; + const enumLabelsElement = document.createElement('div'); + enumLabelsElement.classList.add('neuroglancer-annotation-shader-property-enum-labels'); + for (let i = 0; i < enumLabels.length && i < enumValues.length; i++) { + const label = enumLabels[i]; + const value = enumValues[i]; + const labelElement = document.createElement('div'); + labelElement.textContent = `prop_${property.identifier}_${label}`; + enumLabelsElement.appendChild(labelElement); + const color = segmentColorHash.computeNumber(tempColor, value); + labelElement.style.backgroundColor = getCssColor(color); + labelElement.style.color = useWhiteBackground(tempColor) ? 'white' : 'black'; + } + typeElement.appendChild(enumLabelsElement); + } else { + typeElement.textContent = property.type; + } const nameElement = document.createElement('span'); nameElement.classList.add('neuroglancer-annotation-shader-property-identifier'); nameElement.textContent = `prop_${property.identifier}`; diff --git a/src/neuroglancer/segment_color.ts b/src/neuroglancer/segment_color.ts index 1e23b6f91..bcf472dc0 100644 --- a/src/neuroglancer/segment_color.ts +++ b/src/neuroglancer/segment_color.ts @@ -28,6 +28,33 @@ import {Trackable} from './util/trackable'; const NUM_COMPONENTS = 2; +export class BasicHashColorShaderManager { + constructor(public prefix: string) {} + + defineShader(builder: ShaderBuilder) { + builder.addVertexCode(glsl_hashCombine); + builder.addVertexCode(glsl_hsvToRgb); + let s = ` +vec3 ${this.prefix}(highp uint x) { + highp uint seed = 0u; + uint h = hashCombine(seed, x); + vec${NUM_COMPONENTS} v; +`; + for (let i = 0; i < NUM_COMPONENTS; ++i) { + s += ` + v[${i}] = float(h & 0xFFu) / 255.0; + h >>= 8u; +`; + } + s += ` + vec3 hsv = vec3(v.x, 0.5 + v.y * 0.5, 1.0); + return hsvToRgb(hsv); +} +`; + builder.addVertexCode(s); + } +} + export class SegmentColorShaderManager { seedName = this.prefix + '_seed'; @@ -98,6 +125,14 @@ export class SegmentColorHash implements Trackable { return out; } + computeNumber(out: Float32Array, x: number) { + let h = hashCombine(this.hashSeed, x); + const c0 = (h & 0xFF) / 255; + const c1 = ((h >> 8) & 0xFF) / 255; + hsvToRgb(out, c0, 0.5 + 0.5 * c1, 1.0); + return out; + } + computeCssColor(x: Uint64) { this.compute(tempColor, x); return getCssColor(tempColor);