Skip to content

feat(measurement): implement Change Color for measurements - #6186

Open
awatson1978 wants to merge 1 commit into
OHIF:masterfrom
awatson1978:fix/6059-measurement-change-color
Open

feat(measurement): implement Change Color for measurements#6186
awatson1978 wants to merge 1 commit into
OHIF:masterfrom
awatson1978:fix/6059-measurement-change-color

Conversation

@awatson1978

@awatson1978 awatson1978 commented Jul 26, 2026

Copy link
Copy Markdown

Context

Fixes #6059

Every measurement row in the panel has a "…" menu with a Change Color option — but clicking it does absolutely nothing. No dialog, no error, nothing. As the issue notes, it wasn't even clear whether the feature was ever implemented. It turns out: it wasn't.

Changes & Results

Why this happens: the ui-next MeasurementTable wires its Change Color menu item to run a command called changeMeasurementColor — but no extension ever registered a command by that name, so the commands manager silently drops it.

The change: implement changeMeasurementColor in the cornerstone extension's commands module, reusing pieces that already exist:

  • The shared color picker dialog (the same one the segmentation panel uses for segment colors), seeded with the measurement's current effective color read from cornerstone's style hierarchy (annotation.config.style.getStyleProperty)
  • The existing updateMeasurement action, which already knows how to apply per-annotation styles (setAnnotationStyles) and trigger a viewport re-render

On save, the chosen color is applied to the annotation line and its text box across all interaction states (default / highlighted / selected / locked), so the measurement keeps its color when hovered or selected.

Before (Change Color → nothing) / After (color picker opens; measurement and label re-render in the chosen color immediately):

Testing

  1. Open a study in the basic viewer and draw a measurement (e.g. Length)
  2. In the measurements panel, open the row's "…" menu → Change Color
  3. A color picker opens showing the measurement's current color; pick a new color and Save
  4. The measurement and its text label recolor immediately and stay recolored across hover/selection

Verified in Chromium via Playwright: dialog opens, per-annotation styles are set (checked via annotation.config.style.getAnnotationToolStyles), and the viewport renders the new color. Also confirmed the pre-fix behavior (menu click is a silent no-op).

Screenshots

6059-before-nothing-happens
6059-before-nothing-happens
6059-after-dialog-open
6059-after-dialog-open
6059-after-annotation-recolored
6059-after-annotation-recolored

Checklist

PR

  • My Pull Request title is descriptive, accurate and follows the
    semantic-release format and guidelines.

Code

  • My code has been well-documented (function documentation, inline comments,
    etc.)

Public Documentation Updates

  • The documentation page has been updated as necessary. (no doc changes needed)

Tested Environment

  • "OS: macOS 25.5.0
  • "Node version: 24.x
  • "Browser: Chromium (Playwright)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added the ability to change the color of individual measurements.
    • A color picker now opens with the measurement’s current color and applies the selected color across the measurement and its text box.

The measurement panel's row menu has offered a Change Color action
since the ui-next MeasurementTable landed, but the
changeMeasurementColor command it runs was never implemented, so
selecting it silently did nothing.

Add the command to the cornerstone extension: it opens the shared
color picker dialog seeded with the annotation's effective color from
the style hierarchy, then applies the chosen color to the annotation
and its text box through the existing updateMeasurement action, which
sets per-annotation styles and triggers a re-render.

Fixes OHIF#6059

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@netlify

netlify Bot commented Jul 26, 2026

Copy link
Copy Markdown

Deploy Preview for ohif-dev ready!

Name Link
🔨 Latest commit 0783c25
🔍 Latest deploy log https://app.netlify.com/projects/ohif-dev/deploys/6a6575f862396b00087d488a
😎 Deploy Preview https://deploy-preview-6186--ohif-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Measurement color command

Layer / File(s) Summary
Measurement color update flow
extensions/cornerstone/src/commandsModule.ts
Adds CSS color parsing, opens a color picker for a measurement, updates annotation and text-box color variants, and registers changeMeasurementColor.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: sedghi, wayfarer3130

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, semantic-release friendly, and accurately summarizes the new measurement color feature.
Description check ✅ Passed The description includes context, changes, testing steps, screenshots, and a completed checklist, matching the template well.
Linked Issues check ✅ Passed The changeMeasurementColor command opens a color picker and applies the selected color immediately, satisfying #6059.
Out of Scope Changes check ✅ Passed The changes stay within the measurement color feature work and do not introduce unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@awatson1978
awatson1978 marked this pull request as ready for review July 27, 2026 20:28

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@extensions/cornerstone/src/commandsModule.ts`:
- Around line 831-844: Update the onSave handler in the measurement color flow
to serialize newRgbaColor as rgba rather than rgb, preserving its alpha value in
the generated CSS color. Apply the alpha-preserving color consistently to all
style fields currently assigned cssColor, while retaining the existing RGB
components and updateMeasurement call.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2e7eed58-1b61-45b5-a2b9-2860974e8f94

📥 Commits

Reviewing files that changed from the base of the PR and between 1628ddb and 0783c25.

📒 Files selected for processing (1)
  • extensions/cornerstone/src/commandsModule.ts

Comment on lines +831 to +844
onSave: newRgbaColor => {
const cssColor = `rgb(${newRgbaColor.r}, ${newRgbaColor.g}, ${newRgbaColor.b})`;
actions.updateMeasurement({
uid,
style: {
color: cssColor,
colorHighlighted: cssColor,
colorSelected: cssColor,
colorLocked: cssColor,
textBoxColor: cssColor,
textBoxColorHighlighted: cssColor,
textBoxColorSelected: cssColor,
textBoxColorLocked: cssColor,
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
git ls-files | grep 'extensions/cornerstone/src/commandsModule.ts' || true

echo "== outline relevant symbols =="
ast-grep outline extensions/cornerstone/src/commandsModule.ts --match changeMeasurementColor --view expanded || true

echo "== relevant lines 780-860 =="
sed -n '780,860p' extensions/cornerstone/src/commandsModule.ts

echo "== search color parsing/save usages =="
rg -n "_cssColorToRgba|changeMeasurementColor|colorHighlighted|cssColorTo|rgba|rgb\\(" extensions/cornerstone/src -S

echo "== parse/save color implementation candidates =="
python3 - <<'PY'
from pathlib import Path
p = Path('extensions/cornerstone/src/commandsModule.ts')
text = p.read_text()
for needle in ['function', 'const', 'let', 'var']:
    pass
# Print lines mentioning rgbaColor or color style update
lines = text.splitlines()
for i,l in enumerate(lines,1):
    if ('rgbaColor' in l or '_cssColorToRgba' in l or 'ColorPicker' in l or 'Save' in l or 'onSave:' in l):
        print(f"{i:5}: {l}")
PY

Repository: OHIF/Viewers

Length of output: 6155


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== color parsing helper =="
sed -n '114,160p' extensions/cornerstone/src/commandsModule.ts

echo "== locate colorPickerDialog definition/import =="
rg -n "export .*colorPickerDialog|const colorPickerDialog|function colorPickerDialog|colorPickerDialog|ColorPicker|ColorPickerComponent" extensions/cornerstone/src -S

echo "== files with colorPickerDialog =="
git ls-files extensions/cornerstone/src | xargs rg -l "colorPickerDialog|ColorPickerComponent|ColorPicker" -S

echo "== inspect likely dialog file(s) =="
while IFS= read -r f; do
  echo "--- $f"
  sed -n '1,240p' "$f"
done < <(rg -l "colorPickerDialog|ColorPickerComponent|ColorPicker" extensions/cornerstone/src)

echo "== behavioral probe of parsing and save-format expressions =="
python3 - <<'PY'
import re

def rgba(css_color):
    rgb_match = re.search(r'rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+)\s*)?\)', css_color)
    if not rgb_match:
        return None
    r, g, b, a = rgb_match.groups()
    return {'r': int(r), 'g': int(g), 'b': int(b), 'a': 1.0 if a is None else float(a)}

def save_rgb(rgba_color):
    return f"rgb({rgba_color['r']}, {rgba_color['g']}, {rgba_color['b']})"

def save_rgba(rgba_color):
    return f"rgba({rgba_color['r']}, {rgba_color['g']}, {rgba_color['b']}, {rgba_color['a']})"

cases = [
    "rgba(255, 0, 0, 0.5)",
    "rgba(0, 0, 255, 0)",
    "rgba(0, 0, 255, 1)",
    "rgb(0, 255, 0)",
]
for case in cases:
    parsed = rgba(case)
    print(case, "=>", save_rgb(parsed), "== opaque?", not parsed['a'] < 1.0, "=> rgba?", save_rgba(parsed))
PY

Repository: OHIF/Viewers

Length of output: 1934


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== import section =="
sed -n '1,45p' extensions/cornerstone/src/commandsModule.ts

echo "== other usage around lines 1828-1856 =="
sed -n '1828,1856p' extensions/cornerstone/src/commandsModule.ts

echo "== all files mentioning colorPickerDialog with line numbers =="
rg -n "colorPickerDialog" extensions/corner -S || true

echo "== search color picker packages/components =="
rg -n "ColorPicker|color-picker|react-color|`@uiw/react-color`|`@uiw/color`" package.json pnpm-lock.yaml package-lock.json yarn.lock extensions -S || true

echo "== npm package availability probe =="
node - <<'JS'
const fs = require('fs');
for (const p of [
  'package.json',
  'pnpm-lock.yaml',
  'package-lock.json',
  'yarn.lock'
]) {
  if (fs.existsSync(p)) {
    console.log(p + ' exists');
    const t = fs.readFileSync(p, 'utf8');
    for (const term of ['`@uiw/react-color`', '`@uiw/color`', 'ColorPicker']) {
      let found = t.includes(term);
      if (p.endsWith('.json') && term === '`@uiw/react-color`') {
        // more focused
        const idx = t.indexOf('"`@uiw/react-color`"');
        found = idx !== -1;
      }
      console.log(p + ': ' + term + ' => ' + found);
    }
  }
}
JS

echo "== behavioral probe of parsing and save-format expressions =="
python3 - <<'PY'
import re

def rgba(css_color):
    rgb_match = re.search(r'rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+)\s*)?\)', css_color)
    if not rgb_match:
        return None
    r, g, b, a = rgb_match.groups()
    return {'r': int(r), 'g': int(g), 'b': int(b), 'a': 1.0 if a is None else float(a)}

def save_rgb(rgba_color):
    return f"rgb({rgba_color['r']}, {rgba_color['g']}, {rgba_color['b']})"

def save_rgba(rgba_color):
    return f"rgba({rgba_color['r']}, {rgba_color['g']}, {rgba_color['b']}, {rgba_color['a']})"

cases = [
    "rgba(255, 0, 0, 0.5)",
    "rgba(0, 0, 255, 0)",
    "rgba(0, 0, 255, 1)",
    "rgb(0, 255, 0)",
]
for case in cases:
    parsed = rgba(case)
    print(case, "=>", save_rgb(parsed), "== opaque?", parsed['a'] >= 0.9999, "=> rgba?", save_rgba(parsed))
PY

Repository: OHIF/Viewers

Length of output: 4652


Preserve the selected alpha value.

_cssColorToRgba parses alpha from values like rgba(..., 0.5), but the measurement color save path emits rgb(...), which discards non-opaque alpha and changes the displayed measurement.

Proposed fix
-            const cssColor = `rgb(${newRgbaColor.r}, ${newRgbaColor.g}, ${newRgbaColor.b})`;
+            const cssColor = `rgba(${newRgbaColor.r}, ${newRgbaColor.g}, ${newRgbaColor.b}, ${newRgbaColor.a})`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onSave: newRgbaColor => {
const cssColor = `rgb(${newRgbaColor.r}, ${newRgbaColor.g}, ${newRgbaColor.b})`;
actions.updateMeasurement({
uid,
style: {
color: cssColor,
colorHighlighted: cssColor,
colorSelected: cssColor,
colorLocked: cssColor,
textBoxColor: cssColor,
textBoxColorHighlighted: cssColor,
textBoxColorSelected: cssColor,
textBoxColorLocked: cssColor,
},
onSave: newRgbaColor => {
const cssColor = `rgba(${newRgbaColor.r}, ${newRgbaColor.g}, ${newRgbaColor.b}, ${newRgbaColor.a})`;
actions.updateMeasurement({
uid,
style: {
color: cssColor,
colorHighlighted: cssColor,
colorSelected: cssColor,
colorLocked: cssColor,
textBoxColor: cssColor,
textBoxColorHighlighted: cssColor,
textBoxColorSelected: cssColor,
textBoxColorLocked: cssColor,
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/cornerstone/src/commandsModule.ts` around lines 831 - 844, Update
the onSave handler in the measurement color flow to serialize newRgbaColor as
rgba rather than rgb, preserving its alpha value in the generated CSS color.
Apply the alpha-preserving color consistently to all style fields currently
assigned cssColor, while retaining the existing RGB components and
updateMeasurement call.

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.

[Bug] Change Color feature not working for measurements

1 participant