The SpinData Object
The SpinData object is the central data structure in SpinLab. Every time data is loaded from a file — or created manually — SpinLab returns a SpinData object. All processing, analysis, and plotting functions operate on SpinData objects.
Understanding its structure is the most important step to working effectively with SpinLab.
Overview
A SpinData object bundles five things together:
values |
The actual data, stored as a NumPy |
dims |
A list of strings labeling each dimension of |
coords |
A list of 1D NumPy arrays — one per dimension — giving the physical axis values |
attrs |
A dictionary of experimental parameters imported from the spectrometer file |
proc_attrs |
An automatically maintained audit log of every processing step applied to the object |
The key design principle is that dims and coords always travel with the data. When you slice, add, or Fourier-transform a SpinData object, the axes are updated automatically. You never lose track of what the numbers mean.
Creating a SpinData Object
SpinLab creates SpinData objects automatically when you load a file:
import spinlab as sl
data = sl.load("path/to/my_spectrum.DTA")
You can also create one manually, which is useful for testing or for wrapping data you have computed yourself:
import numpy as np
import spinlab as sl
x = np.linspace(330, 360, 1024) # magnetic field axis in mT
y = sl.math.lineshape.lorentzian(x, 345, 1.0) # simulated EPR line
data = sl.SpinData(
values = y,
dims = ["B0"],
coords = [x],
attrs = {"experiment_type": "epr_spectrum", "mw_frequency": 9.8e9}
)
For a 2D dataset (e.g. 1024 field points × 10 averages):
field = np.linspace(330, 360, 1024)
scans = np.arange(10)
values = np.random.randn(1024, 10)
data_2d = sl.SpinData(
values = values,
dims = ["B0", "Average"],
coords = [field, scans]
)
Note
The order of dims and coords must match the axis order of values.
values.shape[0] must equal len(coords[0]), and so on.
The Four Core Attributes
values
The raw data, always a NumPy ndarray. You can read and write it directly:
print(data.values) # the numpy array
print(data.values.shape) # e.g. (1024,) for 1D or (1024, 10) for 2D
print(data.values.dtype) # e.g. float64, complex128
data.values = data.values * 2 # scale the data in-place
dims
A Python list of strings — one label per dimension. Labels are used throughout SpinLab to identify axes by name rather than by integer index, making code more readable and less error-prone:
print(data.dims) # e.g. ['B0'] or ['t2', 't1']
# Rename a dimension
data.rename("B0", "field")
print(data.dims) # ['field']
coords
A Coords object (behaves like a list of NumPy arrays). Each entry corresponds to one dimension and holds the physical axis values. You can access individual axes by name or by integer index:
print(data.coords["B0"]) # field axis array
print(data.coords[0]) # same, by integer index
# Replace an axis
data.coords["B0"] = np.linspace(330, 360, 1024)
attrs
A plain Python dictionary holding experimental parameters that were read from the spectrometer file — things like microwave frequency, temperature, number of scans, modulation amplitude, etc.:
print(data.attrs) # all parameters
print(data.attrs["mw_frequency"]) # single parameter
data.attrs["my_key"] = "my_value" # add a new entry
A particularly important key is experiment_type, which controls how SpinLab's fancy_plot function labels and formats the figure. It is set automatically on import, but can be changed:
data.attrs["experiment_type"] = "epr_spectrum"
Printing a SpinData Object
Calling print() on a SpinData object gives a compact summary of its contents:
print(data)
Example output for a 1D EPR spectrum:
values:
2250 ndarray (float64)
dims:
['B0']
coords:
array([342.055, 342.062, ..., 357.048], shape=(2250,))
attrs:
'experiment_type': 'epr_spectrum'
'mw_frequency': 9.804448
'center_field': 3495.55
'power': 1.002
+12 attrs
To print the full attrs dictionary without truncation use data.exp_info().
Inspecting Attributes
SpinLab provides three convenience methods for printing the different attribute dictionaries in a formatted table:
data.exp_info() # print experimental parameters (attrs)
data.spinlab_info() # print internal SpinLab parameters
data.proc_info() # print processing audit log
data.show_attrs() # print spinlab_info + proc_info together
The processing audit log (proc_info) is particularly useful for reproducibility — it records every processing step that has been applied to the object, along with the parameters used:
1 | fourier_transform | dim: t2, zero_fill_factor: 2
2 | apodization | dim: t2, linewidth: 10
3 | phase | p0: -12.4, p1: 0.0
Key Methods
copy
Returns a deep copy of the object. Always use copy() before modifying data if you want to keep the original unchanged — all SpinLab processing functions do this internally:
data_copy = data.copy()
squeeze
Removes all dimensions of length 1. This is commonly needed after indexing, which retains the indexed dimension even when only one slice is selected:
data_slice = data["t1", 3] # shape is (1024, 1) — t1 dimension kept
data_slice.squeeze() # shape becomes (1024,)
rename
Renames a dimension in-place. The corresponding coords entry is updated automatically:
data.rename("x0", "t1")
select
Selects a subset of slices from a 2D object. Accepts integers, range objects, or a list/tuple mixing both:
data.select(3) # single slice at index 3
data.select(range(2, 8)) # slices 2 through 7
data.select((1, range(5, 10), 15)) # slices 1, 5–9, and 15
add_proc_attrs
Stamps a processing step into the audit log. This is called automatically by every SpinLab processing function, but you can also call it manually to record custom steps:
data.add_proc_attrs("my_custom_step", {"param1": 1.0, "param2": "value"})
Arithmetic Operations
SpinData objects support standard arithmetic operators directly. Operations are applied element-wise to values:
# With scalars or numpy arrays
data_scaled = data * 2.0
data_shifted = data + 1000
# Between two SpinData objects — axes are aligned automatically
data_sum = data_a + data_b
data_diff = data_a - data_b
data_prod = data_a * data_b
data_div = data_a / data_b
SpinLab also has basic NumPy compatibility, so many NumPy functions (e.g. np.abs, np.real, np.imag) work directly on SpinData objects and return a SpinData object with the correct axes intact.
Further Reading
Indexing — detailed guide to slicing and selecting data by index, integer, and float
Plotting Data — how to plot
SpinDataobjects usingsl.plot()andsl.fancy_plot()Core — full API reference for the
SpinDataclass and related modules