File size: 5,626 Bytes
2dd1cd7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import numpy as np

SPEAKER_COLORS = [
    "#76B900",  # Green
    "#49A4DE",  # Blue
    "#E06C75",  # Red
    "#C678DD",  # Purple
    "#E5A84B",  # Orange
    "#36CFC9",  # Cyan
    "#FF7AB2",  # Pink
    "#F5F5F5",  # White
]


def array_to_gradient_html(arr=None, num_frames=125, num_of_speakers=4, partitions=None):
    """
    Convert num_of_speakers by num_frames numpy array to HTML gradient boxes.
    
    Parameters:
    -----------
    arr : numpy.ndarray, optional
        num_of_speakers by num_frames array with values from 0.0 to 1.0
        Shape: (num_of_speakers, num_frames)
        If None, creates a default random array
    num_frames : int, default 125
        Number of frames/columns (only used if arr is None)
    num_of_speakers : int, default 4
        Number of speakers/rows (only used if arr is None)
    partitions : list of dict, optional
        Named activity arrays rendered as proportional sections. Each dictionary contains
        ``name``, ``values``, ``capacity``, and optionally ``valid_frames``.
    
    Returns:
    --------
    str : HTML string with colored gradient boxes
    
    Example:
    --------
    >>> arr = np.array([[0.0, 0.25, 0.5, 0.75, 1.0]])  # 1 speaker, 5 frames
    >>> html = array_to_gradient_html(arr)
    >>> print(html)
    """
    # Color map for different speakers (black background to full color)
    # Matching the app.py color scheme for dark theme
    color_map = {
        speaker_idx: tuple(int(color[offset : offset + 2], 16) for offset in (1, 3, 5))
        for speaker_idx, color in enumerate(SPEAKER_COLORS)
    }
    
    def value_to_color(value, speaker_idx):
        """
        Convert a value from 0.0 to 1.0 to a color gradient for a specific speaker.
        
        0.0 -> #000000 (black background)
        1.0 -> speaker's color (green, blue, gray, or red)
        """
        # Clamp value between 0 and 1
        value = max(0.0, min(1.0, value))
        
        # Get speaker's target color (cycle through colors if more speakers than colors)
        speaker_color = color_map.get(speaker_idx % len(color_map), (0, 0, 0))
        target_r, target_g, target_b = speaker_color
        
        # Gradient from black (0, 0, 0) to speaker's color
        # Linear interpolation between black and target color
        r = int(target_r * value)
        g = int(target_g * value)
        b = int(target_b * value)
        
        return f"#{r:02X}{g:02X}{b:02X}"
    
    if partitions is None:
        if arr is None:
            arr = np.random.rand(num_of_speakers, num_frames)
        partitions = [{"name": "DIARIZATION", "values": arr, "capacity": num_frames}]

    prepared_partitions = []
    for partition in partitions:
        capacity = max(1, int(partition["capacity"]))
        values = partition.get("values")
        if values is None:
            values = np.zeros((num_of_speakers, 0), dtype=np.float32)
        values = np.asarray(values)
        if values.ndim != 2:
            raise ValueError(f"Expected a 2-D speaker activity array, but received shape {values.shape}")
        if values.shape[0] < num_of_speakers:
            values = np.pad(values, ((0, num_of_speakers - values.shape[0]), (0, 0)), mode='constant')
        else:
            values = values[:num_of_speakers]
        if values.shape[1] < capacity:
            values = np.pad(values, ((0, 0), (capacity - values.shape[1], 0)), mode='constant')
        else:
            values = values[:, -capacity:]
        prepared_partitions.append(
            {
                "name": partition["name"],
                "values": values,
                "capacity": capacity,
                "valid_frames": min(int(partition.get("valid_frames", values.shape[1])), capacity),
            }
        )

    column_weights = " ".join(f"{partition['capacity']}fr" for partition in prepared_partitions)
    html = '<div class="diar-partition-labels">'
    for partition in prepared_partitions:
        html += (
            f'<span class="diar-partition-label">{partition["name"]} '
            f'{partition["valid_frames"]}/{partition["capacity"]}</span>'
        )
    html += '</div>'
    html += '<div class="diar-map">'
    for speaker_idx in range(num_of_speakers):
        html += (
            f'<div class="diar-row" style="grid-template-columns:{column_weights};" '
            f'title="Speaker {speaker_idx + 1}">'
        )
        for partition in prepared_partitions:
            html += (
                f'<div class="diar-partition" title="{partition["name"]}" '
                f'style="grid-template-columns:repeat({partition["capacity"]}, minmax(0, 1fr));">'
            )
            for val in partition["values"][speaker_idx]:
                color = value_to_color(val, speaker_idx)
                html += f'<span class="diar-cell" style="background-color:{color};"></span>'
            html += '</div>'
        html += '</div>'
    html += '</div>'

    return html


if __name__ == "__main__":
    
    # Example 3: Default parameters
    print("Example 3: Using default parameters (4 speakers x 125 frames)")
    html3 = array_to_gradient_html()
    # print(html3[:500] + "...")  # Print first 500 chars
    print(html3)
    print(f"\nTotal HTML length: {len(html3)} characters")
    
    # Example 4: Show the 20-step gradient like in the original example
    # print("\n" + "="*50 + "\n")
    # print("Example 4: 20-step gradient (like the original) - 1 speaker x 20 frames")
    # gradient_arr = np.linspace(0, 1, 20).reshape(1, 20)  # Shape: (1, 20)
    # html4 = array_to_gradient_html(gradient_arr)
    # print(html4)