|
| 1 | +""" pyplots.ai |
| 2 | +spectrogram-basic: Spectrogram Time-Frequency Heatmap |
| 3 | +Library: bokeh 3.8.1 | Python 3.13.11 |
| 4 | +Quality: 92/100 | Created: 2025-12-31 |
| 5 | +""" |
| 6 | + |
| 7 | +import numpy as np |
| 8 | +from bokeh.io import export_png, output_file, save |
| 9 | +from bokeh.models import BasicTicker, ColorBar, LinearColorMapper |
| 10 | +from bokeh.plotting import figure |
| 11 | +from scipy import signal |
| 12 | + |
| 13 | + |
| 14 | +# Data - Generate chirp signal with increasing frequency |
| 15 | +np.random.seed(42) |
| 16 | +sample_rate = 8000 # 8 kHz sampling rate |
| 17 | +duration = 2.0 # 2 seconds |
| 18 | +t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False) |
| 19 | + |
| 20 | +# Create a chirp signal: frequency increases from 200 Hz to 2000 Hz |
| 21 | +f0 = 200 # Start frequency |
| 22 | +f1 = 2000 # End frequency |
| 23 | +chirp_signal = signal.chirp(t, f0, duration, f1, method="linear") |
| 24 | + |
| 25 | +# Add some noise for realism |
| 26 | +chirp_signal += 0.1 * np.random.randn(len(chirp_signal)) |
| 27 | + |
| 28 | +# Compute spectrogram using scipy.signal |
| 29 | +nperseg = 256 # Window size |
| 30 | +noverlap = 192 # Overlap (75%) |
| 31 | +frequencies, times, Sxx = signal.spectrogram( |
| 32 | + chirp_signal, fs=sample_rate, nperseg=nperseg, noverlap=noverlap, scaling="density" |
| 33 | +) |
| 34 | + |
| 35 | +# Convert to dB scale for better visualization |
| 36 | +Sxx_db = 10 * np.log10(Sxx + 1e-10) # Add small value to avoid log(0) |
| 37 | + |
| 38 | +# Viridis palette (256 colors) - perceptually uniform |
| 39 | +viridis = [ |
| 40 | + "#440154", |
| 41 | + "#440256", |
| 42 | + "#450457", |
| 43 | + "#450559", |
| 44 | + "#46075a", |
| 45 | + "#46085c", |
| 46 | + "#460a5d", |
| 47 | + "#460b5e", |
| 48 | + "#470d60", |
| 49 | + "#470e61", |
| 50 | + "#471063", |
| 51 | + "#471164", |
| 52 | + "#471365", |
| 53 | + "#481467", |
| 54 | + "#481668", |
| 55 | + "#481769", |
| 56 | + "#48186a", |
| 57 | + "#481a6c", |
| 58 | + "#481b6d", |
| 59 | + "#481c6e", |
| 60 | + "#481d6f", |
| 61 | + "#481f70", |
| 62 | + "#482071", |
| 63 | + "#482173", |
| 64 | + "#482374", |
| 65 | + "#482475", |
| 66 | + "#482576", |
| 67 | + "#482677", |
| 68 | + "#482878", |
| 69 | + "#482979", |
| 70 | + "#472a79", |
| 71 | + "#472c7a", |
| 72 | + "#472d7b", |
| 73 | + "#472e7c", |
| 74 | + "#472f7d", |
| 75 | + "#46307e", |
| 76 | + "#46327e", |
| 77 | + "#46337f", |
| 78 | + "#463480", |
| 79 | + "#453581", |
| 80 | + "#453681", |
| 81 | + "#453882", |
| 82 | + "#443983", |
| 83 | + "#443a83", |
| 84 | + "#443b84", |
| 85 | + "#433d84", |
| 86 | + "#433e85", |
| 87 | + "#423f85", |
| 88 | + "#424086", |
| 89 | + "#424186", |
| 90 | + "#414287", |
| 91 | + "#414487", |
| 92 | + "#404588", |
| 93 | + "#404688", |
| 94 | + "#3f4788", |
| 95 | + "#3f4889", |
| 96 | + "#3e4989", |
| 97 | + "#3e4a89", |
| 98 | + "#3d4b89", |
| 99 | + "#3d4c89", |
| 100 | + "#3c4d8a", |
| 101 | + "#3c4e8a", |
| 102 | + "#3b508a", |
| 103 | + "#3b518a", |
| 104 | + "#3a528b", |
| 105 | + "#3a538b", |
| 106 | + "#39548b", |
| 107 | + "#39558b", |
| 108 | + "#38568b", |
| 109 | + "#38578c", |
| 110 | + "#37588c", |
| 111 | + "#37598c", |
| 112 | + "#365a8c", |
| 113 | + "#365b8c", |
| 114 | + "#355c8c", |
| 115 | + "#355d8c", |
| 116 | + "#345e8d", |
| 117 | + "#345f8d", |
| 118 | + "#33608d", |
| 119 | + "#33618d", |
| 120 | + "#32628d", |
| 121 | + "#32638d", |
| 122 | + "#31648d", |
| 123 | + "#31658d", |
| 124 | + "#31668d", |
| 125 | + "#30678d", |
| 126 | + "#30688d", |
| 127 | + "#2f698d", |
| 128 | + "#2f6a8d", |
| 129 | + "#2e6b8e", |
| 130 | + "#2e6c8e", |
| 131 | + "#2e6d8e", |
| 132 | + "#2d6e8e", |
| 133 | + "#2d6f8e", |
| 134 | + "#2c708e", |
| 135 | + "#2c718e", |
| 136 | + "#2c728e", |
| 137 | + "#2b738e", |
| 138 | + "#2b748e", |
| 139 | + "#2a758e", |
| 140 | + "#2a768e", |
| 141 | + "#2a778e", |
| 142 | + "#29788e", |
| 143 | + "#29798e", |
| 144 | + "#297a8e", |
| 145 | + "#287b8e", |
| 146 | + "#287c8e", |
| 147 | + "#277d8e", |
| 148 | + "#277e8e", |
| 149 | + "#277f8e", |
| 150 | + "#26808e", |
| 151 | + "#26818e", |
| 152 | + "#26828e", |
| 153 | + "#25838e", |
| 154 | + "#25848e", |
| 155 | + "#25858e", |
| 156 | + "#24868e", |
| 157 | + "#24878e", |
| 158 | + "#23888e", |
| 159 | + "#23898e", |
| 160 | + "#238a8d", |
| 161 | + "#228b8d", |
| 162 | + "#228c8d", |
| 163 | + "#228d8d", |
| 164 | + "#218e8d", |
| 165 | + "#218f8d", |
| 166 | + "#21908d", |
| 167 | + "#21918c", |
| 168 | + "#20928c", |
| 169 | + "#20938c", |
| 170 | + "#20948c", |
| 171 | + "#1f958b", |
| 172 | + "#1f968b", |
| 173 | + "#1f978b", |
| 174 | + "#1f988a", |
| 175 | + "#1f998a", |
| 176 | + "#1f9a8a", |
| 177 | + "#1e9b89", |
| 178 | + "#1e9c89", |
| 179 | + "#1e9d88", |
| 180 | + "#1f9e88", |
| 181 | + "#1f9f88", |
| 182 | + "#1fa087", |
| 183 | + "#1fa187", |
| 184 | + "#1fa286", |
| 185 | + "#20a386", |
| 186 | + "#20a485", |
| 187 | + "#21a585", |
| 188 | + "#21a684", |
| 189 | + "#22a784", |
| 190 | + "#22a883", |
| 191 | + "#23a982", |
| 192 | + "#24aa82", |
| 193 | + "#24ab81", |
| 194 | + "#25ac81", |
| 195 | + "#26ad80", |
| 196 | + "#27ae80", |
| 197 | + "#27af7f", |
| 198 | + "#28b07e", |
| 199 | + "#29b17e", |
| 200 | + "#2ab27d", |
| 201 | + "#2cb37c", |
| 202 | + "#2db47c", |
| 203 | + "#2eb57b", |
| 204 | + "#2fb67a", |
| 205 | + "#30b77a", |
| 206 | + "#32b879", |
| 207 | + "#33b978", |
| 208 | + "#35ba78", |
| 209 | + "#36bb77", |
| 210 | + "#37bc76", |
| 211 | + "#39bd75", |
| 212 | + "#3abe75", |
| 213 | + "#3cbf74", |
| 214 | + "#3ec073", |
| 215 | + "#3fc172", |
| 216 | + "#41c272", |
| 217 | + "#43c371", |
| 218 | + "#44c470", |
| 219 | + "#46c56f", |
| 220 | + "#48c66e", |
| 221 | + "#4ac76d", |
| 222 | + "#4cc86d", |
| 223 | + "#4ec96c", |
| 224 | + "#50ca6b", |
| 225 | + "#51cb6a", |
| 226 | + "#53cc69", |
| 227 | + "#55cd68", |
| 228 | + "#57ce67", |
| 229 | + "#59cf66", |
| 230 | + "#5bd066", |
| 231 | + "#5dd165", |
| 232 | + "#5fd264", |
| 233 | + "#61d363", |
| 234 | + "#63d462", |
| 235 | + "#65d561", |
| 236 | + "#67d660", |
| 237 | + "#69d75f", |
| 238 | + "#6cd85e", |
| 239 | + "#6ed95d", |
| 240 | + "#70da5c", |
| 241 | + "#72db5b", |
| 242 | + "#74dc5a", |
| 243 | + "#76dd59", |
| 244 | + "#78de58", |
| 245 | + "#7bdf57", |
| 246 | + "#7de056", |
| 247 | + "#7fe155", |
| 248 | + "#81e254", |
| 249 | + "#83e353", |
| 250 | + "#86e452", |
| 251 | + "#88e551", |
| 252 | + "#8ae64f", |
| 253 | + "#8ce74e", |
| 254 | + "#8fe84d", |
| 255 | + "#91e94c", |
| 256 | + "#93ea4b", |
| 257 | + "#96eb4a", |
| 258 | + "#98ec49", |
| 259 | + "#9aed48", |
| 260 | + "#9dee47", |
| 261 | + "#9fef46", |
| 262 | + "#a1f045", |
| 263 | + "#a4f144", |
| 264 | + "#a6f243", |
| 265 | + "#a8f342", |
| 266 | + "#abf341", |
| 267 | + "#adf440", |
| 268 | + "#b0f540", |
| 269 | + "#b2f63f", |
| 270 | + "#b5f73e", |
| 271 | + "#b7f83d", |
| 272 | + "#baf93c", |
| 273 | + "#bcfa3c", |
| 274 | + "#bffb3b", |
| 275 | + "#c1fc3b", |
| 276 | + "#c4fd3a", |
| 277 | + "#c7fd3a", |
| 278 | + "#c9fe39", |
| 279 | + "#ccfe39", |
| 280 | + "#cfff38", |
| 281 | + "#d1ff38", |
| 282 | + "#d4ff38", |
| 283 | + "#d7ff37", |
| 284 | + "#d9ff37", |
| 285 | + "#dcff37", |
| 286 | + "#dfff36", |
| 287 | + "#e1ff36", |
| 288 | + "#e4ff36", |
| 289 | + "#e7ff36", |
| 290 | + "#e9ff36", |
| 291 | + "#ecff36", |
| 292 | + "#efff36", |
| 293 | + "#f1ff36", |
| 294 | + "#f4ff36", |
| 295 | + "#f6ff36", |
| 296 | +] |
| 297 | + |
| 298 | +# Create figure with appropriate size |
| 299 | +p = figure( |
| 300 | + width=4800, |
| 301 | + height=2700, |
| 302 | + title="spectrogram-basic · bokeh · pyplots.ai", |
| 303 | + x_axis_label="Time (seconds)", |
| 304 | + y_axis_label="Frequency (Hz)", |
| 305 | + x_range=(times.min(), times.max()), |
| 306 | + y_range=(frequencies.min(), frequencies.max()), |
| 307 | + tools="", |
| 308 | + toolbar_location=None, |
| 309 | +) |
| 310 | + |
| 311 | +# Create color mapper |
| 312 | +color_mapper = LinearColorMapper(palette=viridis, low=Sxx_db.min(), high=Sxx_db.max()) |
| 313 | + |
| 314 | +# Render spectrogram as image |
| 315 | +p.image( |
| 316 | + image=[Sxx_db], |
| 317 | + x=times.min(), |
| 318 | + y=frequencies.min(), |
| 319 | + dw=times.max() - times.min(), |
| 320 | + dh=frequencies.max() - frequencies.min(), |
| 321 | + color_mapper=color_mapper, |
| 322 | + level="image", |
| 323 | +) |
| 324 | + |
| 325 | +# Add colorbar with larger text |
| 326 | +color_bar = ColorBar( |
| 327 | + color_mapper=color_mapper, |
| 328 | + ticker=BasicTicker(), |
| 329 | + label_standoff=20, |
| 330 | + border_line_color=None, |
| 331 | + location=(0, 0), |
| 332 | + title="Power (dB)", |
| 333 | + title_text_font_size="36pt", |
| 334 | + major_label_text_font_size="28pt", |
| 335 | + width=80, |
| 336 | + padding=40, |
| 337 | +) |
| 338 | +p.add_layout(color_bar, "right") |
| 339 | + |
| 340 | +# Style text sizes for 4800x2700 canvas - enlarged for visibility |
| 341 | +p.title.text_font_size = "48pt" |
| 342 | +p.xaxis.axis_label_text_font_size = "36pt" |
| 343 | +p.yaxis.axis_label_text_font_size = "36pt" |
| 344 | +p.xaxis.major_label_text_font_size = "28pt" |
| 345 | +p.yaxis.major_label_text_font_size = "28pt" |
| 346 | + |
| 347 | +# Axis styling |
| 348 | +p.xaxis.axis_line_width = 3 |
| 349 | +p.yaxis.axis_line_width = 3 |
| 350 | +p.xaxis.major_tick_line_width = 3 |
| 351 | +p.yaxis.major_tick_line_width = 3 |
| 352 | + |
| 353 | +# Grid styling - subtle |
| 354 | +p.xgrid.grid_line_alpha = 0.3 |
| 355 | +p.ygrid.grid_line_alpha = 0.3 |
| 356 | +p.xgrid.grid_line_dash = "dashed" |
| 357 | +p.ygrid.grid_line_dash = "dashed" |
| 358 | + |
| 359 | +# Background |
| 360 | +p.background_fill_color = None |
| 361 | +p.border_fill_color = None |
| 362 | + |
| 363 | +# Outline |
| 364 | +p.outline_line_color = "#333333" |
| 365 | +p.outline_line_width = 2 |
| 366 | + |
| 367 | +# Save PNG |
| 368 | +export_png(p, filename="plot.png") |
| 369 | + |
| 370 | +# Save HTML for interactive viewing |
| 371 | +output_file("plot.html", title="spectrogram-basic · bokeh · pyplots.ai") |
| 372 | +save(p) |
0 commit comments