Skip to content

Commit 05e822f

Browse files
Merge pull request #15 from Kitware/feature/cut-outside-range
feat: add cut outside range mode (scissors button)
2 parents fc75d2f + b4defb5 commit 05e822f

13 files changed

Lines changed: 156 additions & 48 deletions

README.md

Lines changed: 46 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ The control panel has three sections, top to bottom:
113113

114114
The toolbar has three areas, left to right:
115115

116-
- **Icon buttons**Eight buttons separated into three groups by vertical
116+
- **Icon buttons**Nine buttons separated into three groups by vertical
117117
dividers. Active toggles show a primary-colored square outline; the icon
118118
itself stays black. The NaN color button opens a dropdown instead of
119119
toggling. Details on each button below.
@@ -140,6 +140,7 @@ The toolbar has three areas, left to right:
140140
| | | | | |
141141
| 7 | <img src="https://cdn.jsdelivr.net/npm/@mdi/svg/svg/gradient-horizontal.svg" width="24"> | <img src="https://cdn.jsdelivr.net/npm/@mdi/svg/svg/gradient-horizontal.svg" width="24" style="border: 2px solid #1867C0; border-radius: 4px; padding: 2px;"> | Discrete | Switches between continuous gradient and discrete color banding. Exposes band count in *Settings panel*. |
142142
| 8 | <img src="https://cdn.jsdelivr.net/npm/@mdi/svg/svg/pencil.svg" width="24"> | <img src="https://cdn.jsdelivr.net/npm/@mdi/svg/svg/pencil.svg" width="24" style="border: 2px solid #1867C0; border-radius: 4px; padding: 2px;"> | Custom Range | Toggles between data-driven range and manual Min/Max inputs. Disabled in Δ mode. Cannot be active at the same time as Δ Difference. |
143+
| 9 | <img src="https://cdn.jsdelivr.net/npm/@mdi/svg/svg/scissors-cutting.svg" width="24"> | <img src="https://cdn.jsdelivr.net/npm/@mdi/svg/svg/scissors-cutting.svg" width="24" style="border: 2px solid #1867C0; border-radius: 4px; padding: 2px;"> | Cut Outside Range | Switches between clamp mode (out-of-range values get endpoint color) and cut mode (out-of-range values get NaN color). Disabled unless Custom Range or Δ mode is active. |
143144

144145
Empty rows in the table indicate the vertical separator dividers between
145146
button groups.
@@ -149,6 +150,8 @@ button groups.
149150
- **Δ Difference** is disabled when Scale is Log or when Custom Range is on.
150151
You can always turn Δ Difference *off*.
151152
- **Custom Range** is disabled when Δ Difference is on.
153+
- **Cut Outside Range** is disabled when neither Custom Range nor Δ
154+
Difference is active.
152155
- **Category dropdown** is disabled when Δ Difference is on (presets forced
153156
to Diverging). When Δ is turned off, category resets to Sequential.
154157

@@ -171,50 +174,54 @@ on both the main CTF and any render CTF (e.g. symlog).
171174

172175
#### Scale Modes
173176

174-
<table>
175-
<tr>
176-
<td width="33%" align="center"><img src="docs/images/clicked-linear.png" width="100%" style="border: 1px solid black;"><br><em>Linear</em></td>
177-
<td width="33%" align="center"><img src="docs/images/clicked-log.png" width="100%" style="border: 1px solid black;"><br><em>Log</em></td>
178-
<td width="33%" align="center"><img src="docs/images/clicked-symlog.png" width="100%" style="border: 1px solid black;"><br><em>SymLog</em></td>
179-
</tr>
180-
</table>
181-
182-
In all three screenshots, Colorblind Safe, Invert, Custom Range, and
183-
Discrete are active (outlined). The category is set to Cyclic via the
184-
dropdown, and the search field contains "V":
185-
186-
- **Category** — Cyclic selected from dropdown; *Preset list* shows only
187-
cyclic presets (vikO, brocO, corkO, …).
188-
- **Colorblind Safe** — Further limits to colorblind-safe cyclic presets.
189-
- **Invert** — Preset swatches and colorbar render reversed.
190-
- **Custom Range***Settings panel* shows editable **Min** and **Max** fields.
191-
- **Discrete***Settings panel* shows band count. Label adapts: "Colors
192-
per tick interval" (Linear) or "Colors per order of magnitude" (Log/SymLog).
193-
- **Search** — Text field contains "V"; a clear button (✕) appears.
194-
195-
The only difference between the three images is the **Scale** icon, which
196-
cycles through Linear (<img src="https://cdn.jsdelivr.net/npm/@mdi/svg/svg/stairs.svg" width="16">), Log (<img src="https://cdn.jsdelivr.net/npm/@mdi/svg/svg/math-log.svg" width="16">), and SymLog (<img src="https://cdn.jsdelivr.net/npm/@mdi/svg/svg/sine-wave.svg" width="16">).
197-
The colorbar tick labels switch between decimal (Linear) and scientific
198-
notation (Log/SymLog).
177+
The Scale button cycles through three modes. The colorbar and tick labels
178+
update to match the active scale:
179+
180+
- **Linear** — evenly spaced ticks, decimal labels. Colorbar image is a
181+
direct mapping of the preset. Discrete label reads "Colors per tick
182+
interval."
183+
- **Log** — decade-based ticks, scientific notation labels. Colorbar image
184+
is resampled through the log transform so more visual space is given to
185+
lower magnitudes. Not available in Δ mode. Discrete label reads "Colors
186+
per order of magnitude."
187+
- **SymLog** — symmetric log around zero, decade ticks adaptively thinned
188+
near zero, scientific notation labels. Colorbar image is resampled
189+
through the symlog transform. In Δ mode, the epsilon dead zone aligns
190+
with the symlog tick marks. Discrete label reads "Colors per order of
191+
magnitude."
199192

200193
#### Δ Difference Mode
201194

202-
<table>
203-
<tr>
204-
<td width="50%" align="center"><img src="docs/images/clicked-delta-linear.png" width="100%" style="border: 1px solid black;"><br><em>Δ Linear</em></td>
205-
<td width="50%" align="center"><img src="docs/images/clicked-delta-symlog.png" width="100%" style="border: 1px solid black;"><br><em>Δ SymLog</em></td>
206-
</tr>
207-
</table>
195+
When Δ Difference is active:
208196

209-
When Δ Difference is active (outlined with primary):
210-
211-
- **Preset list** is forced to diverging-only presets (vik shown here).
212-
- **Scale** only toggles between Linear (<img src="https://cdn.jsdelivr.net/npm/@mdi/svg/svg/stairs.svg" width="16">) and SymLog (<img src="https://cdn.jsdelivr.net/npm/@mdi/svg/svg/sine-wave.svg" width="16">) — Log is
213-
not available.
197+
- **Preset list** is forced to diverging-only presets.
198+
- **Scale** only toggles between Linear and SymLog — Log is not available.
214199
- **Settings panel** replaces Min/Max with **|max|** (symmetric range
215200
centered at zero) and **ε tolerance** (dead zone around zero).
216-
- **Custom Range** button is disabled — range is always driven by |max|.
201+
- **Custom Range** is disabled — range is driven by |max|.
217202
- **Category** dropdown is disabled — presets locked to Diverging.
203+
- **Cut Outside Range** is available — out-of-range values can use the
204+
NaN color instead of endpoint clamping.
205+
206+
#### Cut Outside Range
207+
208+
The Cut button (<img src="https://cdn.jsdelivr.net/npm/@mdi/svg/svg/scissors-cutting.svg" width="16">)
209+
toggles between two modes for values outside the color range:
210+
211+
- **Clamp (default)** — out-of-range values are assigned the nearest
212+
endpoint color. This is VTK's default behavior.
213+
- **Cut** — out-of-range values are assigned the NaN color (set via the
214+
NaN Color dropdown). With the default transparent NaN color, out-of-range
215+
regions become invisible.
216+
217+
Cut mode is only available when **Custom Range** or **Δ Difference** is
218+
active — these are the modes where the color range may intentionally
219+
exclude part of the data. When neither is active, the button is disabled
220+
because the range covers the full data extent.
221+
222+
Internally, cut mode uses `vtkColorTransferFunction.SetUseAboveRangeColor()`
223+
and `SetUseBelowRangeColor()` with the current NaN color RGB. Changing the
224+
NaN color automatically updates the above/below range colors.
218225

219226
## Public API
220227

@@ -521,6 +528,7 @@ subclass. Fields fall into three groups:
521528
| `color_value_min` | `str` | `"0"` | Manual range min (string for text field) |
522529
| `color_value_max` | `str` | `"1"` | Manual range max (string for text field) |
523530
| `override_range` | `bool` | `False` | Use manual range instead of data range |
531+
| `cut_outside_range` | `bool` | `False` | Cut mode: out-of-range values use NaN color instead of endpoint color |
524532
| `nan_color` | `list[float]` | `[0,0,0,0]` | RGBA color for NaN/missing values (transparent by default) |
525533
| **Derived (computed internally, read by UI)** ||||
526534
| `color_range` | `tuple[float, float]` | `(0, 1)` | Active min/max color range |
-67.8 KB
Binary file not shown.
-70.9 KB
Binary file not shown.

docs/images/clicked-linear.png

-70.8 KB
Binary file not shown.

docs/images/clicked-log.png

-71.7 KB
Binary file not shown.

docs/images/clicked-symlog.png

-72.8 KB
Binary file not shown.

docs/images/normal.png

-37.3 KB
Loading

docs/images/panelbar.png

-2.4 KB
Loading

src/trame_colormaps/dataclasses.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ class ColormapConfig(StateDataModel):
113113
color_value_min: str = Sync(str, "0")
114114
color_value_max: str = Sync(str, "1")
115115
override_range: bool = Sync(bool, False)
116+
cut_outside_range: bool = Sync(bool, False)
116117
diverging: bool = Sync(bool, False)
117118
epsilon: str = Sync(str, "0")
118119
abs_max: str = Sync(str, "")
@@ -803,12 +804,30 @@ def _on_nan_color_change(self, nan_color):
803804
self._apply_nan_color()
804805
self.mapper_change += 1
805806

807+
@watch("cut_outside_range", eager=True)
808+
def _on_cut_outside_range_change(self, cut_outside_range):
809+
"""Toggle above/below range color mode on the active CTF(s)."""
810+
self._apply_nan_color()
811+
self.mapper_change += 1
812+
806813
def _apply_nan_color(self):
807-
"""Set NaN color (RGBA) on all active CTFs."""
814+
"""Set NaN color (RGBA) and above/below range colors on all active CTFs."""
808815
c = self.nan_color
809816
if not c or len(c) < 4:
810817
c = [0.0, 0.0, 0.0, 0.0]
811818
r, g, b, a = float(c[0]), float(c[1]), float(c[2]), float(c[3])
812-
self._ctf.SetNanColorRGBA(r, g, b, a)
819+
cut = bool(self.cut_outside_range)
820+
for ctf in self._active_ctfs():
821+
ctf.SetNanColorRGBA(r, g, b, a)
822+
ctf.SetUseAboveRangeColor(cut)
823+
ctf.SetUseBelowRangeColor(cut)
824+
if cut:
825+
ctf.SetAboveRangeColor(r, g, b)
826+
ctf.SetBelowRangeColor(r, g, b)
827+
828+
def _active_ctfs(self):
829+
"""Return list of active CTFs (main + symlog render CTF if present)."""
830+
ctfs = [self._ctf]
813831
if hasattr(self, "_symlog_ctf") and self._symlog_ctf:
814-
self._symlog_ctf.SetNanColorRGBA(r, g, b, a)
832+
ctfs.append(self._symlog_ctf)
833+
return ctfs

src/trame_colormaps/module/serve/style.css

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
:root {
2-
--tcmap-editor-size: 360px;
2+
--tcmap-editor-size: 420px;
33
}
44

55
.tcmap-editor {
@@ -24,6 +24,17 @@
2424
max-width: 120px;
2525
}
2626

27+
.tcmap-editor-search .v-field__input {
28+
padding: 2px 4px;
29+
min-height: 0;
30+
font-size: 0.8rem;
31+
}
32+
33+
.tcmap-editor-search .v-field {
34+
padding-inline-start: 4px !important;
35+
padding-inline-end: 4px !important;
36+
}
37+
2738
.tcmap-img-preset {
2839
width: 100%;
2940
min-width: 20rem;

0 commit comments

Comments
 (0)