Chris Parmer — home

Mass spectrum

Mass Spectrometry — MassBank record MSBNK-EAWAG-EC001501

Example from the compendium of canonical charts

Mass Spectrometry — Mass spectrum (MassBank record MSBNK-EAWAG-EC001501)

Python Code

"""Mass Spectrometry — Mass spectrum (MassBank record MSBNK-EAWAG-EC001501)."""


import numpy as np
import plotly.graph_objects as go

URL = "https://raw.githubusercontent.com/MassBank/MassBank-data/main/Eawag/MSBNK-EAWAG-EC001501.txt"


def parse_massbank(text):
    """Extract m/z and intensity from PK$PEAK block."""
    mz_list, int_list = [], []
    in_peak = False
    for line in text.splitlines():
        line = line.strip()
        if line.startswith("PK$PEAK:"):
            in_peak = True
            continue
        if in_peak:
            if line.startswith("//") or line == "":
                break
            parts = line.split()
            if len(parts) >= 2:
                try:
                    mz_list.append(float(parts[0]))
                    int_list.append(float(parts[1]))
                except ValueError:
                    pass
    return np.array(mz_list), np.array(int_list)


def generate():
    import requests, warnings
    warnings.filterwarnings("ignore")

    print("fetching MassBank spectrum …")
    used_synthetic = False
    try:
        r = requests.get(URL, verify=False, timeout=30)
        r.raise_for_status()
        text = r.text
        mz, intensity = parse_massbank(text)
        if len(mz) == 0:
            raise ValueError("no peaks parsed")
        print(f"  parsed {len(mz)} peaks; m/z range: {mz.min():.1f}–{mz.max():.1f}")
    except Exception as e:
        print(f"  fetch/parse failed ({e}); using synthetic spectrum")
        used_synthetic = True
        # Synthetic caffeinated compound-like spectrum
        mz = np.array([
            55.0, 67.1, 81.1, 95.1, 109.1, 123.1, 135.1, 149.1,
            163.1, 177.1, 191.1, 205.2, 219.2, 233.2, 247.2,
            261.2, 275.2, 281.2, 299.2
        ])
        intensity = np.array([
            120, 350, 280, 450, 890, 620, 1100, 750,
            490, 380, 670, 310, 540, 420, 580,
            290, 810, 430, 5600
        ], dtype=float)

    # Normalize to 100%
    rel_int = intensity / intensity.max() * 100.0

    base_peak_idx = np.argmax(rel_int)
    highest_mz_idx = np.argmax(mz)

    print(f"  base peak: m/z={mz[base_peak_idx]:.2f}, rel_int=100%")
    print(f"  highest m/z: {mz[highest_mz_idx]:.2f}")

    # Build stick-spectrum with None-separator pattern (pairs of points per peak)
    x_sticks, y_sticks = [], []
    for m, ri in zip(mz, rel_int):
        x_sticks += [m, m, None]
        y_sticks += [0, ri, None]

    fig = go.Figure()

    # Stick spectrum
    fig.add_trace(go.Scatter(
        x=x_sticks,
        y=y_sticks,
        mode="lines",
        name="Ions",
        line=dict(color=VIOLET, width=1.5),
        hoverinfo="skip",
    ))

    # Invisible scatter for tooltips
    fig.add_trace(go.Scatter(
        x=mz,
        y=rel_int,
        mode="markers",
        marker=dict(size=6, color=VIOLET, opacity=0.7),
        name="m/z",
        hovertemplate="m/z = %{x:.2f}<br>Rel. intensity = %{y:.1f}%<extra></extra>",
    ))

    annotations = [
        dict(
            x=mz[base_peak_idx],
            y=rel_int[base_peak_idx],
            text=f"Base peak<br>m/z = {mz[base_peak_idx]:.2f}",
            showarrow=True,
            arrowhead=2,
            ax=40,
            ay=-40,
            font=dict(size=11),
            bgcolor="rgba(255,255,255,0.85)",
        ),
    ]

    # Annotate highest-mz peak if different from base peak
    if highest_mz_idx != base_peak_idx:
        annotations.append(dict(
            x=mz[highest_mz_idx],
            y=rel_int[highest_mz_idx],
            text=f"[M+H]⁺<br>m/z = {mz[highest_mz_idx]:.2f}",
            showarrow=True,
            arrowhead=2,
            ax=-60,
            ay=-40,
            font=dict(size=11),
            bgcolor="rgba(255,255,255,0.85)",
        ))

    fig.update_layout(
        xaxis=dict(title="m/z"),
        yaxis=dict(title="Relative Intensity (%)", range=[-3, 115]),
        legend=dict(orientation="h", y=1.05, x=0),
        annotations=annotations,
        margin=dict(t=50, b=60, l=70, r=60),
    )
    return fig


if __name__ == "__main__":
    generate()

Made with Plotly