File size: 5,906 Bytes
742e266
0020ddc
 
742e266
0020ddc
742e266
0020ddc
742e266
0020ddc
742e266
0020ddc
742e266
 
0020ddc
 
 
 
 
 
 
742e266
 
 
0020ddc
 
 
742e266
 
0020ddc
742e266
 
 
 
 
0020ddc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
742e266
 
 
 
0020ddc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
742e266
 
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# Created: 2026-02-18
# Purpose: P(AI) per-segment timeline bar chart with waveform (plotly)
# Dependencies: plotly, numpy

"""Per-segment (chunk) AI probability timeline visualization with waveform."""

import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots

from config import CHUNK_SEC, SR, CHUNK_SAMPLES


def plot_timeline(

    chunk_probs: list[float],

    waveform: np.ndarray = None,

    chunk_metadata: list[dict] = None,

    weighted_median: float = None

) -> go.Figure:
    """Per-chunk P(AI) timeline bar chart with optional waveform.



    Args:

        chunk_probs: P(AI) list for each 4-second chunk

        waveform: Optional mono waveform array for envelope visualization

        chunk_metadata: Optional metadata with start_sample info

        weighted_median: Energy-weighted median P(AI) for reference line



    Returns:

        plotly Figure with waveform (top) + P(AI) bars (bottom)

    """
    n = len(chunk_probs)
    times = [f"{i * CHUNK_SEC:.0f}-{(i + 1) * CHUNK_SEC:.0f}s" for i in range(n)]
    colors = ['#ff4757' if p >= 0.5 else '#2ed573' for p in chunk_probs]

    # ํŒŒํ˜•์ด ์žˆ์œผ๋ฉด subplot, ์—†์œผ๋ฉด ๋‹จ์ˆœ bar chart
    if waveform is not None and len(waveform) > 0:
        fig = make_subplots(
            rows=2, cols=1,
            row_heights=[0.3, 0.7],
            vertical_spacing=0.08,
            subplot_titles=("Waveform Envelope", "Segment-level AI Probability"),
        )

        # Waveform envelope (unipolar - ์ ˆ๋Œ“๊ฐ’์˜ ์ƒ๋‹จ๋งŒ)
        time_axis = np.arange(len(waveform)) / SR
        envelope = np.abs(waveform)

        # Downsample for plotting (๋งค 100 ์ƒ˜ํ”Œ๋งˆ๋‹ค)
        downsample_factor = 100
        time_ds = time_axis[::downsample_factor]
        envelope_ds = envelope[::downsample_factor]

        fig.add_trace(
            go.Scatter(
                x=time_ds,
                y=envelope_ds,
                mode='lines',
                line=dict(color='#5f9ea0', width=0.5),
                fill='tozeroy',
                fillcolor='rgba(95, 158, 160, 0.3)',
                name='Envelope',
                hovertemplate="Time: %{x:.2f}s<br>Amplitude: %{y:.3f}<extra></extra>",
            ),
            row=1, col=1
        )

        # ์„ธ๊ทธ๋จผํŠธ ๊ฒฝ๊ณ„์„  ํ‘œ์‹œ (chunk metadata ์‚ฌ์šฉ)
        if chunk_metadata:
            for meta in chunk_metadata:
                start_sec = meta['start_sample'] / SR
                fig.add_vline(
                    x=start_sec,
                    line=dict(color='#ffa502', width=1, dash='dot'),
                    opacity=0.5,
                    row=1, col=1
                )

        # P(AI) bar chart
        fig.add_trace(
            go.Bar(
                x=list(range(n)),
                y=chunk_probs,
                marker_color=colors,
                text=[f"{p:.2f}" for p in chunk_probs],
                textposition='outside',
                textfont=dict(size=10, color='white'),
                hovertemplate="<b>%{customdata}</b><br>P(AI): %{y:.3f}<extra></extra>",
                customdata=times,
                name='P(AI)',
            ),
            row=2, col=1
        )

        # Energy-weighted median reference line
        if weighted_median is not None:
            fig.add_hline(
                y=weighted_median, line_dash="dash", line_color="#00d2ff",
                annotation_text=f"Weighted Median ({weighted_median:.2f})",
                annotation_position="top right",
                annotation_font_color="#00d2ff",
                annotation_font_size=10,
                row=2, col=1
            )

        # Layout
        fig.update_xaxes(title_text="Time (s)", row=1, col=1)
        fig.update_yaxes(title_text="Amplitude", row=1, col=1)
        fig.update_xaxes(
            title_text="Segment",
            tickvals=list(range(n)),
            ticktext=times,
            tickangle=-45,
            tickfont=dict(size=9),
            row=2, col=1
        )
        fig.update_yaxes(title_text="P(AI)", range=[0, 1.05], row=2, col=1)

        fig.update_layout(
            plot_bgcolor='#1a1a2e',
            paper_bgcolor='#1a1a2e',
            font=dict(color='white'),
            margin=dict(l=50, r=20, t=60, b=60),
            height=500,
            showlegend=False,
        )

    else:
        # Fallback: ๊ธฐ์กด ๋‹จ์ˆœ bar chart
        fig = go.Figure()

        fig.add_trace(go.Bar(
            x=list(range(n)),
            y=chunk_probs,
            marker_color=colors,
            text=[f"{p:.2f}" for p in chunk_probs],
            textposition='outside',
            textfont=dict(size=10, color='white'),
            hovertemplate="<b>%{customdata}</b><br>P(AI): %{y:.3f}<extra></extra>",
            customdata=times,
        ))

        if weighted_median is not None:
            fig.add_hline(y=weighted_median, line_dash="dash", line_color="#00d2ff",
                          annotation_text=f"Weighted Median ({weighted_median:.2f})",
                          annotation_position="top right",
                          annotation_font_color="#00d2ff")

        fig.update_layout(
            title=dict(text="Segment-level AI Probability", font=dict(size=14)),
            xaxis=dict(
                title="Segment",
                tickvals=list(range(n)),
                ticktext=times,
                tickangle=-45,
                tickfont=dict(size=9),
            ),
            yaxis=dict(title="P(AI)", range=[0, 1.05]),
            plot_bgcolor='#1a1a2e',
            paper_bgcolor='#1a1a2e',
            font=dict(color='white'),
            margin=dict(l=50, r=20, t=40, b=60),
            height=300,
            showlegend=False,
        )

    return fig