Skip to content

Commit be63dd0

Browse files
authored
Merge pull request #3 from Kitware/fix/wavelet-and-docs
Fix wavelet example, widget bugs, docs, and tests
2 parents 8c718dd + 19876fd commit be63dd0

15 files changed

Lines changed: 660 additions & 616 deletions

File tree

README.md

Lines changed: 172 additions & 241 deletions
Large diffs are not rendered by default.

examples/wavelet.py

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
- vtkRTAnalyticSource generates a 3D structured grid with the "RTData" scalar.
55
- Contour iso-surfaces are clipped by a plane so you can see inside.
66
- A slice plane through the center shows continuous vs discrete coloring.
7-
- ColormapController manages the color transfer function and wires it to
7+
- Both pipelines are merged via vtkAppendPolyData into a single mapper.
8+
- ColormapConfig manages the color transfer function and wires it to
89
the mapper.
9-
- The colorbar widget provides an interactive preset picker, scale modes
10-
(linear / log / symlog), discrete banding, and manual range override.
10+
- Four colorbar widgets (top, bottom, left, right) each provide an
11+
interactive preset picker, scale modes (linear / log / symlog),
12+
discrete banding, and manual range override.
1113
1214
Run:
1315
cd <repo_root>
@@ -20,7 +22,12 @@
2022
from trame.app import TrameApp
2123
from trame.ui.vuetify3 import SinglePageLayout
2224
from vtkmodules.vtkCommonDataModel import vtkPlane
23-
from vtkmodules.vtkFiltersCore import vtkClipPolyData, vtkContourFilter, vtkCutter
25+
from vtkmodules.vtkFiltersCore import (
26+
vtkAppendPolyData,
27+
vtkClipPolyData,
28+
vtkContourFilter,
29+
vtkCutter,
30+
)
2431
from vtkmodules.vtkImagingCore import vtkRTAnalyticSource
2532
from vtkmodules.vtkRenderingCore import (
2633
vtkActor,
@@ -44,27 +51,27 @@ def __init__(self, server=None):
4451

4552
self.top = colormaps.ColormapConfig(
4653
self.server,
47-
mapper=self.contour_mapper,
54+
mapper=self.mapper,
4855
data_array_fn=self.get_data_array,
49-
).set_data_array("RTData", self.get_data_array, "points")
56+
).set_data_array("RTData", self.get_data_array, "point")
5057

5158
self.right = colormaps.ColormapConfig(
5259
self.server,
53-
mapper=self.contour_mapper,
60+
mapper=self.mapper,
5461
data_array_fn=self.get_data_array,
55-
).set_data_array("RTData", self.get_data_array, "points")
62+
).set_data_array("RTData", self.get_data_array, "point")
5663

5764
self.left = colormaps.ColormapConfig(
5865
self.server,
59-
mapper=self.slice_mapper,
66+
mapper=self.mapper,
6067
data_array_fn=self.get_data_array,
61-
).set_data_array("RTData", self.get_data_array, "points")
68+
).set_data_array("RTData", self.get_data_array, "point")
6269

6370
self.bottom = colormaps.ColormapConfig(
6471
self.server,
65-
mapper=self.slice_mapper,
72+
mapper=self.mapper,
6673
data_array_fn=self.get_data_array,
67-
).set_data_array("RTData", self.get_data_array, "points")
74+
).set_data_array("RTData", self.get_data_array, "point")
6875

6976
# Auto render when mapper update
7077
for colormap in [self.top, self.right, self.bottom, self.left]:
@@ -94,12 +101,6 @@ def _setup_vtk(self):
94101
clip_contour.SetClipFunction(clip_plane)
95102
clip_contour.Update()
96103

97-
contour_mapper = vtkPolyDataMapper()
98-
contour_mapper.SetInputConnection(clip_contour.GetOutputPort())
99-
100-
contour_actor = vtkActor()
101-
contour_actor.SetMapper(contour_mapper)
102-
103104
# --- Slice plane (perpendicular to clip — shows scalar gradient) ---
104105
slice_plane = vtkPlane()
105106
slice_plane.SetOrigin(0, 0, 0)
@@ -110,16 +111,21 @@ def _setup_vtk(self):
110111
slicer.SetCutFunction(slice_plane)
111112
slicer.Update()
112113

113-
slice_mapper = vtkPolyDataMapper()
114-
slice_mapper.SetInputConnection(slicer.GetOutputPort())
114+
# --- Merge contour + slice into one mapper ---
115+
append = vtkAppendPolyData()
116+
append.AddInputConnection(clip_contour.GetOutputPort())
117+
append.AddInputConnection(slicer.GetOutputPort())
118+
append.Update()
119+
120+
mapper = vtkPolyDataMapper()
121+
mapper.SetInputConnection(append.GetOutputPort())
115122

116-
slice_actor = vtkActor()
117-
slice_actor.SetMapper(slice_mapper)
123+
actor = vtkActor()
124+
actor.SetMapper(mapper)
118125

119126
# --- Renderer ---
120127
renderer = vtkRenderer()
121-
renderer.AddActor(contour_actor)
122-
renderer.AddActor(slice_actor)
128+
renderer.AddActor(actor)
123129
renderer.SetBackground(0.15, 0.15, 0.15)
124130
renderer.ResetCamera()
125131

@@ -134,8 +140,7 @@ def _setup_vtk(self):
134140
# Capture variables on class
135141
self.wavelet = wavelet
136142
self.render_window = render_window
137-
self.contour_mapper = contour_mapper
138-
self.slice_mapper = slice_mapper
143+
self.mapper = mapper
139144

140145
def get_data_array(self):
141146
"""Return the active scalar array from the wavelet output."""

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "trame-colormaps"
7-
version = "0.1.0"
7+
version = "1.0.0"
88
description = "Self-contained colormap module for VTK color transfer functions in Trame apps"
99
readme = "README.md"
1010
license = {text = "BSD-3-Clause"}

src/trame/dataclasses/colormaps.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
"""Trame dataclass entry point for trame-colormaps.
2+
3+
Re-exports ColormapConfig for use via ``from trame.dataclasses import colormaps``.
4+
"""
5+
16
from trame_colormaps.dataclasses import ColormapConfig
27

38
__all__ = ["ColormapConfig"]

src/trame/widgets/colormaps.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1+
"""Trame widget entry point for trame-colormaps.
2+
3+
Re-exports all widget classes (ColorMapEditor, HorizontalScalarBar,
4+
VerticalScalarBar) and registers the module with the trame server.
5+
"""
6+
17
from trame_colormaps.widgets import * # noqa: F403
28

39

410
def initialize(server):
11+
"""Called automatically by trame when this widget module is imported."""
512
from trame_colormaps import module # noqa: PLC0415
613

714
server.enable_module(module)

src/trame_colormaps/core/transforms.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
- Symlog (symmetric log) mapping
99
- Discrete symlog banding
1010
11-
These functions are designed to be called by the ColormapController but
11+
These functions are designed to be called by ColormapConfig but
1212
have no framework dependencies beyond VTK and numpy.
1313
"""
1414

@@ -429,7 +429,8 @@ def apply_symlog(ctf, linthresh, linear_rgb_points=None, n_samples=256):
429429
n_samples: Number of uniform samples in symlog space for building the CTF.
430430
431431
Returns:
432-
Base64 PNG colorbar image string, or None if the range is zero.
432+
Tuple of (lut_img_h, lut_img_v) base64 PNG strings, or None if
433+
the range is zero.
433434
"""
434435
x_min, x_max = ctf.GetRange()
435436
data_range = x_max - x_min
@@ -516,13 +517,13 @@ def apply_discrete_symlog(ctf, linthresh, linear_rgb_points, n_sub=1, n_samples=
516517
n_samples: Number of uniform samples in symlog space for building the CTF.
517518
518519
Returns:
519-
Tuple of (display_rgb_points, discrete_tick_data, lut_img) or
520-
(None, None, None) if the range is zero.
520+
Tuple of (display_rgb_points, discrete_tick_data, lut_img_h,
521+
lut_img_v) or (None, None, None, None) if the range is zero.
521522
"""
522523
x_min, x_max = ctf.GetRange()
523524
data_range = x_max - x_min
524525
if data_range == 0:
525-
return None, None, None
526+
return None, None, None, None
526527

527528
def symlog(v):
528529
"""Symmetric log: sign(v) * log10(1 + |v|/linthresh)."""
@@ -565,14 +566,14 @@ def symlog(v):
565566
boundaries = sorted(b for b in boundaries if x_min <= b <= x_max)
566567

567568
if len(boundaries) < 2:
568-
return None, None, None
569+
return None, None, None, None
569570

570571
# Symlog range for normalization
571572
s_min = float(symlog(x_min))
572573
s_max = float(symlog(x_max))
573574
s_range = s_max - s_min
574575
if s_range == 0:
575-
return None, None, None
576+
return None, None, None, None
576577

577578
# Store boundary values and their display positions (%) for tick alignment.
578579
all_tick_data = []

src/trame_colormaps/dataclasses.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44
scale mode, range, discrete settings, and derived display data (lut_img_h/v, ticks).
55
66
Can be used standalone or composed into a larger application config.
7-
When used standalone, instantiate ColormapConfig directly. When composed
8-
into an existing config, pass that config object to ColormapController
9-
instead.
107
"""
118

129
import math
@@ -49,14 +46,9 @@
4946
class ColormapConfig(StateDataModel):
5047
"""Reactive state model for a single colormap instance.
5148
52-
All fields are synced to the Trame client via ``Sync``.
53-
Use this standalone when the colormaps module owns its own state,
54-
or compose these same fields into a larger application config and
55-
pass that object to ``ColormapController`` instead.
56-
5749
Fields fall into three groups:
5850
59-
**User-settable** — bound to UI controls, read by the controller:
51+
**User-settable** — bound to UI controls, trigger reactive updates:
6052
6153
- ``active_presets``: List of preset names available in the picker.
6254
- ``preset``: Active color preset name.
@@ -72,7 +64,7 @@ class ColormapConfig(StateDataModel):
7264
- ``override_range``: When True, use the manual strings instead of
7365
the data-derived range.
7466
75-
**Derived** — written by the controller, consumed by UI:
67+
**Derived** — computed internally, consumed by UI:
7668
7769
- ``color_range``: Active (min, max) as floats, either from data or
7870
parsed from the manual strings.
@@ -93,9 +85,11 @@ class ColormapConfig(StateDataModel):
9385
9486
- ``menu``: Whether the preset control panel is open.
9587
- ``search``: Preset search/filter text.
88+
- ``orientation``: Colorbar orientation (``"horizontal"`` or ``"vertical"``).
89+
- ``mapper_change``: Server-only counter incremented on each mapper update.
9690
"""
9791

98-
# --- User-settable (bound to UI, read by controller) ---
92+
# --- User-settable (bound to UI, triggers reactive updates) ---
9993
active_presets: list[str] = Sync(list, DEFAULT_PRESETS)
10094
preset: str = Sync(str, "BuGnYl")
10195
invert: bool = Sync(bool, False)
@@ -108,7 +102,7 @@ class ColormapConfig(StateDataModel):
108102
color_value_max: str = Sync(str, "1")
109103
override_range: bool = Sync(bool, False)
110104

111-
# --- Derived (written by controller, read by UI) ---
105+
# --- Derived (computed internally, read by UI) ---
112106
color_value_min_valid: bool = Sync(bool, True)
113107
color_value_max_valid: bool = Sync(bool, True)
114108
color_range: list[float] = Sync(tuple[float, float], (0, 1))
@@ -183,7 +177,7 @@ def _build_lut_lists(self, active_presets):
183177
"""Rebuild the sorted preset picker lists from active_presets.
184178
185179
Filters COLORBAR_CACHE by the given preset names and populates
186-
``config.luts_normal`` and ``config.luts_inverted``.
180+
``self.luts_normal`` and ``self.luts_inverted``.
187181
188182
Args:
189183
active_presets: List of preset names to include.
@@ -383,7 +377,7 @@ def update_color_preset(
383377
"""Apply a color preset with the specified scale and discrete settings.
384378
385379
Args:
386-
name: Preset name (must exist in PRESET_REGISTRY).
380+
name: Preset name (must exist in COLORBAR_CACHE).
387381
invert: Whether to invert the transfer function.
388382
log_scale: Scale mode — ``"linear"``, ``"log"``, or ``"symlog"``.
389383
discrete_log: Enable discrete (stepped) color banding.

src/trame_colormaps/module.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""Trame module definition for trame-colormaps.
2+
3+
This package has no custom client-side components — it uses only
4+
standard vuetify3 and html widgets. The module stub exists so that
5+
``trame.widgets.colormaps.initialize(server)`` works without error.
6+
"""
7+
8+
9+
def setup(app, **kwargs):
10+
"""No-op — nothing to register on the client side."""

src/trame_colormaps/widgets.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def buttons(name):
3232
{
3333
"icon": (
3434
f"{name}.use_log_scale === 'log' ? 'mdi-math-log' : "
35-
"{name}.use_log_scale === 'symlog' ? 'mdi-sine-wave' : 'mdi-stairs'"
35+
f"{name}.use_log_scale === 'symlog' ? 'mdi-sine-wave' : 'mdi-stairs'"
3636
),
3737
"click": (
3838
f"{name}.use_log_scale = {name}.use_log_scale === 'linear' ? 'log' : "
@@ -330,13 +330,13 @@ def __init__(self, name, popup_location="top", **kwargs):
330330
"width:100%;"
331331
"transform:translateY(-50%);"
332332
"display:flex;"
333-
"flex-direction:column;"
333+
"flex-direction:row;"
334334
"align-items:center;`",
335335
),
336336
):
337337
html.Div(
338338
style=(
339-
"`height:1.5px;width:30%;background:${tick.color};`",
339+
"`width:100%;height:1.5px;background:${tick.color};`",
340340
),
341341
)
342342
html.Span(
@@ -347,12 +347,10 @@ def __init__(self, name, popup_location="top", **kwargs):
347347
"white-space: nowrap;"
348348
"color: ${tick.color};"
349349
"writing-mode:vertical-lr;"
350-
"transform: rotate(180deg);`",
350+
"transform: rotate(180deg);"
351+
"padding-left:2px;`",
351352
),
352353
)
353-
html.Div(
354-
style=("`height:1.5px;flex:1;background:${tick.color};`",),
355-
)
356354
# Min label at bottom
357355
html.Div(
358356
f"{{{{ {name}.color_range && {name}.color_range[0] != null"

0 commit comments

Comments
 (0)