Analysis Module

The analysis module handles data saving/loading, and should eventually include data analysis routines for common NMR experiments. Specific calibrations routines will re-use identical fitting procedures, so these will be implemented here for modularity. A well maintained collection of analysis tools will facilitate rapid experiment development and deployment, and hopefully lead to autonomous machine calibration.

Data Saver

Data Saving Module.

This module provides utilities for saving experiment data from NMR experiments using the OPX-1000, including automated handling of NumPy arrays, matplotlib figures, and other scientific computing types via QuantumEncoder.

class qeg_nmr_qua.analysis.data_saver.DataSaver(root_data_folder)[source]

Bases: object

Manage saving and loading of NMR experiment data with metadata.

Provides a structured approach to persisting experiment configurations, settings, commands, and results to disk. Each experiment is saved to a uniquely named folder containing JSON files and optionally PNG figures.

Folder Structure:

Each saved experiment creates a folder with:

experiment_001/
├── config.json          # OPX configuration
├── settings.json        # Experiment settings
├── commands.json        # Command sequence executed
├── data.json            # Experimental results and metadata
├── figures.json         # (Optional) Mapping of figure keys to filenames
└── figure_*.png         # (Optional) Saved matplotlib figures

Data Handling:

  • JSON-serializable data (dicts, lists, numbers, strings) are saved directly

  • NumPy arrays/scalars are converted to native Python types

  • Matplotlib figures are automatically saved as PNG files (300 dpi)

  • Non-serializable objects are converted to descriptive strings with warnings

  • Path objects are converted to strings

Error Recovery:

If saving fails, the partially created experiment folder is automatically cleaned up, maintaining a consistent state.

root_data_folder

The root directory where experiment data will be saved.

Type:

Path

Example

>>> saver = DataSaver("./experiment_data")
>>> config = {"qop_ip": "192.168.1.100", ...}
>>> settings = {"n_avg": 8, ...}
>>> commands = [{"type": "pulse", ...}]
>>> data = {"I_data": np.array([...]), "Q_data": np.array([...])}
>>> folder = saver.save_experiment("exp_001", config, settings, commands, data)
>>> loaded = saver.load_experiment("exp_001")
__init__(root_data_folder)[source]

Initialize the DataSaver with a root data folder.

Creates the root data folder if it doesn’t exist. All experiments will be saved as subfolders within this directory.

Parameters:

root_data_folder (str | Path) – The root directory for saving experiment data. Can be a string path or Path object. Will be created with parents=True if it doesn’t exist.

Example

>>> saver = DataSaver("./data")
>>> saver.root_data_folder
PosixPath('data')
save_experiment(experiment_prefix, config, settings, commands, data)[source]

Save experiment metadata and data to a structured directory.

Creates a folder with an auto-incremented name based on experiment_prefix (e.g., prefix_0001, prefix_0002) and atomically saves OPX configuration, experiment settings, command sequence, and experimental results. Handles special data types (numpy arrays, matplotlib figures) and non-serializable objects gracefully.

Saved Files:

  • config.json: OPX-1000 configuration dictionary

  • settings.json: Experiment settings (frequencies, pulse params, etc.)

  • commands.json: List of pulse commands executed

  • data.json: Experimental results and metadata (numpy arrays converted to lists)

  • figures.json: (Optional) Mapping of data keys to saved figure filenames

  • figure_*.png: (Optional) Matplotlib figures extracted from data

Data Processing:

  • NumPy arrays are converted to JSON-serializable lists

  • NumPy scalars are converted to native Python types

  • Matplotlib figures are automatically saved as PNG files with 300 dpi

  • Non-serializable objects are converted to descriptive strings with warnings

  • Failed keys are tracked in _failed_keys in the saved data

Parameters:
  • experiment_prefix (str) – Prefix for the experiment folder name (e.g., “experiment”). The actual folder created will be <experiment_prefix>_NNNN with a zero-padded 4-digit counter (starting at 0001). Must not contain path separators or be “.”.

  • config (OPXConfig) – OPX configuration object.

  • settings (ExperimentSettings) – Experiment settings object.

  • commands (list[dict[str, Any]]) – List of command dictionaries defining the pulse sequence.

  • data (dict[str, Any]) – Experimental data dictionary. Can contain numpy arrays, matplotlib figures, and other Python objects. NumPy types and figures are handled automatically.

Returns:

The path to the created experiment folder.

Return type:

Path

Raises:
  • ValueError – If experiment_prefix contains path separators or is invalid.

  • RuntimeError – If saving fails (folder is cleaned up on failure).

Example

>>> saver = DataSaver("./data")
>>> folder = saver.save_experiment(
...     "exp",
...     config={"qop_ip": "192.168.1.100"},
...     settings={"n_avg": 8},
...     commands=[{"type": "pulse", "name": "pi_half"}],
...     data={"I": np.array([1, 2, 3]), "Q": np.array([4, 5, 6])}
... )
>>> folder.name  # first call
'exp_0001'
load_experiment(experiment_name)[source]

Load experiment metadata and data from a saved folder.

Reconstructs the complete experiment state from JSON files. Returns all saved data including configuration, settings, commands, and results.

Parameters:

experiment_name (str) – Name of the experiment folder to load.

Returns:

Dictionary with keys:
  • config: OPX configuration

  • settings: Experiment settings

  • commands: Command sequence

  • data: Experimental results

  • figures: (Optional) Mapping of figure keys to filenames

Return type:

dict[str, Any]

Raises:

FileNotFoundError – If the experiment folder or required files don’t exist.

Example

>>> saver = DataSaver("./data")
>>> loaded = saver.load_experiment("exp_001")
>>> config = loaded["config"]
>>> data = loaded["data"]
save_settings(settings, name, overwrite=False)[source]

Save settings independently for later reuse.

The settings are stored under the settings/ subfolder in the root data folder as a JSON file named <name>.json. Accepts either a dict or an object that implements to_dict(). By default this method will not overwrite an existing settings file unless overwrite=True.

Parameters:
  • name (str) – Simple name for the settings (no path separators).

  • settings (ExperimentSettings) – Settings object or dict.

  • overwrite (bool) – Whether to overwrite an existing settings file.

Returns:

Path to the saved settings JSON file.

Return type:

Path

Raises:
  • ValueError – If name is invalid or the file exists and overwrite is False.

  • RuntimeError – On I/O or serialization failures.

load_settings(name)[source]

Load a previously saved settings JSON by name.

Returns the deserialized dict as saved. Raises FileNotFoundError if not found.

Return type:

dict[str, Any]

list_saved_settings()[source]

Return list of saved settings names (without .json extension).

Return type:

list[str]

list_experiments()[source]

List all saved experiments in the root data folder.

Scans the root folder and returns a sorted list of all experiment folders that contain a valid data.json file.

Returns:

List of experiment folder names sorted alphabetically.

Returns an empty list if no experiments have been saved.

Return type:

list[str]

Example

>>> saver = DataSaver("./data")
>>> experiments = saver.list_experiments()
>>> experiments
['exp_001', 'exp_002', 'exp_003']