Working with Spin objects directly¶
SpinPlots
makes it easy to create plots with a few lines of code using the .plot()
method, advanced users might want more control over data visualization. This tutorial will show you how to work directly with Spin
and SpinCollection
objects to access raw data, process it, and create custom plots.
Spin and SpinCollection objects¶
SpinPlots provides two main classes for handling NMR data:
Spin
: Represents a single NMR datasetSpinCollection
: Is a collection ofSpin
objects
Working with Spin objects¶
# 1D data
glycine = read_nmr("../../data/1D/glycine/pdata/1", provider="bruker")
print(f"Object type: {type(glycine).__name__}")
print(f"Dimensions: {glycine.ndim}D")
print(f"Provider: {glycine.provider}")
print(f"Path to file: {glycine.spectrum['path']}")
print("\nAvailable keys")
for key in glycine.spectrum.keys():
print(f"- {key}")
# Load a 2D spectrum
spectrum_2d = read_nmr("../../data/2D/16/pdata/1", provider="bruker")
print(f"Type: {type(spectrum_2d).__name__}")
print(f"Dimensions: {spectrum_2d.ndim}D")
print(f"Provider: {spectrum_2d.provider}")
print(f"Path to file: {spectrum_2d.spectrum['path']}")
print("\nAvailable keys")
for key in spectrum_2d.spectrum.keys():
print(f"- {key}")
Working with SpinCollection¶
A SpinCollection
behaves like a dictionary of Spin
objects, where each spectrum can be accessed by its tag:
# Load multiple spectra into a collection
collection = read_nmr(
["../../data/1D/glycine/pdata/1", "../../data/1D/alanine/pdata/1"],
provider="bruker",
tags=["Glycine", "Alanine"] # Optional custom tags
)
print(f"Type: {type(collection).__name__}")
print(f"Dimensions: {collection.ndim}D")
print(f"Provider: {collection.provider}")
print(f"Path to file 1: {collection[0].spectrum['path']}")
print(f"Path to file 2: {collection[1].spectrum['path']}")
print("\nKeys file 1")
for key in collection[0].spectrum.keys():
print(f"- {key}")
print("\nKeys file 2")
for key in collection[1].spectrum.keys():
print(f"- {key}")
collection = read_nmr(
["../../data/1D/glycine/pdata/1", "../../data/1D/alanine/pdata/1"],
provider="bruker",
tags=["Glycine", "Alanine"] # Add custom tag
)
glycine_spin = collection["Glycine"]
alanine_spin = collection["Alanine"]
print(f"Glycine Spin object: {glycine_spin}")
print(f"Alanine Spin object: {alanine_spin}")
# Can also be accessed by index
first_spin = collection[0]
print(f"\nFirst Spin in collection: {first_spin}")
# Get all tags
all_tags = list(collection.spins.keys())
print(f"\nAvailable spectra: {all_tags}")
Spin
objects can be easily added or romoved from a SpinCollection
with:
tyrosine = read_nmr("../../data/1D/tyrosine/pdata/1", provider="bruker")
tyrosine.tag = "Tyrosine"
collection.append(tyrosine)
print(f"Collection sized after adding: {collection.size}")
print(f"Updated tags: {list(collection.spins.keys())}\n")
collection.remove("Alanine")
print(f"Collection size after removal: {collection.size}")
print(f"Remaining tags: {list(collection.spins.keys())}")
Create plots from Spin objects¶
The Spin
object contains all information necessary to make a NMR plot
fig, ax = plt.subplots(figsize=(6, 5))
# Load glycine spectrum
glycine = read_nmr("../../data/1D/glycine/pdata/1", provider="bruker")
ppm = glycine.spectrum["ppm_scale"]
data = glycine.spectrum["data"]
nuclei = glycine.spectrum["nuclei"]
# Extract the number and nucleus from the nuclei string
number = "".join(filter(str.isdigit, nuclei))
nucleus = "".join(filter(str.isalpha, nuclei))
ax.plot(ppm, data, linewidth=1.5, color='darkblue')
ax.set_xlabel(f"$^{{{number}}}${nucleus} (ppm)", fontsize=14)
ax.set_ylabel("Intensity (a.u.)", fontsize=14)
ax.set_xlim(180, 0)
plt.tight_layout()
plt.show()
You can also use all the data in SpinCollection
objects to plot the data as you wish
collection = read_nmr(
["../../data/1D/glycine/pdata/1", "../../data/1D/alanine/pdata/1"],
provider="bruker",
tags=["Glycine", "Alanine"] # Add custom tag
)
fig, ax = plt.subplots(figsize=(6, 5))
colors = ['#1f77b4', '#ff7f0e']
for i, (tag, spin) in enumerate(collection):
ppm = spin.spectrum["ppm_scale"]
data = spin.spectrum["norm_max"] # Use normalized data by maximum intensity
color = colors[i % len(colors)]
ax.plot(ppm, data, linewidth=1.5, label=tag, color=color)
ax.set_xlabel("$^{13}$C (ppm)", fontsize=12)
ax.set_ylabel("Normalized Intensity", fontsize=12)
ax.set_xlim(180, 0)
ax.grid(alpha=0.3, linestyle='--')
plt.tight_layout()
plt.show()
Spin
and SpinCollection
objects from 2D spectra can also be used to plot the spectra, with or without projections.
fig = plt.figure(figsize=(8, 6))
gs = fig.add_gridspec(2, 2,
width_ratios=[0.8, 6],
height_ratios=[0.8, 6],
wspace=0.05, hspace=0.05)
ax_main = fig.add_subplot(gs[1, 1]) # Main 2D spectrum
ax_top = fig.add_subplot(gs[0, 1], sharex=ax_main) # Top projection (F2)
ax_left = fig.add_subplot(gs[1, 0], sharey=ax_main) # Left projection (F1)
spectrum_2d = read_nmr("../../data/2D/16/pdata/1", provider="bruker")
data_2d = spectrum_2d.spectrum["data"]
ppm_f1 = spectrum_2d.spectrum["ppm_scale"][0]
ppm_f2 = spectrum_2d.spectrum["ppm_scale"][1]
proj_f1 = spectrum_2d.spectrum["projections"]["f1"]['F1 projection']
proj_f2 = spectrum_2d.spectrum["projections"]["f2"]['F2 projection']
contour_start = 6e6
contour_factor = 1.5
contour_num = 25
contour_levels = contour_start * contour_factor ** np.arange(contour_num)
X, Y = np.meshgrid(ppm_f2, ppm_f1)
contour = ax_main.contour(
X, Y, data_2d,
levels=contour_levels,
colors='black',
linewidths=0.5
)
ax_top.plot(ppm_f2, proj_f2, 'k-', linewidth=1)
ax_left.plot(-proj_f1, ppm_f1, 'k-', linewidth=1)
ax_top.axis('off')
ax_left.axis('off')
ax_main.yaxis.tick_right()
ax_main.yaxis.set_label_position("right")
ax_main.set_xlim(200, 30)
ax_main.set_ylim(400, 60)
ax_left.set_ylim(400, 60)
ax_top.set_xlim(200, 30)
nuclei = spectrum_2d.spectrum["nuclei"]
number_f1, nucleus_f1 = "".join(filter(str.isdigit, nuclei[0])), "".join(filter(str.isalpha, nuclei[0]))
number_f2, nucleus_f2 = "".join(filter(str.isdigit, nuclei[1])), "".join(filter(str.isalpha, nuclei[1]))
ax_main.set_xlabel(f"$^{{{number_f2}}}${nucleus_f2} (ppm)", fontsize=14)
ax_main.set_ylabel(f"$^{{{number_f2}}}${nucleus_f2} (ppm)", fontsize=14)
Find peaks¶
from scipy.signal import find_peaks
alanine = collection["Alanine"]
ppm = alanine.spectrum["ppm_scale"]
data = alanine.spectrum["norm_max"]
# Find peaks
peaks, _ = find_peaks(data, height=0.1, distance=50)
peak_ppm = ppm[peaks]
peak_heights = data[peaks]
fig, ax = plt.subplots(figsize=(7, 6))
ax.plot(ppm, data, 'b-', linewidth=1.5)
ax.plot(peak_ppm, peak_heights, 'ro', label='Detected Peaks')
for i, (x, y) in enumerate(zip(peak_ppm, peak_heights)):
ax.annotate(f'{x:.1f}', xy=(x, y), xytext=(0, 10),
textcoords='offset points', ha='center', fontsize=10)
ax.set_xlabel('$^{13}$C (ppm)', fontsize=14)
ax.set_ylabel('Normalized Intensity', fontsize=14)
ax.legend()
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.tight_layout()
plt.show()