Switch thread color distance from HSV to dE-2000 (perceptually accurate) #79

Closed
opened 2026-06-24 00:11:39 +00:00 by Mark · 0 comments
Owner

Context

src/embroidery_converter/thread_manager.py uses HSV distance for thread matching:

class ThreadMatcher:
    def match_color(self, input_color: str, tolerance: float = 50.0) -> tuple[str, str, str]:
        matched_color = self.thread_library.find_closest_color(normalized_color, tolerance)

find_closest_color delegates to _color_distance_hsv. HSV is not perceptually uniform — equal numerical distances in HSV don't correspond to equal perceived color differences. This produces thread matches that look wrong to human eyes (a "good" HSV match can be visually distant).

Goal

Switch the distance metric to ΔE-2000 (CIEDE2000), which is the standard for perceptually accurate color difference.

Why ΔE-2000

  • Industry standard for textile / print color matching
  • Implemented in colormath, colour, scipy (via skimage), and many others
  • Handles perceptual non-uniformity (Lab color space + correction terms)
  • Already mentioned in ThreadCraft #11 as the required algorithm

Implementation

  1. Add a _color_distance_delta_e_2000(rgb1, rgb2) helper that computes CIEDE2000 distance.
    • Either implement directly (small, ~50 lines) or pull from scikit-image (already a dependency).
    • scikit-image.color.deltaE_ciede2000 works directly.
  2. Add a distance_metric parameter to ThreadMatcher.__init__ (default "delta_e_2000", allow "hsv" for back-compat).
  3. Replace _color_distance_hsv calls with _color_distance_delta_e_2000 when the new metric is selected.
  4. Update tolerance default — ΔE thresholds are different from HSV thresholds. Reasonable defaults: tolerance=5.0 (ΔE), tolerance=50.0 (HSV).

Acceptance

  • ThreadMatcher().match_color("#aabbcc") returns a match where the ΔE distance is below 5 (perceptually close).
  • A unit test asserts that two near-identical inputs return the same catalog entry, and two perceptually distinct inputs return distinct entries.
  • The existing HSV path still works (parametrize, don't remove).
  • Tolerance defaults are documented.

Blocks

  • Mark/easystitch-service#1 — /v1/threads/resolve response shape requires deltaE: float per match.

Notes

  • Lab conversion is required for ΔE-2000. Either sRGB->Lab (via skimage or colormath) or direct conversion.
  • Catalog entries should be stored as Lab or RGB+Lab (ΔE needs Lab). The existing catalog stores hex; conversion at match-time is fine for v1.
## Context `src/embroidery_converter/thread_manager.py` uses HSV distance for thread matching: ```python class ThreadMatcher: def match_color(self, input_color: str, tolerance: float = 50.0) -> tuple[str, str, str]: matched_color = self.thread_library.find_closest_color(normalized_color, tolerance) ``` `find_closest_color` delegates to `_color_distance_hsv`. HSV is **not perceptually uniform** — equal numerical distances in HSV don't correspond to equal perceived color differences. This produces thread matches that look wrong to human eyes (a "good" HSV match can be visually distant). ## Goal Switch the distance metric to ΔE-2000 (CIEDE2000), which is the standard for perceptually accurate color difference. ## Why ΔE-2000 - Industry standard for textile / print color matching - Implemented in colormath, colour, scipy (via skimage), and many others - Handles perceptual non-uniformity (Lab color space + correction terms) - Already mentioned in ThreadCraft #11 as the required algorithm ## Implementation 1. Add a `_color_distance_delta_e_2000(rgb1, rgb2)` helper that computes CIEDE2000 distance. - Either implement directly (small, ~50 lines) or pull from `scikit-image` (already a dependency). - `scikit-image.color.deltaE_ciede2000` works directly. 2. Add a `distance_metric` parameter to `ThreadMatcher.__init__` (default `"delta_e_2000"`, allow `"hsv"` for back-compat). 3. Replace `_color_distance_hsv` calls with `_color_distance_delta_e_2000` when the new metric is selected. 4. Update tolerance default — ΔE thresholds are different from HSV thresholds. Reasonable defaults: `tolerance=5.0` (ΔE), `tolerance=50.0` (HSV). ## Acceptance - `ThreadMatcher().match_color("#aabbcc")` returns a match where the ΔE distance is below 5 (perceptually close). - A unit test asserts that two near-identical inputs return the same catalog entry, and two perceptually distinct inputs return distinct entries. - The existing HSV path still works (parametrize, don't remove). - Tolerance defaults are documented. ## Blocks - `Mark/easystitch-service#1` — `/v1/threads/resolve` response shape requires `deltaE: float` per match. ## Notes - Lab conversion is required for ΔE-2000. Either sRGB->Lab (via skimage or colormath) or direct conversion. - Catalog entries should be stored as Lab or RGB+Lab (ΔE needs Lab). The existing catalog stores hex; conversion at match-time is fine for v1.
Mark 2026-06-24 00:11:39 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Mark/easystitch#79
No description provided.