Skip to content

Perceptually distinct run colors#64

Merged
Demonstrandum merged 3 commits into
masterfrom
cursor/perceptually-distinct-run-colors-c8e3
Mar 3, 2026
Merged

Perceptually distinct run colors#64
Demonstrandum merged 3 commits into
masterfrom
cursor/perceptually-distinct-run-colors-c8e3

Conversation

@Demonstrandum

Copy link
Copy Markdown
Owner

Motivation for features / changes

The existing hash-based run color assignment sometimes resulted in perceptually indistinct colors, making it difficult to differentiate runs. The previous clash resolution mechanism was limited, only adjusting hue and mixing deconflicted colors with user-set overrides, leading to these computed colors being inadvertently saved in user profiles.

This change aims to:

  1. Ensure run colors are perceptually distinct using a more robust deconfliction algorithm.
  2. Separate automatically deconflicted colors from user-defined color overrides.
  3. Implement a deterministic, incremental, and cached deconfliction process that does not persist in user profiles.

Technical description of changes

  • Enhanced Deconfliction Algorithm: The oklch_colors.ts module now uses a higher MIN_DELTA_E (0.075) and findDistantColor searches across all three OKLCH axes (lightness, chroma, hue) for a wider range of perceptually distinct color candidates (1,728 options). The computeDeconfliction function supports incremental updates and deterministic color assignment.
  • Separate State for Deconfliction: A new state field, deconflictedRunColors: Map<RunId, string>, has been added to RunsDataNamespacedState to store deconflicted colors, ensuring they are distinct from user-defined overrides.
  • New Actions and Reducers: runColorDeconflictionComputed and runColorDeconflictionLoaded actions, along with their respective reducers, manage the new deconfliction state.
  • Updated Color Resolution Priority: ui_selectors.ts's getRunColorMap now applies colors in the order: user override > deconfliction override > hash-based > legacy palette > inactive.
  • Persistent and Incremental Caching: runs_effects.ts implements a new pipeline to load and persist deconflicted colors to _tb_run_color_deconfliction.v1 in localStorage. Deconfliction is computed incrementally for new runs after fetchRunsSucceeded, and the cache is validated for consistency. Deconflicted colors are explicitly excluded from profile saving.
  • Documentation: AGENTS_DEV.md has been updated to reflect the new deconfliction mechanism and localStorage key.

Screenshots of UI changes (or N/A)

N/A (Changes affect color generation logic, not UI components directly, though the visual outcome will be more distinct run colors).

Detailed steps to verify changes work correctly (as executed by you)

  1. Observe Distinct Colors: Open TensorBoard with a large number of runs (e.g., 20-30 runs) that would typically result in similar hash-based colors. Verify that the run colors displayed are now perceptually more distinct.
  2. Verify Determinism:
    • Clear the _tb_run_color_deconfliction.v1 entry from localStorage (e.g., using browser dev tools).
    • Reload TensorBoard with the same set of runs.
    • Confirm that the deconflicted colors are recomputed and are identical to the colors observed before clearing the cache.
  3. Verify Incremental Computation:
    • Load TensorBoard with an initial set of runs.
    • Add new runs (e.g., by starting new training jobs or manually adding run directories).
    • Observe that the colors for the existing runs remain stable (if their base colors haven't changed), and only the new runs have their colors deconflicted against the full set of existing colors.
  4. Verify User Override Priority:
    • Set a custom color for a run using the UI.
    • Confirm that the custom color takes precedence over any deconflicted color.
  5. Verify Profile Separation:
    • Set a custom color for a run.
    • Save the TensorBoard profile.
    • Clear all localStorage.
    • Load the saved profile.
    • Confirm that only the user-set custom color is restored, and the deconflicted colors (if any) are recomputed based on the current runs, not loaded from the profile.

Alternate designs / implementations considered (or N/A)

N/A


Open in Web Open in Cursor 

Overhaul the run color deconfliction system to produce truly perceptually
distinct colors by searching across all three OKLCH axes (lightness,
chroma, and hue) instead of just hue.

Key changes:

1. **Raised MIN_DELTA_E from 0.04 to 0.075** — more conservative threshold
   that ensures colors are clearly distinguishable on charts.

2. **3-axis findDistantColor** — replacement colors are now chosen by
   maximizing the minimum OKLAB delta-E across a grid of 6 lightness ×
   4 chroma × 72 hue candidates (1728 points), with sRGB gamut filtering.
   Previously only 72 hue values at fixed L/C were searched.

3. **Separate deconfliction state** — deconflicted colors are stored in a
   new `deconflictedRunColors` map on the runs state, separate from
   user-set `runColorOverrideForGroupBy`. This means:
   - Deconfliction colors are NEVER saved in profiles
   - They are cached in a separate localStorage key
     (`_tb_run_color_deconfliction.v1`)
   - If the cache is lost, they are deterministically recomputed

4. **Incremental computation** — when new runs are added, only the new
   runs are checked against the full set of existing effective colors
   (including cached deconflictions). Existing cached runs keep their
   colors. Cache is invalidated when dark mode changes, runs are removed,
   or base colors change.

5. **Color priority** in getRunColorMap:
   user override > deconfliction override > hash-based > legacy > inactive

Files changed:
- oklch_colors.ts: new computeDeconfliction(), improved findDistantColor()
- runs_types.ts: added deconflictedRunColors field
- runs_actions.ts: runColorDeconflictionComputed, runColorDeconflictionLoaded
- runs_reducers.ts: reducers for new actions
- runs_selectors.ts: getDeconflictedRunColors selector
- ui_selectors.ts: updated getRunColorMap with deconfliction layer
- runs_effects.ts: new cache pipeline, incremental deconfliction
- testing.ts: updated test builder
- AGENTS_DEV.md: documented new localStorage key and feature

Co-authored-by: Samuel <samuel@knutsen.co>
@cursor

cursor Bot commented Mar 2, 2026

Copy link
Copy Markdown

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@github-actions

github-actions Bot commented Mar 2, 2026

Copy link
Copy Markdown

Preview Deployment

Status ✅ Running
Live Preview https://Demonstrandum-tensorbored-pr-64.hf.space
Space https://huggingface.co/spaces/Demonstrandum/tensorbored-pr-64
Details
  • Wheel: tensorbored_nightly-2.21.0a20260302-py3-none-any.whl
  • Commit: 156f28c
  • Build status: success

@Demonstrandum Demonstrandum marked this pull request as ready for review March 2, 2026 17:50
cursoragent and others added 2 commits March 2, 2026 17:51
Co-authored-by: Samuel <samuel@knutsen.co>
…tests

Fix a bug where a hash-based run sorting before a user-overridden run
would not be deconflicted against the user's explicit color choice.

The algorithm now has three phases:
- Phase 0: Place ALL user-overridden colors as immovable landmarks first,
  regardless of sort position.  This ensures every hash-based run is
  always checked against every user-chosen color.
- Phase 1: Place cached non-user runs (preserving their deconflictions).
- Phase 2: Process remaining new non-user runs against the full set.

Add comprehensive test suite (oklch_colors_test.ts) covering:
- Basic: no runs, single run, distant colors (no deconfliction)
- Clash detection: identical colors, very similar colors, multiple clashes
- User overrides: never deconflicted, respected as landmarks, hash runs
  checked against user colors regardless of sort order
- Mixed scenarios: user override in middle, user override sorting later
- Replacement quality: 3-axis search verified, sRGB-valid output
- Incremental/cache: cached runs preserved, new runs checked against all
- Determinism: same inputs → same outputs, Map insertion order independent
- Dark mode: different lightness ranges
- Stress: 30 identical runs, realistic 12-run scenario

Co-authored-by: Samuel <samuel@knutsen.co>
@Demonstrandum Demonstrandum merged commit a2ecee0 into master Mar 3, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants