Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8b74a5c
feat: add accordions to tabs to sort controls
seankmartin Sep 2, 2025
639e6e2
fix: correct paddings
seankmartin Sep 2, 2025
6e5cd58
NGLASS-1051 some style change to accordions
Aiga115 Oct 17, 2025
e146b8f
NGLASS-1051 fix paddings and color of accordion content and header
Aiga115 Oct 20, 2025
740d51f
NGLASS-1051 delete unnecessary code
Aiga115 Oct 22, 2025
ab0bcd3
Merge pull request #117 from MetaCell/feature/NGLASS-1051
seankmartin Jan 7, 2026
3ab4ef3
Merge branch 'master' into feat/tab-accordions
seankmartin Feb 19, 2026
5770999
fix: place skeleton rendering in right section
seankmartin Feb 20, 2026
f7c9175
NGLASS-1141 make annotation property value to overflow
Aiga115 Mar 5, 2026
2c35c08
NGLASS-1141 change accordion visibility using variable
Aiga115 Mar 6, 2026
9d9ddcc
NGLASS-1141 update chevron icon title
Aiga115 Mar 9, 2026
19a2ef4
NGLASS-1141 change class name and make default expand value false
Aiga115 Mar 12, 2026
836ef29
NGLASS-1141 make data source tab a regular tab
Aiga115 Mar 12, 2026
44a8e2e
Merge pull request #121 from MetaCell/feature/NGLASS-1141
seankmartin Apr 2, 2026
6aa7739
revert: remove unrelated change
seankmartin Apr 2, 2026
19ea43f
chore: lint and format
seankmartin Apr 2, 2026
f85e02b
Merge branch 'master' into feat/tab-accordions
seankmartin Apr 2, 2026
1d42db9
refactor: change chevron title logic
seankmartin Apr 2, 2026
32a237d
fix: standardise rspack usage for accordion
seankmartin Apr 2, 2026
8d9b6da
fix: correct accordion display to flex
seankmartin Apr 2, 2026
3e86667
feat: add default expanded control
seankmartin Apr 2, 2026
8d88691
fix: add default section to seg render accordion
seankmartin Apr 2, 2026
8655085
fix: correct skeleton show/hide logic
seankmartin Apr 2, 2026
c20dd00
lint: correct lint errors on example
seankmartin Apr 2, 2026
f13757c
fix: removed unused source accordion
seankmartin Apr 2, 2026
25baefd
chore: small reversions back to master branch
seankmartin Apr 2, 2026
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
37 changes: 37 additions & 0 deletions python/examples/example_accordion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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.ones((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.selected_layer.layer = "example_layer"
s.selected_layer.visible = True

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


@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 @@ -438,6 +493,10 @@ class Layer(JsonObjectWrapper):
)
tool = wrapped_property("tool", optional(Tool))

annotations_accordion = annotationsAccordion = wrapped_property(
"annotationsAccordion", AnnotationsAccordion
)

@staticmethod
def interpolate(a, b, t):
c = copy.deepcopy(a)
Expand Down Expand Up @@ -619,6 +678,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 @@ -954,6 +1016,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
3 changes: 2 additions & 1 deletion rspack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ export default defineConfig((env, args) => {
NEUROGLANCER_BRAINMAPS_CLIENT_ID: JSON.stringify(
"639403125587-4k5hgdfumtrvur8v48e3pr7oo91d765k.apps.googleusercontent.com",
),

// NEUROGLANCER_USE_ACCORDIONS: false,
// NEUROGLANCER_ACCORDION_DEFAULT_EXPANDED: true,
// NEUROGLANCER_CREDIT_LINK: JSON.stringify({url: '...', text: '...'}),
// NEUROGLANCER_DEFAULT_STATE_FRAGMENT: JSON.stringify('gs://bucket/state.json'),
// NEUROGLANCER_SHOW_LAYER_BAR_EXTRA_BUTTONS: true,
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 @@ -57,7 +57,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 @@ -676,12 +680,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 @@ -696,12 +702,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
Loading