From 86a5af6bd191a08e576876c39a4ef75cd1155b44 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 11 Mar 2026 19:42:38 +0000 Subject: [PATCH 1/7] feat(bokeh): implement spectrogram-mel --- .../spectrogram-mel/implementations/bokeh.py | 449 ++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 plots/spectrogram-mel/implementations/bokeh.py diff --git a/plots/spectrogram-mel/implementations/bokeh.py b/plots/spectrogram-mel/implementations/bokeh.py new file mode 100644 index 0000000000..4dc9432fdf --- /dev/null +++ b/plots/spectrogram-mel/implementations/bokeh.py @@ -0,0 +1,449 @@ +"""pyplots.ai +spectrogram-mel: Mel-Spectrogram for Audio Analysis +Library: bokeh | Python 3.13 +Quality: pending | Created: 2026-03-11 +""" + +import numpy as np +from bokeh.io import export_png, output_file, save +from bokeh.models import BasicTicker, ColorBar, FixedTicker, LinearColorMapper +from bokeh.plotting import figure +from scipy import signal + + +# Data - Synthesize a melody-like audio signal with multiple frequency components +np.random.seed(42) +sample_rate = 22050 +duration = 4.0 +n_samples = int(sample_rate * duration) +t = np.linspace(0, duration, n_samples, endpoint=False) + +# Create a rich audio signal: melody with harmonics and transients +audio_signal = np.zeros(n_samples) + +# Melody notes (fundamental frequencies in Hz with harmonics) +notes = [ + (0.0, 1.0, 261.63), # C4 + (0.5, 1.5, 329.63), # E4 + (1.0, 2.0, 392.00), # G4 + (1.5, 2.5, 523.25), # C5 + (2.0, 3.0, 440.00), # A4 + (2.5, 3.5, 349.23), # F4 + (3.0, 4.0, 293.66), # D4 +] + +for start, end, freq in notes: + mask = (t >= start) & (t < end) + envelope = np.zeros(n_samples) + note_len = np.sum(mask) + # ADSR-like envelope + attack = int(0.05 * sample_rate) + release = int(0.1 * sample_rate) + if note_len > attack + release: + env = np.ones(note_len) + env[:attack] = np.linspace(0, 1, attack) + env[-release:] = np.linspace(1, 0, release) + envelope[mask] = env + # Fundamental + harmonics + audio_signal += envelope * ( + 0.6 * np.sin(2 * np.pi * freq * t) + + 0.25 * np.sin(2 * np.pi * 2 * freq * t) + + 0.1 * np.sin(2 * np.pi * 3 * freq * t) + + 0.05 * np.sin(2 * np.pi * 4 * freq * t) + ) + +# Add subtle background noise +audio_signal += 0.02 * np.random.randn(n_samples) + +# Normalize +audio_signal = audio_signal / np.max(np.abs(audio_signal)) + +# Compute STFT +n_fft = 2048 +hop_length = 512 +frequencies, times, Zxx = signal.stft(audio_signal, fs=sample_rate, nperseg=n_fft, noverlap=n_fft - hop_length) +power_spectrum = np.abs(Zxx) ** 2 + + +# Mel filterbank construction +n_mels = 128 +f_min = 0.0 +f_max = sample_rate / 2.0 + +# Mel points evenly spaced on mel scale (Hz-to-mel: 2595 * log10(1 + f/700)) +mel_min = 2595.0 * np.log10(1.0 + f_min / 700.0) +mel_max = 2595.0 * np.log10(1.0 + f_max / 700.0) +mel_points = np.linspace(mel_min, mel_max, n_mels + 2) +hz_points = 700.0 * (10.0 ** (mel_points / 2595.0) - 1.0) + +# Convert Hz points to FFT bin indices +bin_points = np.floor((n_fft + 1) * hz_points / sample_rate).astype(int) + +# Build triangular filterbank +n_freqs = len(frequencies) +filterbank = np.zeros((n_mels, n_freqs)) +for m in range(n_mels): + f_left = bin_points[m] + f_center = bin_points[m + 1] + f_right = bin_points[m + 2] + for k in range(f_left, f_center): + if f_center != f_left: + filterbank[m, k] = (k - f_left) / (f_center - f_left) + for k in range(f_center, f_right): + if f_right != f_center: + filterbank[m, k] = (f_right - k) / (f_right - f_center) + +# Apply mel filterbank to power spectrum +mel_spectrogram = filterbank @ power_spectrum + +# Convert to decibel scale +mel_spectrogram_db = 10.0 * np.log10(mel_spectrogram + 1e-10) + +# Mel frequency axis - center frequencies for each mel band (mel-to-Hz conversion) +mel_center_points = np.linspace(mel_min, mel_max, n_mels + 2)[1:-1] +mel_center_freqs = 700.0 * (10.0 ** (mel_center_points / 2595.0) - 1.0) + +# Magma palette for mel spectrogram +magma = [ + "#000004", + "#010005", + "#010106", + "#010108", + "#020109", + "#02020b", + "#02020d", + "#03030f", + "#030312", + "#040414", + "#050416", + "#060518", + "#07051a", + "#08061c", + "#09071e", + "#0a0720", + "#0b0822", + "#0c0926", + "#0d0a28", + "#0e0b2a", + "#0f0b2c", + "#100c2f", + "#110d31", + "#120d33", + "#130e36", + "#140e38", + "#160f3b", + "#170f3d", + "#181040", + "#1a1042", + "#1b1044", + "#1c1147", + "#1e1149", + "#1f114b", + "#20114e", + "#221150", + "#231152", + "#251155", + "#261157", + "#281159", + "#29115b", + "#2b105d", + "#2d1060", + "#2e1062", + "#301064", + "#311066", + "#331068", + "#34106a", + "#36106b", + "#38106d", + "#39106f", + "#3b1070", + "#3d1072", + "#3e0f73", + "#400f75", + "#420f76", + "#430f77", + "#450e78", + "#470e79", + "#480e7a", + "#4a0e7b", + "#4c0e7c", + "#4d0d7c", + "#4f0d7d", + "#510d7d", + "#520d7e", + "#540d7e", + "#560c7e", + "#570c7f", + "#590c7f", + "#5b0c7f", + "#5c0b7f", + "#5e0b7f", + "#600b7f", + "#610a7f", + "#630a7f", + "#650a7f", + "#66097f", + "#68097e", + "#6a097e", + "#6b097e", + "#6d087d", + "#6f087d", + "#70087d", + "#72077c", + "#74077c", + "#75077b", + "#77067b", + "#79067a", + "#7a067a", + "#7c0679", + "#7e0578", + "#7f0578", + "#810577", + "#830476", + "#840476", + "#860475", + "#880474", + "#890374", + "#8b0373", + "#8d0372", + "#8e0271", + "#900270", + "#920270", + "#93026f", + "#95026e", + "#97016d", + "#98016c", + "#9a016b", + "#9c016b", + "#9d016a", + "#9f0169", + "#a10168", + "#a20167", + "#a40166", + "#a60165", + "#a70164", + "#a90163", + "#ab0162", + "#ac0161", + "#ae0160", + "#b00160", + "#b1015f", + "#b3015e", + "#b4015d", + "#b6015c", + "#b8015b", + "#b9015a", + "#bb0159", + "#bc0158", + "#be0157", + "#c00256", + "#c10255", + "#c30354", + "#c40453", + "#c60552", + "#c70651", + "#c90750", + "#ca084f", + "#cc094e", + "#cd0a4d", + "#cf0b4d", + "#d00d4c", + "#d10e4b", + "#d3104a", + "#d41149", + "#d51349", + "#d71448", + "#d81647", + "#d91847", + "#da1946", + "#dc1b46", + "#dd1d45", + "#de1f45", + "#df2044", + "#e02244", + "#e12444", + "#e22643", + "#e32843", + "#e42a43", + "#e52c43", + "#e62e43", + "#e73043", + "#e83243", + "#e93443", + "#ea3643", + "#eb3843", + "#eb3a43", + "#ec3c43", + "#ed3e44", + "#ee4044", + "#ef4344", + "#ef4545", + "#f04745", + "#f14946", + "#f14b46", + "#f24d47", + "#f34f48", + "#f35148", + "#f45349", + "#f5554a", + "#f5574b", + "#f6594c", + "#f65b4d", + "#f75d4e", + "#f75f4f", + "#f86250", + "#f86451", + "#f96653", + "#f96854", + "#f96a55", + "#fa6c57", + "#fa6e58", + "#fa705a", + "#fb725b", + "#fb745d", + "#fb765e", + "#fb7860", + "#fc7a62", + "#fc7c63", + "#fc7e65", + "#fc8067", + "#fc8269", + "#fc846b", + "#fd866d", + "#fd886f", + "#fd8a71", + "#fd8c73", + "#fd8e75", + "#fd9078", + "#fd927a", + "#fd947c", + "#fd967e", + "#fd9880", + "#fe9a83", + "#fe9c85", + "#fe9e87", + "#fea08a", + "#fea28c", + "#fea48e", + "#fea690", + "#fea893", + "#feaa95", + "#feac98", + "#feae9a", + "#feb09c", + "#feb29f", + "#feb4a1", + "#feb6a4", + "#feb8a6", + "#febaa8", + "#febcab", + "#fdbead", + "#fdc0b0", + "#fdc2b2", + "#fdc4b5", + "#fdc6b7", + "#fdc8ba", + "#fdcabc", + "#fdccbf", + "#fdcec1", + "#fdd0c4", + "#fdd2c6", + "#fdd4c9", + "#fdd5cb", + "#fdd7ce", + "#fdd9d1", + "#fddbd3", + "#fdddd6", + "#fddfd8", + "#fde1db", + "#fde3dd", + "#fde5e0", + "#fee7e3", + "#fee9e5", + "#feebe8", + "#fcedea", + "#fcefed", +] + +# Plot +p = figure( + width=4800, + height=2700, + title="spectrogram-mel · bokeh · pyplots.ai", + x_axis_label="Time (seconds)", + y_axis_label="Frequency (Hz)", + x_range=(times.min(), times.max()), + y_range=(mel_center_freqs[0], mel_center_freqs[-1]), + y_axis_type="log", + tools="", + toolbar_location=None, +) + +# Color mapper +vmin = np.percentile(mel_spectrogram_db, 5) +vmax = mel_spectrogram_db.max() +color_mapper = LinearColorMapper(palette=magma, low=vmin, high=vmax) + +# Render mel spectrogram as image +p.image( + image=[mel_spectrogram_db], + x=times.min(), + y=mel_center_freqs[0], + dw=times.max() - times.min(), + dh=mel_center_freqs[-1] - mel_center_freqs[0], + color_mapper=color_mapper, + level="image", +) + +# Colorbar labeled in dB +color_bar = ColorBar( + color_mapper=color_mapper, + ticker=BasicTicker(desired_num_ticks=8), + label_standoff=20, + border_line_color=None, + location=(0, 0), + title="Power (dB)", + title_text_font_size="32pt", + major_label_text_font_size="24pt", + width=70, + padding=40, + title_standoff=20, +) +p.add_layout(color_bar, "right") + +# Y-axis tick labels at key mel band frequencies +mel_tick_freqs = [50, 100, 200, 500, 1000, 2000, 4000, 8000] +mel_tick_freqs = [f for f in mel_tick_freqs if mel_center_freqs[0] <= f <= mel_center_freqs[-1]] +p.yaxis.ticker = FixedTicker(ticks=mel_tick_freqs) + +# Style for 4800x2700 canvas +p.title.text_font_size = "40pt" +p.xaxis.axis_label_text_font_size = "32pt" +p.yaxis.axis_label_text_font_size = "32pt" +p.xaxis.major_label_text_font_size = "24pt" +p.yaxis.major_label_text_font_size = "24pt" + +# Axis styling +p.xaxis.axis_line_width = 3 +p.yaxis.axis_line_width = 3 +p.xaxis.major_tick_line_width = 3 +p.yaxis.major_tick_line_width = 3 + +# Grid - subtle dashed lines +p.xgrid.grid_line_alpha = 0.2 +p.ygrid.grid_line_alpha = 0.2 +p.xgrid.grid_line_dash = "dashed" +p.ygrid.grid_line_dash = "dashed" + +# Background +p.background_fill_color = None +p.border_fill_color = "white" +p.outline_line_color = "#333333" +p.outline_line_width = 2 +p.min_border_right = 100 + +# Save +export_png(p, filename="plot.png") + +output_file("plot.html", title="spectrogram-mel · bokeh · pyplots.ai") +save(p) From 5b27b9cd64a431c4dba28f70cd11220a0e102529 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 11 Mar 2026 19:42:56 +0000 Subject: [PATCH 2/7] chore(bokeh): add metadata for spectrogram-mel --- plots/spectrogram-mel/metadata/bokeh.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 plots/spectrogram-mel/metadata/bokeh.yaml diff --git a/plots/spectrogram-mel/metadata/bokeh.yaml b/plots/spectrogram-mel/metadata/bokeh.yaml new file mode 100644 index 0000000000..95f92298de --- /dev/null +++ b/plots/spectrogram-mel/metadata/bokeh.yaml @@ -0,0 +1,19 @@ +# Per-library metadata for bokeh implementation of spectrogram-mel +# Auto-generated by impl-generate.yml + +library: bokeh +specification_id: spectrogram-mel +created: '2026-03-11T19:42:55Z' +updated: '2026-03-11T19:42:55Z' +generated_by: claude-opus-4-5-20251101 +workflow_run: 22970857215 +issue: 4672 +python_version: 3.14.3 +library_version: 3.9.0 +preview_url: https://storage.googleapis.com/pyplots-images/plots/spectrogram-mel/bokeh/plot.png +preview_thumb: https://storage.googleapis.com/pyplots-images/plots/spectrogram-mel/bokeh/plot_thumb.png +preview_html: https://storage.googleapis.com/pyplots-images/plots/spectrogram-mel/bokeh/plot.html +quality_score: null +review: + strengths: [] + weaknesses: [] From 709d4a26c033372684c724fa04bbfefc52783b53 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 11 Mar 2026 19:47:30 +0000 Subject: [PATCH 3/7] chore(bokeh): update quality score 80 and review feedback for spectrogram-mel --- .../spectrogram-mel/implementations/bokeh.py | 6 +- plots/spectrogram-mel/metadata/bokeh.yaml | 229 +++++++++++++++++- 2 files changed, 225 insertions(+), 10 deletions(-) diff --git a/plots/spectrogram-mel/implementations/bokeh.py b/plots/spectrogram-mel/implementations/bokeh.py index 4dc9432fdf..5a7f522a80 100644 --- a/plots/spectrogram-mel/implementations/bokeh.py +++ b/plots/spectrogram-mel/implementations/bokeh.py @@ -1,7 +1,7 @@ -"""pyplots.ai +""" pyplots.ai spectrogram-mel: Mel-Spectrogram for Audio Analysis -Library: bokeh | Python 3.13 -Quality: pending | Created: 2026-03-11 +Library: bokeh 3.9.0 | Python 3.14.3 +Quality: 80/100 | Created: 2026-03-11 """ import numpy as np diff --git a/plots/spectrogram-mel/metadata/bokeh.yaml b/plots/spectrogram-mel/metadata/bokeh.yaml index 95f92298de..b59b9b5ea0 100644 --- a/plots/spectrogram-mel/metadata/bokeh.yaml +++ b/plots/spectrogram-mel/metadata/bokeh.yaml @@ -1,10 +1,7 @@ -# Per-library metadata for bokeh implementation of spectrogram-mel -# Auto-generated by impl-generate.yml - library: bokeh specification_id: spectrogram-mel created: '2026-03-11T19:42:55Z' -updated: '2026-03-11T19:42:55Z' +updated: '2026-03-11T19:47:30Z' generated_by: claude-opus-4-5-20251101 workflow_run: 22970857215 issue: 4672 @@ -13,7 +10,225 @@ library_version: 3.9.0 preview_url: https://storage.googleapis.com/pyplots-images/plots/spectrogram-mel/bokeh/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/spectrogram-mel/bokeh/plot_thumb.png preview_html: https://storage.googleapis.com/pyplots-images/plots/spectrogram-mel/bokeh/plot.html -quality_score: null +quality_score: 80 review: - strengths: [] - weaknesses: [] + strengths: + - Excellent audio synthesis with realistic musical notes, harmonics, and ADSR envelopes + - Correct mel filterbank construction from scratch without librosa dependency + - All font sizes explicitly set and appropriate for 4800x2700 canvas + - Good colormap choice (magma) for spectrogram visualization + - Proper dB conversion with floor value to avoid log(0) + weaknesses: + - Hardcoded 256-hex-value magma palette instead of using Bokeh built-in Magma256 + from bokeh.palettes + - p.image() linear mapping combined with y_axis_type=log creates visual distortion + of mel bands + - No use of Bokeh interactive features like HoverTool for time/frequency/power readout + - Design could be more polished with better title positioning and colorbar integration + image_description: The plot displays a mel-spectrogram rendered with the magma colormap + (dark purple background transitioning through pink to near-white for high-intensity + regions). The x-axis shows "Time (seconds)" from 0 to 4, and the y-axis shows + "Frequency (Hz)" on a logarithmic scale with ticks at 50, 100, 200, 500, 1000, + 2000, 4000, and 8000 Hz. A colorbar on the right is labeled "Power (dB)" ranging + from approximately -70 to -20 dB. Bright horizontal bands are clearly visible + at various frequency levels corresponding to the synthesized melody notes (C4, + E4, G4, C5, A4, F4, D4) and their harmonics, with fundamental frequencies showing + the brightest intensity. The note onsets and offsets are visible with ADSR envelope + shaping. Background noise appears as a low-level dark purple texture. The lower + mel bins appear heavily stretched due to the combination of linear image mapping + with a log y-axis, creating visually uneven band heights. The title reads "spectrogram-mel + · bokeh · pyplots.ai". + criteria_checklist: + visual_quality: + score: 27 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 8 + max: 8 + passed: true + comment: 'All font sizes explicitly set: title=40pt, axis labels=32pt, tick + labels=24pt, colorbar title=32pt, colorbar labels=24pt' + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No overlapping text elements anywhere + - id: VQ-03 + name: Element Visibility + score: 4 + max: 6 + passed: true + comment: Spectrogram visible but mel bands appear blocky due to log axis stretching + lower bins disproportionately + - id: VQ-04 + name: Color Accessibility + score: 4 + max: 4 + passed: true + comment: Magma colormap is perceptually uniform and colorblind-safe + - id: VQ-05 + name: Layout & Canvas + score: 3 + max: 4 + passed: true + comment: Good layout with colorbar on right, minor bottom gap and right margin + could be tighter + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Time (seconds) and Frequency (Hz) both descriptive with units + design_excellence: + score: 11 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: true + comment: Magma colormap is strong choice, log y-axis with meaningful tick + positions, toolbar removed. Above default but not publication-quality + - id: DE-02 + name: Visual Refinement + score: 3 + max: 6 + passed: true + comment: Subtle dashed grid with alpha=0.2, removed toolbar, styled outline, + background fill None + - id: DE-03 + name: Data Storytelling + score: 3 + max: 6 + passed: true + comment: Synthesized melody creates visual narrative with note progressions + and harmonic structure visible + spec_compliance: + score: 14 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Correct mel-spectrogram visualization + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: 'All required features present: dB scale, sequential colormap, time/frequency + axes, colorbar, correct parameters, synthesized audio' + - id: SC-03 + name: Data Mapping + score: 2 + max: 3 + passed: true + comment: X=time Y=frequency correct but p.image() linear mapping with log + y-axis causes visual distortion of mel bands + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title format correct, no legend needed for this plot type + data_quality: + score: 14 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 5 + max: 6 + passed: true + comment: Rich audio with 7 notes, harmonics, ADSR envelopes, background noise. + Could show wider frequency variety + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Real musical notes (C4-C5) with correct frequencies, realistic sample + rate and duration + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: 'All values sensible: 22050 Hz sample rate, musical frequencies in + hearing range, 128 mel bands' + code_quality: + score: 9 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: Clean imports-data-plot-save structure, no functions or classes + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: np.random.seed(42) set at beginning + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: 'All imports used: numpy, bokeh models, scipy.signal' + - id: CQ-04 + name: Code Elegance + score: 1 + max: 2 + passed: false + comment: 256-line hardcoded magma palette is unnecessarily verbose when Bokeh + provides Magma256 + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves as plot.png and plot.html, current API usage + library_mastery: + score: 5 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 3 + max: 5 + passed: true + comment: Correct figure/image/ColorBar usage but manually defines palette + instead of using bokeh.palettes + - id: LM-02 + name: Distinctive Features + score: 2 + max: 5 + passed: false + comment: HTML export is Bokeh-specific but missing HoverTool and other interactive + features that make Bokeh distinctive + verdict: REJECTED +impl_tags: + dependencies: + - scipy + techniques: + - colorbar + - manual-ticks + - html-export + patterns: + - data-generation + - matrix-construction + - iteration-over-groups + dataprep: + - binning + styling: + - custom-colormap + - grid-styling From 6be6976d3bb73f1684841b232015fb3469f07396 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 11 Mar 2026 19:52:52 +0000 Subject: [PATCH 4/7] fix(bokeh): address review feedback for spectrogram-mel Attempt 1/3 - fixes based on AI review --- .../spectrogram-mel/implementations/bokeh.py | 385 +++++------------- 1 file changed, 93 insertions(+), 292 deletions(-) diff --git a/plots/spectrogram-mel/implementations/bokeh.py b/plots/spectrogram-mel/implementations/bokeh.py index 5a7f522a80..0f547d4a66 100644 --- a/plots/spectrogram-mel/implementations/bokeh.py +++ b/plots/spectrogram-mel/implementations/bokeh.py @@ -1,4 +1,4 @@ -""" pyplots.ai +"""pyplots.ai spectrogram-mel: Mel-Spectrogram for Audio Analysis Library: bokeh 3.9.0 | Python 3.14.3 Quality: 80/100 | Created: 2026-03-11 @@ -6,7 +6,8 @@ import numpy as np from bokeh.io import export_png, output_file, save -from bokeh.models import BasicTicker, ColorBar, FixedTicker, LinearColorMapper +from bokeh.models import BasicTicker, ColorBar, ColumnDataSource, FixedTicker, HoverTool, LinearColorMapper +from bokeh.palettes import Magma256 from bokeh.plotting import figure from scipy import signal @@ -99,302 +100,92 @@ # Convert to decibel scale mel_spectrogram_db = 10.0 * np.log10(mel_spectrogram + 1e-10) -# Mel frequency axis - center frequencies for each mel band (mel-to-Hz conversion) -mel_center_points = np.linspace(mel_min, mel_max, n_mels + 2)[1:-1] -mel_center_freqs = 700.0 * (10.0 ** (mel_center_points / 2595.0) - 1.0) - -# Magma palette for mel spectrogram -magma = [ - "#000004", - "#010005", - "#010106", - "#010108", - "#020109", - "#02020b", - "#02020d", - "#03030f", - "#030312", - "#040414", - "#050416", - "#060518", - "#07051a", - "#08061c", - "#09071e", - "#0a0720", - "#0b0822", - "#0c0926", - "#0d0a28", - "#0e0b2a", - "#0f0b2c", - "#100c2f", - "#110d31", - "#120d33", - "#130e36", - "#140e38", - "#160f3b", - "#170f3d", - "#181040", - "#1a1042", - "#1b1044", - "#1c1147", - "#1e1149", - "#1f114b", - "#20114e", - "#221150", - "#231152", - "#251155", - "#261157", - "#281159", - "#29115b", - "#2b105d", - "#2d1060", - "#2e1062", - "#301064", - "#311066", - "#331068", - "#34106a", - "#36106b", - "#38106d", - "#39106f", - "#3b1070", - "#3d1072", - "#3e0f73", - "#400f75", - "#420f76", - "#430f77", - "#450e78", - "#470e79", - "#480e7a", - "#4a0e7b", - "#4c0e7c", - "#4d0d7c", - "#4f0d7d", - "#510d7d", - "#520d7e", - "#540d7e", - "#560c7e", - "#570c7f", - "#590c7f", - "#5b0c7f", - "#5c0b7f", - "#5e0b7f", - "#600b7f", - "#610a7f", - "#630a7f", - "#650a7f", - "#66097f", - "#68097e", - "#6a097e", - "#6b097e", - "#6d087d", - "#6f087d", - "#70087d", - "#72077c", - "#74077c", - "#75077b", - "#77067b", - "#79067a", - "#7a067a", - "#7c0679", - "#7e0578", - "#7f0578", - "#810577", - "#830476", - "#840476", - "#860475", - "#880474", - "#890374", - "#8b0373", - "#8d0372", - "#8e0271", - "#900270", - "#920270", - "#93026f", - "#95026e", - "#97016d", - "#98016c", - "#9a016b", - "#9c016b", - "#9d016a", - "#9f0169", - "#a10168", - "#a20167", - "#a40166", - "#a60165", - "#a70164", - "#a90163", - "#ab0162", - "#ac0161", - "#ae0160", - "#b00160", - "#b1015f", - "#b3015e", - "#b4015d", - "#b6015c", - "#b8015b", - "#b9015a", - "#bb0159", - "#bc0158", - "#be0157", - "#c00256", - "#c10255", - "#c30354", - "#c40453", - "#c60552", - "#c70651", - "#c90750", - "#ca084f", - "#cc094e", - "#cd0a4d", - "#cf0b4d", - "#d00d4c", - "#d10e4b", - "#d3104a", - "#d41149", - "#d51349", - "#d71448", - "#d81647", - "#d91847", - "#da1946", - "#dc1b46", - "#dd1d45", - "#de1f45", - "#df2044", - "#e02244", - "#e12444", - "#e22643", - "#e32843", - "#e42a43", - "#e52c43", - "#e62e43", - "#e73043", - "#e83243", - "#e93443", - "#ea3643", - "#eb3843", - "#eb3a43", - "#ec3c43", - "#ed3e44", - "#ee4044", - "#ef4344", - "#ef4545", - "#f04745", - "#f14946", - "#f14b46", - "#f24d47", - "#f34f48", - "#f35148", - "#f45349", - "#f5554a", - "#f5574b", - "#f6594c", - "#f65b4d", - "#f75d4e", - "#f75f4f", - "#f86250", - "#f86451", - "#f96653", - "#f96854", - "#f96a55", - "#fa6c57", - "#fa6e58", - "#fa705a", - "#fb725b", - "#fb745d", - "#fb765e", - "#fb7860", - "#fc7a62", - "#fc7c63", - "#fc7e65", - "#fc8067", - "#fc8269", - "#fc846b", - "#fd866d", - "#fd886f", - "#fd8a71", - "#fd8c73", - "#fd8e75", - "#fd9078", - "#fd927a", - "#fd947c", - "#fd967e", - "#fd9880", - "#fe9a83", - "#fe9c85", - "#fe9e87", - "#fea08a", - "#fea28c", - "#fea48e", - "#fea690", - "#fea893", - "#feaa95", - "#feac98", - "#feae9a", - "#feb09c", - "#feb29f", - "#feb4a1", - "#feb6a4", - "#feb8a6", - "#febaa8", - "#febcab", - "#fdbead", - "#fdc0b0", - "#fdc2b2", - "#fdc4b5", - "#fdc6b7", - "#fdc8ba", - "#fdcabc", - "#fdccbf", - "#fdcec1", - "#fdd0c4", - "#fdd2c6", - "#fdd4c9", - "#fdd5cb", - "#fdd7ce", - "#fdd9d1", - "#fddbd3", - "#fdddd6", - "#fddfd8", - "#fde1db", - "#fde3dd", - "#fde5e0", - "#fee7e3", - "#fee9e5", - "#feebe8", - "#fcedea", - "#fcefed", -] +# Mel band edge frequencies (for positioning rectangles correctly on log scale) +mel_edge_freqs = hz_points # n_mels + 2 edges +# Clamp to positive values for log scale +mel_edge_freqs = np.maximum(mel_edge_freqs, 1.0) + +# Build quad data for each mel band x time frame +n_times = len(times) +time_step = times[1] - times[0] if n_times > 1 else hop_length / sample_rate + +quad_left = [] +quad_right = [] +quad_bottom = [] +quad_top = [] +quad_power = [] +quad_time_label = [] +quad_freq_label = [] + +for ti in range(n_times): + for mi in range(n_mels): + quad_left.append(times[ti] - time_step / 2) + quad_right.append(times[ti] + time_step / 2) + quad_bottom.append(mel_edge_freqs[mi + 1]) + quad_top.append(mel_edge_freqs[mi + 2]) + quad_power.append(mel_spectrogram_db[mi, ti]) + quad_time_label.append(round(times[ti], 3)) + quad_freq_label.append(round((mel_edge_freqs[mi + 1] + mel_edge_freqs[mi + 2]) / 2, 1)) + +quad_power_arr = np.array(quad_power) +vmin = float(np.percentile(mel_spectrogram_db, 5)) +vmax = float(mel_spectrogram_db.max()) + +# Map power to palette indices for fill_color +normalized = (quad_power_arr - vmin) / (vmax - vmin) +normalized = np.clip(normalized, 0, 1) +color_indices = (normalized * 255).astype(int) +colors = [Magma256[i] for i in color_indices] + +source = ColumnDataSource( + data={ + "left": quad_left, + "right": quad_right, + "bottom": quad_bottom, + "top": quad_top, + "power": quad_power, + "color": colors, + "time_s": quad_time_label, + "freq_hz": quad_freq_label, + } +) # Plot p = figure( width=4800, height=2700, - title="spectrogram-mel · bokeh · pyplots.ai", + title="spectrogram-mel \u00b7 bokeh \u00b7 pyplots.ai", x_axis_label="Time (seconds)", y_axis_label="Frequency (Hz)", - x_range=(times.min(), times.max()), - y_range=(mel_center_freqs[0], mel_center_freqs[-1]), + x_range=(times.min() - time_step / 2, times.max() + time_step / 2), + y_range=(mel_edge_freqs[1], mel_edge_freqs[-1]), y_axis_type="log", tools="", toolbar_location=None, ) -# Color mapper -vmin = np.percentile(mel_spectrogram_db, 5) -vmax = mel_spectrogram_db.max() -color_mapper = LinearColorMapper(palette=magma, low=vmin, high=vmax) - -# Render mel spectrogram as image -p.image( - image=[mel_spectrogram_db], - x=times.min(), - y=mel_center_freqs[0], - dw=times.max() - times.min(), - dh=mel_center_freqs[-1] - mel_center_freqs[0], - color_mapper=color_mapper, +# Render mel bands as quads for correct log-scale positioning +p.quad( + left="left", + right="right", + bottom="bottom", + top="top", + fill_color="color", + line_color=None, + source=source, level="image", ) +# HoverTool for interactive readout +hover = HoverTool( + tooltips=[("Time", "@time_s{0.000} s"), ("Frequency", "@freq_hz{0.0} Hz"), ("Power", "@power{0.0} dB")], + point_policy="follow_mouse", +) +p.add_tools(hover) + +# Color mapper for the colorbar +color_mapper = LinearColorMapper(palette=Magma256, low=vmin, high=vmax) + # Colorbar labeled in dB color_bar = ColorBar( color_mapper=color_mapper, @@ -413,37 +204,47 @@ # Y-axis tick labels at key mel band frequencies mel_tick_freqs = [50, 100, 200, 500, 1000, 2000, 4000, 8000] -mel_tick_freqs = [f for f in mel_tick_freqs if mel_center_freqs[0] <= f <= mel_center_freqs[-1]] +mel_tick_freqs = [f for f in mel_tick_freqs if mel_edge_freqs[1] <= f <= mel_edge_freqs[-1]] p.yaxis.ticker = FixedTicker(ticks=mel_tick_freqs) # Style for 4800x2700 canvas p.title.text_font_size = "40pt" +p.title.text_font_style = "bold" +p.title.text_color = "#222222" p.xaxis.axis_label_text_font_size = "32pt" p.yaxis.axis_label_text_font_size = "32pt" p.xaxis.major_label_text_font_size = "24pt" p.yaxis.major_label_text_font_size = "24pt" +p.xaxis.axis_label_text_font_style = "normal" +p.yaxis.axis_label_text_font_style = "normal" # Axis styling p.xaxis.axis_line_width = 3 p.yaxis.axis_line_width = 3 p.xaxis.major_tick_line_width = 3 p.yaxis.major_tick_line_width = 3 +p.xaxis.minor_tick_line_color = None +p.yaxis.minor_tick_line_color = None -# Grid - subtle dashed lines -p.xgrid.grid_line_alpha = 0.2 -p.ygrid.grid_line_alpha = 0.2 -p.xgrid.grid_line_dash = "dashed" -p.ygrid.grid_line_dash = "dashed" +# Grid - subtle styling +p.xgrid.grid_line_alpha = 0.15 +p.ygrid.grid_line_alpha = 0.15 +p.xgrid.grid_line_dash = [6, 4] +p.ygrid.grid_line_dash = [6, 4] +p.xgrid.grid_line_color = "#aaaaaa" +p.ygrid.grid_line_color = "#aaaaaa" # Background -p.background_fill_color = None +p.background_fill_color = "#000004" p.border_fill_color = "white" p.outline_line_color = "#333333" p.outline_line_width = 2 -p.min_border_right = 100 +p.min_border_right = 140 +p.min_border_left = 120 +p.min_border_bottom = 100 # Save export_png(p, filename="plot.png") -output_file("plot.html", title="spectrogram-mel · bokeh · pyplots.ai") +output_file("plot.html", title="spectrogram-mel \u00b7 bokeh \u00b7 pyplots.ai") save(p) From 99fec2cdc5e4d9869dba901fd4323bb7d7b2c9e1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 11 Mar 2026 19:58:05 +0000 Subject: [PATCH 5/7] chore(bokeh): update quality score 85 and review feedback for spectrogram-mel --- .../spectrogram-mel/implementations/bokeh.py | 4 +- plots/spectrogram-mel/metadata/bokeh.yaml | 151 ++++++++++-------- 2 files changed, 85 insertions(+), 70 deletions(-) diff --git a/plots/spectrogram-mel/implementations/bokeh.py b/plots/spectrogram-mel/implementations/bokeh.py index 0f547d4a66..a66c5ed7e4 100644 --- a/plots/spectrogram-mel/implementations/bokeh.py +++ b/plots/spectrogram-mel/implementations/bokeh.py @@ -1,7 +1,7 @@ -"""pyplots.ai +""" pyplots.ai spectrogram-mel: Mel-Spectrogram for Audio Analysis Library: bokeh 3.9.0 | Python 3.14.3 -Quality: 80/100 | Created: 2026-03-11 +Quality: 85/100 | Created: 2026-03-11 """ import numpy as np diff --git a/plots/spectrogram-mel/metadata/bokeh.yaml b/plots/spectrogram-mel/metadata/bokeh.yaml index b59b9b5ea0..49a0199836 100644 --- a/plots/spectrogram-mel/metadata/bokeh.yaml +++ b/plots/spectrogram-mel/metadata/bokeh.yaml @@ -1,7 +1,7 @@ library: bokeh specification_id: spectrogram-mel created: '2026-03-11T19:42:55Z' -updated: '2026-03-11T19:47:30Z' +updated: '2026-03-11T19:58:05Z' generated_by: claude-opus-4-5-20251101 workflow_run: 22970857215 issue: 4672 @@ -10,37 +10,41 @@ library_version: 3.9.0 preview_url: https://storage.googleapis.com/pyplots-images/plots/spectrogram-mel/bokeh/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/spectrogram-mel/bokeh/plot_thumb.png preview_html: https://storage.googleapis.com/pyplots-images/plots/spectrogram-mel/bokeh/plot.html -quality_score: 80 +quality_score: 85 review: strengths: - - Excellent audio synthesis with realistic musical notes, harmonics, and ADSR envelopes - - Correct mel filterbank construction from scratch without librosa dependency - - All font sizes explicitly set and appropriate for 4800x2700 canvas - - Good colormap choice (magma) for spectrogram visualization - - Proper dB conversion with floor value to avoid log(0) + - Excellent mel filterbank construction from scratch using scipy.signal STFT, avoiding + librosa dependency + - Quad rendering correctly positions mel bands on log y-axis, fixing the visual + distortion from attempt 1 + - HoverTool provides genuinely interactive readout of time, frequency, and power + values + - Dark background matching Magma colormap floor creates cohesive visual design + - Rich audio synthesis with harmonics and ADSR envelopes produces a visually engaging + spectrogram weaknesses: - - Hardcoded 256-hex-value magma palette instead of using Bokeh built-in Magma256 - from bokeh.palettes - - p.image() linear mapping combined with y_axis_type=log creates visual distortion - of mel bands - - No use of Bokeh interactive features like HoverTool for time/frequency/power readout - - Design could be more polished with better title positioning and colorbar integration - image_description: The plot displays a mel-spectrogram rendered with the magma colormap - (dark purple background transitioning through pink to near-white for high-intensity - regions). The x-axis shows "Time (seconds)" from 0 to 4, and the y-axis shows - "Frequency (Hz)" on a logarithmic scale with ticks at 50, 100, 200, 500, 1000, - 2000, 4000, and 8000 Hz. A colorbar on the right is labeled "Power (dB)" ranging - from approximately -70 to -20 dB. Bright horizontal bands are clearly visible - at various frequency levels corresponding to the synthesized melody notes (C4, - E4, G4, C5, A4, F4, D4) and their harmonics, with fundamental frequencies showing - the brightest intensity. The note onsets and offsets are visible with ADSR envelope - shaping. Background noise appears as a low-level dark purple texture. The lower - mel bins appear heavily stretched due to the combination of linear image mapping - with a log y-axis, creating visually uneven band heights. The title reads "spectrogram-mel - · bokeh · pyplots.ai". + - 'Design Excellence needs improvement: typography could be more refined, no visual + emphasis or annotation guiding viewer to key insight' + - Nested loop for building quad data arrays is verbose and could be vectorized with + np.repeat/np.tile + - Colorbar right margin slightly tight, could use more breathing room + image_description: The plot displays a mel-spectrogram rendered with the Magma colormap + on a dark background (#000004, matching the colormap's lowest value). The x-axis + shows "Time (seconds)" from 0 to 4, and the y-axis shows "Frequency (Hz)" on a + logarithmic scale with fixed ticks at 50, 100, 200, 500, 1000, 2000, 4000, and + 8000 Hz. A colorbar on the right is labeled "Power (dB)" ranging from approximately + -70 to -20 dB. Bright horizontal bands (yellow-white through pink) are clearly + visible at various frequency levels corresponding to the synthesized melody notes + (C4, E4, G4, C5, A4, F4, D4) and their harmonics, with fundamentals showing the + brightest intensity and harmonics progressively dimmer. Note onsets and offsets + show clean ADSR envelope shaping. The mel bands are rendered as individual quads + correctly positioned on the log y-axis, eliminating the distortion seen in attempt + 1. Background noise appears as low-level dark texture primarily in the lower frequencies. + The title reads "spectrogram-mel · bokeh · pyplots.ai" in bold at the top left. + Subtle dashed grid lines are visible at low opacity. criteria_checklist: visual_quality: - score: 27 + score: 28 max: 30 items: - id: VQ-01 @@ -49,41 +53,45 @@ review: max: 8 passed: true comment: 'All font sizes explicitly set: title=40pt, axis labels=32pt, tick - labels=24pt, colorbar title=32pt, colorbar labels=24pt' + labels=24pt, colorbar title=32pt, colorbar labels=24pt. All text clearly + readable.' - id: VQ-02 name: No Overlap score: 6 max: 6 passed: true - comment: No overlapping text elements anywhere + comment: No overlapping text elements anywhere in the plot. - id: VQ-03 name: Element Visibility - score: 4 + score: 5 max: 6 passed: true - comment: Spectrogram visible but mel bands appear blocky due to log axis stretching - lower bins disproportionately + comment: Mel bands clearly visible with good contrast. Quad rendering correctly + positions bands on log scale. Lower frequency bins slightly coarse due to + narrow mel bandwidth. - id: VQ-04 name: Color Accessibility score: 4 max: 4 passed: true - comment: Magma colormap is perceptually uniform and colorblind-safe + comment: Magma is perceptually uniform and colorblind-safe with excellent + contrast. - id: VQ-05 name: Layout & Canvas score: 3 max: 4 passed: true - comment: Good layout with colorbar on right, minor bottom gap and right margin - could be tighter + comment: Good canvas utilization with colorbar on right. Colorbar title text + slightly tight against right edge. - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Time (seconds) and Frequency (Hz) both descriptive with units + comment: Time (seconds) and Frequency (Hz) descriptive with units. Colorbar + labeled Power (dB). design_excellence: - score: 11 + score: 12 max: 20 items: - id: DE-01 @@ -91,24 +99,26 @@ review: score: 5 max: 8 passed: true - comment: Magma colormap is strong choice, log y-axis with meaningful tick - positions, toolbar removed. Above default but not publication-quality + comment: Magma colormap is strong choice. Dark background matching colormap + floor is professional. Toolbar removed, bold title. Above default but not + publication-grade. - id: DE-02 name: Visual Refinement - score: 3 + score: 4 max: 6 passed: true - comment: Subtle dashed grid with alpha=0.2, removed toolbar, styled outline, - background fill None + comment: Subtle dashed grid (alpha=0.15), minor ticks hidden, custom outline, + dark background fill, generous padding. Clear refinement effort. - id: DE-03 name: Data Storytelling score: 3 max: 6 passed: true comment: Synthesized melody creates visual narrative with note progressions - and harmonic structure visible + and harmonic structure. No explicit visual emphasis guiding viewer to key + insight. spec_compliance: - score: 14 + score: 15 max: 15 items: - id: SC-01 @@ -116,27 +126,27 @@ review: score: 5 max: 5 passed: true - comment: Correct mel-spectrogram visualization + comment: Correct mel-spectrogram visualization with mel-scaled frequency axis. - id: SC-02 name: Required Features score: 4 max: 4 passed: true comment: 'All required features present: dB scale, sequential colormap, time/frequency - axes, colorbar, correct parameters, synthesized audio' + axes, colorbar in dB, correct parameters, synthesized audio.' - id: SC-03 name: Data Mapping - score: 2 + score: 3 max: 3 passed: true - comment: X=time Y=frequency correct but p.image() linear mapping with log - y-axis causes visual distortion of mel bands + comment: X=time Y=frequency correctly mapped. Quads properly positioned on + mel scale with log y-axis. - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title format correct, no legend needed for this plot type + comment: Title format correct. No legend needed for this plot type. data_quality: score: 14 max: 15 @@ -147,21 +157,21 @@ review: max: 6 passed: true comment: Rich audio with 7 notes, harmonics, ADSR envelopes, background noise. - Could show wider frequency variety + Could show wider frequency variety. - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Real musical notes (C4-C5) with correct frequencies, realistic sample - rate and duration + comment: Real musical notes with correct frequencies, standard sample rate + and duration. - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true comment: 'All values sensible: 22050 Hz sample rate, musical frequencies in - hearing range, 128 mel bands' + hearing range, 128 mel bands.' code_quality: score: 9 max: 10 @@ -171,50 +181,52 @@ review: score: 3 max: 3 passed: true - comment: Clean imports-data-plot-save structure, no functions or classes + comment: Clean imports-data-STFT-mel-plot-save structure, no functions or + classes. - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: np.random.seed(42) set at beginning + comment: np.random.seed(42) set at beginning. - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: 'All imports used: numpy, bokeh models, scipy.signal' + comment: 'All imports used: numpy, bokeh models/palettes/plotting/io, scipy.signal.' - id: CQ-04 name: Code Elegance score: 1 max: 2 passed: false - comment: 256-line hardcoded magma palette is unnecessarily verbose when Bokeh - provides Magma256 + comment: Nested loop for quad data construction is verbose; could be vectorized + with np.repeat/np.tile. - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Saves as plot.png and plot.html, current API usage + comment: Saves as plot.png and plot.html, current API usage. library_mastery: - score: 5 + score: 7 max: 10 items: - id: LM-01 name: Idiomatic Usage - score: 3 + score: 4 max: 5 passed: true - comment: Correct figure/image/ColorBar usage but manually defines palette - instead of using bokeh.palettes + comment: Good use of ColumnDataSource, quad renderer, ColorBar, LinearColorMapper, + FixedTicker, Magma256 from bokeh.palettes, export_png/output_file/save. - id: LM-02 name: Distinctive Features - score: 2 + score: 3 max: 5 - passed: false - comment: HTML export is Bokeh-specific but missing HoverTool and other interactive - features that make Bokeh distinctive + passed: true + comment: HoverTool with time/frequency/power readout is genuinely distinctive + Bokeh feature. HTML export alongside PNG. ColumnDataSource enables hover + interactivity. verdict: REJECTED impl_tags: dependencies: @@ -222,13 +234,16 @@ impl_tags: techniques: - colorbar - manual-ticks + - hover-tooltips - html-export patterns: - data-generation - matrix-construction - iteration-over-groups + - columndatasource dataprep: - binning styling: - custom-colormap - grid-styling + - dark-theme From 73b2c8c03cb98101d40bd4fd9cd0646acdbfebb4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 11 Mar 2026 20:04:01 +0000 Subject: [PATCH 6/7] fix(bokeh): address review feedback for spectrogram-mel Attempt 2/3 - fixes based on AI review --- .../spectrogram-mel/implementations/bokeh.py | 194 +++++++++++------- 1 file changed, 115 insertions(+), 79 deletions(-) diff --git a/plots/spectrogram-mel/implementations/bokeh.py b/plots/spectrogram-mel/implementations/bokeh.py index a66c5ed7e4..919ac6076f 100644 --- a/plots/spectrogram-mel/implementations/bokeh.py +++ b/plots/spectrogram-mel/implementations/bokeh.py @@ -1,12 +1,21 @@ -""" pyplots.ai +"""pyplots.ai spectrogram-mel: Mel-Spectrogram for Audio Analysis Library: bokeh 3.9.0 | Python 3.14.3 -Quality: 85/100 | Created: 2026-03-11 """ import numpy as np from bokeh.io import export_png, output_file, save -from bokeh.models import BasicTicker, ColorBar, ColumnDataSource, FixedTicker, HoverTool, LinearColorMapper +from bokeh.models import ( + BasicTicker, + BoxAnnotation, + ColorBar, + ColumnDataSource, + FixedTicker, + HoverTool, + Label, + LinearColorMapper, + Span, +) from bokeh.palettes import Magma256 from bokeh.plotting import figure from scipy import signal @@ -37,7 +46,6 @@ mask = (t >= start) & (t < end) envelope = np.zeros(n_samples) note_len = np.sum(mask) - # ADSR-like envelope attack = int(0.05 * sample_rate) release = int(0.1 * sample_rate) if note_len > attack + release: @@ -45,7 +53,6 @@ env[:attack] = np.linspace(0, 1, attack) env[-release:] = np.linspace(1, 0, release) envelope[mask] = env - # Fundamental + harmonics audio_signal += envelope * ( 0.6 * np.sin(2 * np.pi * freq * t) + 0.25 * np.sin(2 * np.pi * 2 * freq * t) @@ -53,10 +60,7 @@ + 0.05 * np.sin(2 * np.pi * 4 * freq * t) ) -# Add subtle background noise audio_signal += 0.02 * np.random.randn(n_samples) - -# Normalize audio_signal = audio_signal / np.max(np.abs(audio_signal)) # Compute STFT @@ -65,88 +69,63 @@ frequencies, times, Zxx = signal.stft(audio_signal, fs=sample_rate, nperseg=n_fft, noverlap=n_fft - hop_length) power_spectrum = np.abs(Zxx) ** 2 - # Mel filterbank construction n_mels = 128 f_min = 0.0 f_max = sample_rate / 2.0 -# Mel points evenly spaced on mel scale (Hz-to-mel: 2595 * log10(1 + f/700)) mel_min = 2595.0 * np.log10(1.0 + f_min / 700.0) mel_max = 2595.0 * np.log10(1.0 + f_max / 700.0) mel_points = np.linspace(mel_min, mel_max, n_mels + 2) hz_points = 700.0 * (10.0 ** (mel_points / 2595.0) - 1.0) -# Convert Hz points to FFT bin indices bin_points = np.floor((n_fft + 1) * hz_points / sample_rate).astype(int) -# Build triangular filterbank +# Build triangular filterbank (vectorized inner loop) n_freqs = len(frequencies) filterbank = np.zeros((n_mels, n_freqs)) for m in range(n_mels): - f_left = bin_points[m] - f_center = bin_points[m + 1] - f_right = bin_points[m + 2] - for k in range(f_left, f_center): - if f_center != f_left: - filterbank[m, k] = (k - f_left) / (f_center - f_left) - for k in range(f_center, f_right): - if f_right != f_center: - filterbank[m, k] = (f_right - k) / (f_right - f_center) - -# Apply mel filterbank to power spectrum -mel_spectrogram = filterbank @ power_spectrum + f_left, f_center, f_right = bin_points[m], bin_points[m + 1], bin_points[m + 2] + if f_center > f_left: + rising = np.arange(f_left, f_center) + filterbank[m, rising] = (rising - f_left) / (f_center - f_left) + if f_right > f_center: + falling = np.arange(f_center, f_right) + filterbank[m, falling] = (f_right - falling) / (f_right - f_center) -# Convert to decibel scale +mel_spectrogram = filterbank @ power_spectrum mel_spectrogram_db = 10.0 * np.log10(mel_spectrogram + 1e-10) -# Mel band edge frequencies (for positioning rectangles correctly on log scale) -mel_edge_freqs = hz_points # n_mels + 2 edges -# Clamp to positive values for log scale -mel_edge_freqs = np.maximum(mel_edge_freqs, 1.0) +# Mel band edge frequencies for quad positioning on log scale +mel_edge_freqs = np.maximum(hz_points, 1.0) -# Build quad data for each mel band x time frame +# Build quad data vectorized with np.repeat/np.tile n_times = len(times) time_step = times[1] - times[0] if n_times > 1 else hop_length / sample_rate -quad_left = [] -quad_right = [] -quad_bottom = [] -quad_top = [] -quad_power = [] -quad_time_label = [] -quad_freq_label = [] - -for ti in range(n_times): - for mi in range(n_mels): - quad_left.append(times[ti] - time_step / 2) - quad_right.append(times[ti] + time_step / 2) - quad_bottom.append(mel_edge_freqs[mi + 1]) - quad_top.append(mel_edge_freqs[mi + 2]) - quad_power.append(mel_spectrogram_db[mi, ti]) - quad_time_label.append(round(times[ti], 3)) - quad_freq_label.append(round((mel_edge_freqs[mi + 1] + mel_edge_freqs[mi + 2]) / 2, 1)) - -quad_power_arr = np.array(quad_power) +time_grid = np.repeat(times, n_mels) +bottom_grid = np.tile(mel_edge_freqs[1 : n_mels + 1], n_times) +top_grid = np.tile(mel_edge_freqs[2 : n_mels + 2], n_times) +power_grid = mel_spectrogram_db.T.ravel() + vmin = float(np.percentile(mel_spectrogram_db, 5)) vmax = float(mel_spectrogram_db.max()) -# Map power to palette indices for fill_color -normalized = (quad_power_arr - vmin) / (vmax - vmin) -normalized = np.clip(normalized, 0, 1) +# Map power to palette colors +normalized = np.clip((power_grid - vmin) / (vmax - vmin), 0, 1) color_indices = (normalized * 255).astype(int) colors = [Magma256[i] for i in color_indices] source = ColumnDataSource( data={ - "left": quad_left, - "right": quad_right, - "bottom": quad_bottom, - "top": quad_top, - "power": quad_power, + "left": time_grid - time_step / 2, + "right": time_grid + time_step / 2, + "bottom": bottom_grid, + "top": top_grid, + "power": power_grid, "color": colors, - "time_s": quad_time_label, - "freq_hz": quad_freq_label, + "time_s": np.round(time_grid, 3), + "freq_hz": np.round((bottom_grid + top_grid) / 2, 1), } ) @@ -164,7 +143,7 @@ toolbar_location=None, ) -# Render mel bands as quads for correct log-scale positioning +# Render mel bands as quads p.quad( left="left", right="right", @@ -176,6 +155,53 @@ level="image", ) +# Visual storytelling: annotate the C-major arpeggio rising pattern +arpeggio_box = BoxAnnotation( + left=0.0, right=2.5, fill_alpha=0, line_color="#ffffff", line_alpha=0.45, line_width=3, line_dash="dashed" +) +p.add_layout(arpeggio_box) + +arpeggio_label = Label( + x=0.05, + y=mel_edge_freqs[-1] * 0.85, + text="C Major Arpeggio (C4 \u2192 E4 \u2192 G4 \u2192 C5)", + text_font_size="22pt", + text_color="#ffffff", + text_alpha=0.85, + text_font_style="italic", +) +p.add_layout(arpeggio_label) + +# Mark octave fundamentals (C4, C5) with horizontal frequency guides +for freq, name in [(261.63, "C4"), (523.25, "C5")]: + if mel_edge_freqs[1] <= freq <= mel_edge_freqs[-1]: + span = Span( + location=freq, dimension="width", line_color="#ffffff", line_alpha=0.25, line_width=2, line_dash="dotted" + ) + p.add_layout(span) + label = Label( + x=times.max() + time_step * 0.3, + y=freq, + text=name, + text_font_size="20pt", + text_color="#ffffff", + text_alpha=0.7, + text_font_style="bold", + ) + p.add_layout(label) + +# Descending passage label +desc_label = Label( + x=2.55, + y=mel_edge_freqs[-1] * 0.85, + text="Descending (A4 \u2192 F4 \u2192 D4)", + text_font_size="22pt", + text_color="#ffffff", + text_alpha=0.65, + text_font_style="italic", +) +p.add_layout(desc_label) + # HoverTool for interactive readout hover = HoverTool( tooltips=[("Time", "@time_s{0.000} s"), ("Frequency", "@freq_hz{0.0} Hz"), ("Power", "@power{0.0} dB")], @@ -183,65 +209,75 @@ ) p.add_tools(hover) -# Color mapper for the colorbar +# Colorbar color_mapper = LinearColorMapper(palette=Magma256, low=vmin, high=vmax) - -# Colorbar labeled in dB color_bar = ColorBar( color_mapper=color_mapper, ticker=BasicTicker(desired_num_ticks=8), - label_standoff=20, + label_standoff=24, border_line_color=None, location=(0, 0), title="Power (dB)", title_text_font_size="32pt", + title_text_font_style="italic", major_label_text_font_size="24pt", + major_label_text_color="#444444", width=70, - padding=40, - title_standoff=20, + padding=50, + title_standoff=24, ) p.add_layout(color_bar, "right") # Y-axis tick labels at key mel band frequencies -mel_tick_freqs = [50, 100, 200, 500, 1000, 2000, 4000, 8000] -mel_tick_freqs = [f for f in mel_tick_freqs if mel_edge_freqs[1] <= f <= mel_edge_freqs[-1]] +mel_tick_freqs = [ + f for f in [50, 100, 200, 500, 1000, 2000, 4000, 8000] if mel_edge_freqs[1] <= f <= mel_edge_freqs[-1] +] p.yaxis.ticker = FixedTicker(ticks=mel_tick_freqs) -# Style for 4800x2700 canvas -p.title.text_font_size = "40pt" +# Typography for 4800x2700 canvas +p.title.text_font_size = "42pt" p.title.text_font_style = "bold" -p.title.text_color = "#222222" +p.title.text_color = "#333333" p.xaxis.axis_label_text_font_size = "32pt" p.yaxis.axis_label_text_font_size = "32pt" p.xaxis.major_label_text_font_size = "24pt" p.yaxis.major_label_text_font_size = "24pt" p.xaxis.axis_label_text_font_style = "normal" p.yaxis.axis_label_text_font_style = "normal" +p.xaxis.axis_label_text_color = "#444444" +p.yaxis.axis_label_text_color = "#444444" +p.xaxis.major_label_text_color = "#555555" +p.yaxis.major_label_text_color = "#555555" # Axis styling p.xaxis.axis_line_width = 3 p.yaxis.axis_line_width = 3 +p.xaxis.axis_line_color = "#555555" +p.yaxis.axis_line_color = "#555555" p.xaxis.major_tick_line_width = 3 p.yaxis.major_tick_line_width = 3 +p.xaxis.major_tick_line_color = "#555555" +p.yaxis.major_tick_line_color = "#555555" p.xaxis.minor_tick_line_color = None p.yaxis.minor_tick_line_color = None # Grid - subtle styling -p.xgrid.grid_line_alpha = 0.15 -p.ygrid.grid_line_alpha = 0.15 +p.xgrid.grid_line_alpha = 0.12 +p.ygrid.grid_line_alpha = 0.12 p.xgrid.grid_line_dash = [6, 4] p.ygrid.grid_line_dash = [6, 4] -p.xgrid.grid_line_color = "#aaaaaa" -p.ygrid.grid_line_color = "#aaaaaa" +p.xgrid.grid_line_color = "#888888" +p.ygrid.grid_line_color = "#888888" # Background p.background_fill_color = "#000004" -p.border_fill_color = "white" +p.border_fill_color = "#fafafa" p.outline_line_color = "#333333" p.outline_line_width = 2 -p.min_border_right = 140 -p.min_border_left = 120 -p.min_border_bottom = 100 +p.min_border_right = 180 +p.min_border_left = 130 +p.min_border_bottom = 110 +p.min_border_top = 80 # Save export_png(p, filename="plot.png") From 76cb57a61e00e957962fef560b5454cfc2cfef19 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 11 Mar 2026 20:09:08 +0000 Subject: [PATCH 7/7] chore(bokeh): update quality score 92 and review feedback for spectrogram-mel --- .../spectrogram-mel/implementations/bokeh.py | 3 +- plots/spectrogram-mel/metadata/bokeh.yaml | 168 ++++++++---------- 2 files changed, 81 insertions(+), 90 deletions(-) diff --git a/plots/spectrogram-mel/implementations/bokeh.py b/plots/spectrogram-mel/implementations/bokeh.py index 919ac6076f..5579aa5bc2 100644 --- a/plots/spectrogram-mel/implementations/bokeh.py +++ b/plots/spectrogram-mel/implementations/bokeh.py @@ -1,6 +1,7 @@ -"""pyplots.ai +""" pyplots.ai spectrogram-mel: Mel-Spectrogram for Audio Analysis Library: bokeh 3.9.0 | Python 3.14.3 +Quality: 92/100 | Created: 2026-03-11 """ import numpy as np diff --git a/plots/spectrogram-mel/metadata/bokeh.yaml b/plots/spectrogram-mel/metadata/bokeh.yaml index 49a0199836..30d1b72877 100644 --- a/plots/spectrogram-mel/metadata/bokeh.yaml +++ b/plots/spectrogram-mel/metadata/bokeh.yaml @@ -1,7 +1,7 @@ library: bokeh specification_id: spectrogram-mel created: '2026-03-11T19:42:55Z' -updated: '2026-03-11T19:58:05Z' +updated: '2026-03-11T20:09:08Z' generated_by: claude-opus-4-5-20251101 workflow_run: 22970857215 issue: 4672 @@ -10,41 +10,40 @@ library_version: 3.9.0 preview_url: https://storage.googleapis.com/pyplots-images/plots/spectrogram-mel/bokeh/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/spectrogram-mel/bokeh/plot_thumb.png preview_html: https://storage.googleapis.com/pyplots-images/plots/spectrogram-mel/bokeh/plot.html -quality_score: 85 +quality_score: 92 review: strengths: - - Excellent mel filterbank construction from scratch using scipy.signal STFT, avoiding - librosa dependency - - Quad rendering correctly positions mel bands on log y-axis, fixing the visual - distortion from attempt 1 - - HoverTool provides genuinely interactive readout of time, frequency, and power - values - - Dark background matching Magma colormap floor creates cohesive visual design - - Rich audio synthesis with harmonics and ADSR envelopes produces a visually engaging - spectrogram + - Mel filterbank computed from scratch with proper triangular filters demonstrating + deep audio signal processing understanding + - Magma colormap on dark background is industry-standard for spectrograms and looks + professional + - Musical narrative (C Major Arpeggio to descending passage) with annotations creates + excellent data storytelling + - All font sizes explicitly scaled for 4800x2700 canvas with careful typography + hierarchy + - Vectorized quad construction is efficient and clean + - HoverTool adds genuine interactive value for the HTML export weaknesses: - - 'Design Excellence needs improvement: typography could be more refined, no visual - emphasis or annotation guiding viewer to key insight' - - Nested loop for building quad data arrays is verbose and could be vectorized with - np.repeat/np.tile - - Colorbar right margin slightly tight, could use more breathing room - image_description: The plot displays a mel-spectrogram rendered with the Magma colormap - on a dark background (#000004, matching the colormap's lowest value). The x-axis - shows "Time (seconds)" from 0 to 4, and the y-axis shows "Frequency (Hz)" on a - logarithmic scale with fixed ticks at 50, 100, 200, 500, 1000, 2000, 4000, and - 8000 Hz. A colorbar on the right is labeled "Power (dB)" ranging from approximately - -70 to -20 dB. Bright horizontal bands (yellow-white through pink) are clearly - visible at various frequency levels corresponding to the synthesized melody notes - (C4, E4, G4, C5, A4, F4, D4) and their harmonics, with fundamentals showing the - brightest intensity and harmonics progressively dimmer. Note onsets and offsets - show clean ADSR envelope shaping. The mel bands are rendered as individual quads - correctly positioned on the log y-axis, eliminating the distortion seen in attempt - 1. Background noise appears as low-level dark texture primarily in the lower frequencies. - The title reads "spectrogram-mel · bokeh · pyplots.ai" in bold at the top left. - Subtle dashed grid lines are visible at low opacity. + - LM-01 could reach 5/5 with use of Bokeh built-in image glyph (p.image) instead + of manually constructing quads + - DE-02 could benefit from removing the outline line entirely for a cleaner aesthetic + image_description: The plot displays a mel-spectrogram on a dark (#000004) background + using the Magma256 sequential colormap. The title "spectrogram-mel · bokeh · pyplots.ai" + appears at the top left in bold dark text. The x-axis is labeled "Time (seconds)" + spanning 0 to ~4 seconds, and the y-axis shows "Frequency (Hz)" on a logarithmic + scale with mel-spaced tick marks at 50, 100, 200, 500, 1000, 2000, 4000, and 8000 + Hz. Bright yellow-white horizontal bands clearly depict individual musical notes + with visible harmonic overtones above each fundamental. A white dashed bounding + box in the left portion annotates "C Major Arpeggio (C4 → E4 → G4 → C5)" in italic + white text, while the right portion shows "Descending (A4 → F4 → D4)". Dotted + white horizontal reference lines mark the C4 (261 Hz) and C5 (523 Hz) frequencies + with bold labels at the right edge. A colorbar on the right displays "Power (dB)" + ranging from approximately −100 to −20 dB. The spectrogram clearly shows the ascending + arpeggio followed by a descending passage, with visible attack/release envelopes, + harmonic structure, and a subtle noise floor. criteria_checklist: visual_quality: - score: 28 + score: 30 max: 30 items: - id: VQ-01 @@ -52,71 +51,66 @@ review: score: 8 max: 8 passed: true - comment: 'All font sizes explicitly set: title=40pt, axis labels=32pt, tick - labels=24pt, colorbar title=32pt, colorbar labels=24pt. All text clearly - readable.' + comment: 'All font sizes explicitly set: 42pt title, 32pt axis labels, 24pt + tick labels, 22pt annotations' - id: VQ-02 name: No Overlap score: 6 max: 6 passed: true - comment: No overlapping text elements anywhere in the plot. + comment: All text elements well-positioned, annotations in upper region away + from dense data - id: VQ-03 name: Element Visibility - score: 5 + score: 6 max: 6 passed: true - comment: Mel bands clearly visible with good contrast. Quad rendering correctly - positions bands on log scale. Lower frequency bins slightly coarse due to - narrow mel bandwidth. + comment: Mel band quads clearly visible with excellent contrast, harmonic + structure distinguishable - id: VQ-04 name: Color Accessibility score: 4 max: 4 passed: true - comment: Magma is perceptually uniform and colorblind-safe with excellent - contrast. + comment: Magma256 is perceptually uniform and colorblind-safe - id: VQ-05 name: Layout & Canvas - score: 3 + score: 4 max: 4 passed: true - comment: Good canvas utilization with colorbar on right. Colorbar title text - slightly tight against right edge. + comment: Plot fills canvas well with explicitly set margins, colorbar properly + positioned - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Time (seconds) and Frequency (Hz) descriptive with units. Colorbar - labeled Power (dB). + comment: Time (seconds) and Frequency (Hz) - descriptive with units design_excellence: - score: 12 + score: 15 max: 20 items: - id: DE-01 name: Aesthetic Sophistication - score: 5 + score: 6 max: 8 passed: true - comment: Magma colormap is strong choice. Dark background matching colormap - floor is professional. Toolbar removed, bold title. Above default but not - publication-grade. + comment: 'Strong design: Magma on dark background, intentional typography + hierarchy, white annotation overlays' - id: DE-02 name: Visual Refinement score: 4 max: 6 passed: true - comment: Subtle dashed grid (alpha=0.15), minor ticks hidden, custom outline, - dark background fill, generous padding. Clear refinement effort. + comment: Subtle grid (alpha=0.12, dashed), minor ticks removed, custom background/border + colors - id: DE-03 name: Data Storytelling - score: 3 + score: 5 max: 6 passed: true - comment: Synthesized melody creates visual narrative with note progressions - and harmonic structure. No explicit visual emphasis guiding viewer to key - insight. + comment: Dashed box highlights ascending arpeggio, separate label for descending + passage, C4/C5 frequency guides spec_compliance: score: 15 max: 15 @@ -126,54 +120,53 @@ review: score: 5 max: 5 passed: true - comment: Correct mel-spectrogram visualization with mel-scaled frequency axis. + comment: Correct mel-spectrogram with proper mel filterbank computed from + scratch - id: SC-02 name: Required Features score: 4 max: 4 passed: true - comment: 'All required features present: dB scale, sequential colormap, time/frequency - axes, colorbar in dB, correct parameters, synthesized audio.' + comment: 'All spec requirements met: dB scale, Magma colormap, time x-axis, + mel-scaled y-axis, colorbar in dB, correct parameters' - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: X=time Y=frequency correctly mapped. Quads properly positioned on - mel scale with log y-axis. + comment: X=time in seconds, Y=mel-frequency in Hz on log scale - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title format correct. No legend needed for this plot type. + comment: Title matches required format, colorbar serves as scale reference data_quality: - score: 14 + score: 15 max: 15 items: - id: DQ-01 name: Feature Coverage - score: 5 + score: 6 max: 6 passed: true - comment: Rich audio with 7 notes, harmonics, ADSR envelopes, background noise. - Could show wider frequency variety. + comment: 'Rich audio: 7 notes with 4 harmonics each, attack/release envelopes, + background noise' - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Real musical notes with correct frequencies, standard sample rate - and duration. + comment: Musical melody context (C Major Arpeggio) is realistic and neutral - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: 'All values sensible: 22050 Hz sample rate, musical frequencies in - hearing range, 128 mel bands.' + comment: Sample rate 22050 Hz, 4-second duration, musical frequencies - all + realistic code_quality: - score: 9 + score: 10 max: 10 items: - id: CQ-01 @@ -181,33 +174,31 @@ review: score: 3 max: 3 passed: true - comment: Clean imports-data-STFT-mel-plot-save structure, no functions or - classes. + comment: Clean imports-data-plot-save structure, no functions or classes - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: np.random.seed(42) set at beginning. + comment: np.random.seed(42) set at start - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: 'All imports used: numpy, bokeh models/palettes/plotting/io, scipy.signal.' + comment: 'All imports used: numpy, bokeh models, Magma256, scipy.signal' - id: CQ-04 name: Code Elegance - score: 1 + score: 2 max: 2 - passed: false - comment: Nested loop for quad data construction is verbose; could be vectorized - with np.repeat/np.tile. + passed: true + comment: Appropriate complexity, vectorized quad construction, no fake functionality - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Saves as plot.png and plot.html, current API usage. + comment: Saves as plot.png via export_png and plot.html via save library_mastery: score: 7 max: 10 @@ -217,29 +208,28 @@ review: score: 4 max: 5 passed: true - comment: Good use of ColumnDataSource, quad renderer, ColorBar, LinearColorMapper, - FixedTicker, Magma256 from bokeh.palettes, export_png/output_file/save. + comment: Excellent use of ColumnDataSource, quad glyphs, layout system, ColorBar, + FixedTicker - id: LM-02 name: Distinctive Features score: 3 max: 5 passed: true - comment: HoverTool with time/frequency/power readout is genuinely distinctive - Bokeh feature. HTML export alongside PNG. ColumnDataSource enables hover - interactivity. - verdict: REJECTED + comment: HoverTool with formatted tooltips, HTML export, BoxAnnotation - distinctively + Bokeh features + verdict: APPROVED impl_tags: dependencies: - scipy techniques: - colorbar - - manual-ticks + - annotations - hover-tooltips + - manual-ticks - html-export patterns: - data-generation - matrix-construction - - iteration-over-groups - columndatasource dataprep: - binning