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 ndarray (can be real or complex, any dtype)

dims

A list of strings labeling each dimension of values (e.g. ['B0'], ['t2', 't1'])

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 SpinData objects using sl.plot() and sl.fancy_plot()

  • Core — full API reference for the SpinData class and related modules