duongthienz commited on
Commit
87018fc
Β·
verified Β·
1 Parent(s): ae3e6a3

Add a new color logic for plotly (Needs some more adjustments later)

Browse files
Files changed (1) hide show
  1. utils.py +72 -53
utils.py CHANGED
@@ -40,50 +40,75 @@ TRANSPARENT_BG = dict(
40
  # ---------------------------------------------------------------------------
41
 
42
  # ---------------------------------------------------------------------------
43
- # Fixed 24-color palette: 6 hues Γ— 4 saturation/brightness tiers.
44
- # Hue order: red, yellow, green, cyan, blue, magenta β€” the 6 perceptually
45
- # distinct primaries. Cycling through all 6 hues before repeating a tier
46
- # means no two adjacent speakers ever share a similar hue.
47
- # Tiers (OpenCV HSV sat, val):
48
- # 1 β€” vivid saturated (255, 200)
49
- # 2 β€” medium bright (180, 230)
50
- # 3 β€” deep dark (255, 140)
51
- # 4 β€” pale tint (100, 240)
52
- # The palette is interleaved hue-first so index 0=red-vivid, 1=yellow-vivid,
53
- # 2=green-vivid ... 6=red-medium, 7=yellow-medium, etc.
54
  # ---------------------------------------------------------------------------
55
- def _build_palette():
56
- # OpenCV HSV hue is 0-179 (half-degrees)
57
- hues_deg = [0, 30, 60, 90, 120, 150] # red, yellow, green, cyan, blue, magenta
58
- tiers = [(255, 200), (180, 230), (255, 140), (100, 240)]
59
- palette = []
60
- for sat, val in tiers:
61
- for h_deg in hues_deg:
62
- h_cv = h_deg # already 0-179 range
63
- hsv = np.uint8([[[h_cv, sat, val]]])
64
- bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
65
- b = f'{bgr[0][0][0].item():02x}'
66
- g = f'{bgr[0][0][1].item():02x}'
67
- r = f'{bgr[0][0][2].item():02x}'
68
- palette.append('#' + b + g + r)
69
- return palette # 24 colors
70
-
71
- _PALETTE = _build_palette() + ["#999DA0"] # index 24: grey for No Voice
72
 
 
 
73
 
74
- def colorsCSS(n, startingHue=None, pool=None):
75
- """Return n CSS hex colors drawn from the fixed 24-color palette.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
- Colors are taken in palette order (red→yellow→green→cyan→blue→magenta,
78
- then repeating across brightness tiers) so adjacent speakers always have
79
- maximally different hues. Cycles if n > 24.
80
 
81
- startingHue and pool are accepted for backwards compatibility but ignored β€”
82
- the palette is fixed so colors are always distinct and deterministic.
 
 
 
83
  """
84
  if n == 0:
85
  return []
86
- return [_PALETTE[i % len(_PALETTE)] for i in range(n)]
 
87
 
88
 
89
  def extract_clip_bytes(waveform, sample_rate, seg_start, seg_end):
@@ -324,7 +349,7 @@ def build_fig_pie2(df4, speakerNames, speakerColors, catColors, get_display_name
324
  fig = go.Figure()
325
  fig.update_layout(
326
  title_text="Percentage of Speakers per Role",
327
- colorway=catColors + figColors,
328
  **TRANSPARENT_BG,
329
  )
330
  fig.add_trace(go.Pie(values=df4["values"], labels=df4["names"], sort=False))
@@ -334,26 +359,20 @@ def build_fig_pie2(df4, speakerNames, speakerColors, catColors, get_display_name
334
  def _voice_color_map(df5_labels, speakerColors):
335
  """Build the label->color map for sunburst/treemap charts.
336
 
337
- Single Voice β†’ palette index 0 (always)
338
- Multi Voice β†’ palette index 4 (always)
339
- No Voice β†’ palette index 8 (neutral, always)
340
- Speakers β†’ cycle through every index EXCEPT 0 and 4 so they never
341
- blend into their parent category layer.
342
  """
343
- reserved = {0, 4}
344
- speaker_indices = [i for i in range(24) if i not in reserved] # exclude grey index
345
-
346
  top_labels = ["No Voice", "Single Voice", "Multi Voice"]
347
  color_map = {
348
- "Single Voice": _PALETTE[0],
349
- "Multi Voice": _PALETTE[4],
350
- "No Voice": _PALETTE[24], # grey
351
  }
352
  speaker_labels = [l for l in df5_labels if l not in top_labels]
353
  for i, lbl in enumerate(speaker_labels):
354
- color_map[lbl] = speaker_indices[i % len(speaker_indices)]
355
- # speaker_indices contains palette indices; convert to hex
356
- color_map[lbl] = _PALETTE[speaker_indices[i % len(speaker_indices)]]
357
  return color_map
358
 
359
 
@@ -436,7 +455,7 @@ def build_fig_timeline(speakers_dataFrame, currTotalTime, speakerColors, get_dis
436
  fig = px.timeline(
437
  df, x_start="Start", x_end="Finish", y="Resource", color="Resource",
438
  title="Timeline of Audio with Speakers",
439
- color_discrete_sequence=speakerColors,
440
  )
441
  fig.update_yaxes(autorange="reversed")
442
 
@@ -486,7 +505,7 @@ def build_fig_bar(df2, speakerNames, catColors, speakerColors, get_display_name_
486
  df2, x="values", y="names", color="names", orientation="h",
487
  custom_data=["names", "time_label"],
488
  title="Time Spoken by each Speaker",
489
- color_discrete_sequence=catColors + speakerColors,
490
  )
491
  # Hide x-axis tick labels β€” values are crowded with many speakers.
492
  # The exact time is still visible on hover via the hovertemplate.
 
40
  # ---------------------------------------------------------------------------
41
 
42
  # ---------------------------------------------------------------------------
43
+ # Color palette β€” loaded from plotly_colorwheel.txt at import time.
44
+ # Edit that file to change colors; no code changes needed.
45
+ # Structure: 8 colors Γ— 3 shades (indices 0-23) + 1 grey (index 24).
46
+ # Reserved indices:
47
+ # 0 = Single Voice (red shade 0)
48
+ # 3 = Multi Voice (green shade 0)
49
+ # 24 = No Voice (grey)
50
+ # Speakers cycle through all other indices in order.
 
 
 
51
  # ---------------------------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
+ def _load_palette(path="plotly_colorwheel.txt"):
54
+ """Parse plotly_colorwheel.txt (id, color, shade, hex table) into a list.
55
 
56
+ Returns a list indexed by id so _PALETTE[id] gives the hex color directly.
57
+ Falls back to hardcoded defaults if file is missing.
58
+ """
59
+ FALLBACK = [
60
+ "#de2d26","#fc9272","#fee0d2", # red 0-2
61
+ "#e6550d","#fdae6b","#feedde", # orange 3-5
62
+ "#d9b300","#fdd44d","#fff7bc", # yellow 6-8
63
+ "#31a354","#a1d99b","#e5f5e0", # green 9-11
64
+ "#1a9e9e","#66c2c2","#ccecec", # teal 12-14
65
+ "#3182bd","#9ecae1","#deebf7", # blue 15-17
66
+ "#756bb1","#bcbddc","#efedf5", # purple 18-20
67
+ "#d4679a","#f1b6d1","#fce4f0", # pink 21-23
68
+ "#999DA0", # grey 24
69
+ ]
70
+ try:
71
+ entries = {}
72
+ with open(path, "r") as f:
73
+ for line in f:
74
+ line = line.strip()
75
+ if not line or line.startswith("#"):
76
+ continue
77
+ parts = [p.strip() for p in line.split(",")]
78
+ if len(parts) < 4:
79
+ continue
80
+ try:
81
+ idx = int(parts[0])
82
+ hex_color = parts[3]
83
+ if hex_color.startswith("#"):
84
+ entries[idx] = hex_color
85
+ except ValueError:
86
+ continue
87
+ if not entries:
88
+ return FALLBACK
89
+ max_idx = max(entries.keys())
90
+ palette = [entries.get(i, "#cccccc") for i in range(max_idx + 1)]
91
+ return palette
92
+ except FileNotFoundError:
93
+ return FALLBACK
94
+
95
+ _PALETTE = _load_palette()
96
+ # Reserved: 0 = Single Voice (red shade 0), 9 = Multi Voice (green shade 0),
97
+ # 24 = No Voice (grey). Speakers use all other indices.
98
+ _RESERVED = {0, 9, len(_PALETTE) - 1}
99
+ _SPEAKER_PALETTE = [c for i, c in enumerate(_PALETTE) if i not in _RESERVED]
100
 
 
 
 
101
 
102
+ def colorsCSS(n, startingHue=None, pool=None):
103
+ """Return n CSS hex colors from the speaker palette.
104
+
105
+ Cycles if n > len(_SPEAKER_PALETTE).
106
+ startingHue and pool accepted for backwards compatibility but ignored.
107
  """
108
  if n == 0:
109
  return []
110
+ pal = _SPEAKER_PALETTE if _SPEAKER_PALETTE else _PALETTE
111
+ return [pal[i % len(pal)] for i in range(n)]
112
 
113
 
114
  def extract_clip_bytes(waveform, sample_rate, seg_start, seg_end):
 
349
  fig = go.Figure()
350
  fig.update_layout(
351
  title_text="Percentage of Speakers per Role",
352
+ colorway=_SPEAKER_PALETTE,
353
  **TRANSPARENT_BG,
354
  )
355
  fig.add_trace(go.Pie(values=df4["values"], labels=df4["names"], sort=False))
 
359
  def _voice_color_map(df5_labels, speakerColors):
360
  """Build the label->color map for sunburst/treemap charts.
361
 
362
+ Single Voice β†’ _PALETTE[0] (red shade 0, reserved)
363
+ Multi Voice β†’ _PALETTE[3] (green shade 0, reserved)
364
+ No Voice β†’ _PALETTE[-1] (grey, reserved)
365
+ Speakers β†’ _SPEAKER_PALETTE in order (never collides with reserved)
 
366
  """
 
 
 
367
  top_labels = ["No Voice", "Single Voice", "Multi Voice"]
368
  color_map = {
369
+ "Single Voice": _PALETTE[0], # red shade 0
370
+ "Multi Voice": _PALETTE[9], # green shade 0
371
+ "No Voice": _PALETTE[-1], # grey
372
  }
373
  speaker_labels = [l for l in df5_labels if l not in top_labels]
374
  for i, lbl in enumerate(speaker_labels):
375
+ color_map[lbl] = _SPEAKER_PALETTE[i % len(_SPEAKER_PALETTE)]
 
 
376
  return color_map
377
 
378
 
 
455
  fig = px.timeline(
456
  df, x_start="Start", x_end="Finish", y="Resource", color="Resource",
457
  title="Timeline of Audio with Speakers",
458
+ color_discrete_sequence=_SPEAKER_PALETTE,
459
  )
460
  fig.update_yaxes(autorange="reversed")
461
 
 
505
  df2, x="values", y="names", color="names", orientation="h",
506
  custom_data=["names", "time_label"],
507
  title="Time Spoken by each Speaker",
508
+ color_discrete_sequence=_SPEAKER_PALETTE,
509
  )
510
  # Hide x-axis tick labels β€” values are crowded with many speakers.
511
  # The exact time is still visible on hover via the hovertemplate.