Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions python/examples/example_accordion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import argparse

import neuroglancer
import neuroglancer.cli
import numpy as np


def add_example_layers(state):
state.dimensions = neuroglancer.CoordinateSpace(
names=["x", "y", "z"], units="nm", scales=[10, 10, 10]
)
state.layers.append(
name="example_layer",
layer=neuroglancer.LocalVolume(
data=np.random.rand(10, 10, 10).astype(np.float32),
dimensions=state.dimensions,
),
)
return state.layers[0]


if __name__ == "__main__":
ap = argparse.ArgumentParser()
neuroglancer.cli.add_server_arguments(ap)
args = ap.parse_args()
neuroglancer.cli.handle_server_arguments(args)
viewer = neuroglancer.Viewer()
with viewer.txn() as s:
add_example_layers(s)
s.layers[0].annotations_accordion.annotations_expanded = False
s.layers[0].annotations_accordion.related_segments_expanded = True
s.layers[0].rendering_accordion.slice_expanded = True
s.layers[0].rendering_accordion.shader_expanded = False
s.layers[0].source_accordion.source_expanded = False

print(viewer)
82 changes: 82 additions & 0 deletions python/neuroglancer/viewer_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,75 @@ class DimensionPlaybackVelocity(JsonObjectWrapper):
paused = wrapped_property("paused", optional(bool, True))


@export
class SourceAccordion(JsonObjectWrapper):
"""Accordion state for layer data source controls."""

__slots__ = ()

source_expanded = sourceExpanded = wrapped_property(
"sourceExpanded", optional(bool)
)
create_expanded = createExpanded = wrapped_property(
"createExpanded", optional(bool)
)


@export
class AnnotationsAccordion(JsonObjectWrapper):
"""Accordion state for layer annotation controls."""

__slots__ = ()

spacing_expanded = spacingExpanded = wrapped_property(
"spacingExpanded", optional(bool)
)
related_segments_expanded = relatedSegmentsExpanded = wrapped_property(
"relatedSegmentsExpanded", optional(bool)
)
annotations_expanded = annotationsExpanded = wrapped_property(
"annotationsExpanded", optional(bool)
)


@export
class ImageRenderingAccordion(JsonObjectWrapper):
"""Accordion state for image layer rendering controls."""

__slots__ = ()

slice_expanded = sliceExpanded = wrapped_property("sliceExpanded", optional(bool))
volume_rendering_expanded = volumeRenderingExpanded = wrapped_property(
"volumeRenderingExpanded", optional(bool)
)
shader_expanded = shaderExpanded = wrapped_property(
"shaderExpanded", optional(bool)
)


@export
class SegmentationRenderingAccordion(JsonObjectWrapper):
"""Accordion state for segmentation layer rendering controls."""

__slots__ = ()

visibility_expanded = visibilityExpanded = wrapped_property(
"visibilityExpanded", optional(bool)
)
appearance_expanded = appearanceExpanded = wrapped_property(
"appearanceExpanded", optional(bool)
)
slice_rendering_expanded = sliceRenderingExpanded = wrapped_property(
"sliceRenderingExpanded", optional(bool)
)
mesh_rendering_expanded = meshRenderingExpanded = wrapped_property(
"meshRenderingExpanded", optional(bool)
)
skeletons_expanded = skeletonsExpanded = wrapped_property(
"skeletonsExpanded", optional(bool)
)


@export
class Layer(JsonObjectWrapper):
__slots__ = ()
Expand All @@ -428,6 +497,13 @@ class Layer(JsonObjectWrapper):
)
tool = wrapped_property("tool", optional(Tool))

annotations_accordion = annotationsAccordion = wrapped_property(
"annotationsAccordion", AnnotationsAccordion
)
source_accordion = sourceAccordion = wrapped_property(
"sourceAccordion", SourceAccordion
)

@staticmethod
def interpolate(a, b, t):
c = copy.deepcopy(a)
Expand Down Expand Up @@ -609,6 +685,9 @@ def __init__(self, *args, **kwargs):
cross_section_render_scale = crossSectionRenderScale = wrapped_property(
"crossSectionRenderScale", optional(float, 1)
)
rendering_accordion = renderingAccordion = wrapped_property(
"renderingAccordion", ImageRenderingAccordion
)

@staticmethod
def interpolate(a, b, t):
Expand Down Expand Up @@ -946,6 +1025,9 @@ def visible_segments(self, segments):
skeleton_rendering = skeletonRendering = wrapped_property(
"skeletonRendering", SkeletonRenderingOptions
)
rendering_accordion = renderingAccordion = wrapped_property(
"renderingAccordion", SegmentationRenderingAccordion
)

@property
def skeleton_shader(self):
Expand Down
2 changes: 1 addition & 1 deletion src/datasource/graphene/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1954,7 +1954,7 @@ class MulticutAnnotationLayerView extends AnnotationLayerView {
public layer: SegmentationUserLayer,
public displayState: AnnotationDisplayState,
) {
super(layer, displayState);
super(layer, displayState, layer.annotationAccordionState);
const {
graphConnection: { value: graphConnection },
} = layer;
Expand Down
17 changes: 12 additions & 5 deletions src/layer/annotation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ import type {
AnnotationLayerView,
MergedAnnotationStates,
} from "#src/ui/annotations.js";
import { UserLayerWithAnnotationsMixin } from "#src/ui/annotations.js";
import {
RELATED_SEGMENT_SECTION_JSON_KEY,
SPACING_SECTION_JSON_KEY,
UserLayerWithAnnotationsMixin,
} from "#src/ui/annotations.js";
import { animationFrameDebounce } from "#src/util/animation_frame_debounce.js";
import type { Borrowed, Owned } from "#src/util/disposable.js";
import { RefCounted } from "#src/util/disposable.js";
Expand Down Expand Up @@ -675,12 +679,14 @@ export class AnnotationUserLayer extends Base {
renderScaleWidget.label.textContent = "Spacing (projection)";
parent.appendChild(renderScaleWidget.element);
}
tab.showSection(SPACING_SECTION_JSON_KEY);
},
),
);
tab.element.insertBefore(
tab.appendChild(
renderScaleControls.element,
tab.element.firstChild,
SPACING_SECTION_JSON_KEY,
true /* hidden */,
);
{
const checkbox = tab.registerDisposer(
Expand All @@ -695,12 +701,13 @@ export class AnnotationUserLayer extends Base {
label.title =
"Display all annotations if filtering by related segments is enabled but no segments are selected";
label.appendChild(checkbox.element);
tab.element.appendChild(label);
tab.appendChild(label, RELATED_SEGMENT_SECTION_JSON_KEY);
}
tab.element.appendChild(
tab.appendChild(
tab.registerDisposer(
new LinkedSegmentationLayersWidget(this.linkedSegmentationLayers),
).element,
RELATED_SEGMENT_SECTION_JSON_KEY,
);
}

Expand Down
67 changes: 57 additions & 10 deletions src/layer/image/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ import {
setControlsInShader,
ShaderControlState,
} from "#src/webgl/shader_ui_controls.js";
import { AccordionState, AccordionTab } from "#src/widget/accordion.js";
import { ChannelDimensionsWidget } from "#src/widget/channel_dimensions_widget.js";
import { makeCopyButton } from "#src/widget/copy_button.js";
import type { DependentViewContext } from "#src/widget/dependent_view_widget.js";
Expand All @@ -102,7 +103,6 @@ import {
registerLayerShaderControlsTool,
ShaderControls,
} from "#src/widget/shader_controls.js";
import { Tab } from "#src/widget/tab_view.js";

const OPACITY_JSON_KEY = "opacity";
const BLEND_JSON_KEY = "blend";
Expand All @@ -114,6 +114,10 @@ const CHANNEL_DIMENSIONS_JSON_KEY = "channelDimensions";
const VOLUME_RENDERING_JSON_KEY = "volumeRendering";
const VOLUME_RENDERING_GAIN_JSON_KEY = "volumeRenderingGain";
const VOLUME_RENDERING_DEPTH_SAMPLES_JSON_KEY = "volumeRenderingDepthSamples";
const RENDERING_ACCORDION_JSON_KEY = "renderingAccordion";
const SLICE_SECTION_JSON_KEY = "sliceExpanded";
const VOLUME_RENDERING_SECTION_JSON_KEY = "volumeRenderingExpanded";
const SHADER_SECTION_JSON_KEY = "shaderExpanded";

export interface ImageLayerSelectionState extends UserLayerSelectionState {
value: any;
Expand Down Expand Up @@ -157,6 +161,28 @@ export class ImageUserLayer extends Base {
);
volumeRenderingMode = trackableShaderModeValue();

renderingAccordionState = this.registerDisposer(
new AccordionState({
accordionJsonKey: RENDERING_ACCORDION_JSON_KEY,
sections: [
{
jsonKey: SLICE_SECTION_JSON_KEY,
displayName: "Slice 2D",
},
{
jsonKey: VOLUME_RENDERING_SECTION_JSON_KEY,
displayName: "Volume rendering",
},
{
jsonKey: SHADER_SECTION_JSON_KEY,
displayName: "Shader controls",
defaultExpanded: true,
isDefaultKey: true,
},
],
}),
);

shaderControlState = this.registerDisposer(
new ShaderControlState(
this.fragmentMain,
Expand Down Expand Up @@ -219,10 +245,13 @@ export class ImageUserLayer extends Base {
this.volumeRenderingDepthSamplesTarget.changed.add(
this.specificationChanged.dispatch,
);
this.renderingAccordionState.specificationChanged.add(
this.specificationChanged.dispatch,
);
this.tabs.add("rendering", {
label: "Rendering",
order: -100,
getter: () => new RenderingOptionsTab(this),
getter: () => new RenderingOptionsTab(this, this.renderingAccordionState),
});
this.tabs.default = "rendering";
}
Expand Down Expand Up @@ -339,6 +368,13 @@ export class ImageUserLayer extends Base {
volumeRenderingDepthSamplesTarget,
),
);
verifyOptionalObjectProperty(
specification,
RENDERING_ACCORDION_JSON_KEY,
(accordionState) => {
this.renderingAccordionState.restoreState(accordionState);
},
);
}
toJSON() {
const x = super.toJSON();
Expand All @@ -354,6 +390,7 @@ export class ImageUserLayer extends Base {
x[VOLUME_RENDERING_GAIN_JSON_KEY] = this.volumeRenderingGain.toJSON();
x[VOLUME_RENDERING_DEPTH_SAMPLES_JSON_KEY] =
this.volumeRenderingDepthSamplesTarget.toJSON();
x[RENDERING_ACCORDION_JSON_KEY] = this.renderingAccordionState.toJSON();
return x;
}

Expand Down Expand Up @@ -470,6 +507,7 @@ const LAYER_CONTROLS: LayerControlDefinition<ImageUserLayer>[] = [
{
label: "Resolution (slice)",
toolJson: CROSS_SECTION_RENDER_SCALE_JSON_KEY,
sectionKey: SLICE_SECTION_JSON_KEY,
...renderScaleLayerControl((layer) => ({
histogram: layer.sliceViewRenderScaleHistogram,
target: layer.sliceViewRenderScaleTarget,
Expand All @@ -478,21 +516,25 @@ const LAYER_CONTROLS: LayerControlDefinition<ImageUserLayer>[] = [
{
label: "Blending (slice)",
toolJson: BLEND_JSON_KEY,
sectionKey: SLICE_SECTION_JSON_KEY,
...enumLayerControl((layer) => layer.blendMode),
},
{
label: "Opacity (slice)",
toolJson: OPACITY_JSON_KEY,
sectionKey: SLICE_SECTION_JSON_KEY,
...rangeLayerControl((layer) => ({ value: layer.opacity })),
},
{
label: "Volume rendering (experimental)",
toolJson: VOLUME_RENDERING_JSON_KEY,
sectionKey: VOLUME_RENDERING_SECTION_JSON_KEY,
...enumLayerControl((layer) => layer.volumeRenderingMode),
},
{
label: "Gain (3D)",
toolJson: VOLUME_RENDERING_GAIN_JSON_KEY,
sectionKey: VOLUME_RENDERING_SECTION_JSON_KEY,
isValid: (layer) =>
makeCachedDerivedWatchableValue(
(volumeRenderingMode) =>
Expand All @@ -507,6 +549,7 @@ const LAYER_CONTROLS: LayerControlDefinition<ImageUserLayer>[] = [
{
label: "Resolution (3D)",
toolJson: VOLUME_RENDERING_DEPTH_SAMPLES_JSON_KEY,
sectionKey: VOLUME_RENDERING_SECTION_JSON_KEY,
isValid: (layer) =>
makeCachedDerivedWatchableValue(
(volumeRenderingMode) =>
Expand All @@ -527,21 +570,25 @@ for (const control of LAYER_CONTROLS) {
registerLayerControl(ImageUserLayer, control);
}

class RenderingOptionsTab extends Tab {
class RenderingOptionsTab extends AccordionTab {
codeWidget: ShaderCodeWidget;
constructor(public layer: ImageUserLayer) {
super();
constructor(
public layer: ImageUserLayer,
protected accordionState: AccordionState,
) {
super(accordionState);
const { element } = this;
this.codeWidget = this.registerDisposer(makeShaderCodeWidget(this.layer));
element.classList.add("neuroglancer-image-dropdown");

for (const control of LAYER_CONTROLS) {
element.appendChild(
this.appendChild(
addLayerControlToOptionsTab(this, layer, this.visibility, control),
control.sectionKey,
);
}

element.appendChild(
this.appendChild(
makeShaderCodeWidgetTopRow(
this.layer,
this.codeWidget,
Expand All @@ -553,14 +600,14 @@ class RenderingOptionsTab extends Tab {
"neuroglancer-image-dropdown-top-row",
),
);
element.appendChild(
this.appendChild(
this.registerDisposer(
new ChannelDimensionsWidget(layer.channelCoordinateSpaceCombiner),
).element,
);

element.appendChild(this.codeWidget.element);
element.appendChild(
this.appendChild(this.codeWidget.element);
this.appendChild(
this.registerDisposer(
new ShaderControls(
layer.shaderControlState,
Expand Down
Loading