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:
objectManage 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 dictionarysettings.json: Experiment settings (frequencies, pulse params, etc.)commands.json: List of pulse commands executeddata.json: Experimental results and metadata (numpy arrays converted to lists)figures.json: (Optional) Mapping of data keys to saved figure filenamesfigure_*.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_keysin the saved data
- Parameters:
experiment_prefix (str) – Prefix for the experiment folder name (e.g., “experiment”). The actual folder created will be
<experiment_prefix>_NNNNwith 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 configurationsettings: Experiment settingscommands: Command sequencedata: Experimental resultsfigures: (Optional) Mapping of figure keys to filenames
- Return type:
- 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 implementsto_dict(). By default this method will not overwrite an existing settings file unlessoverwrite=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.
- 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.jsonfile.- Returns:
- List of experiment folder names sorted alphabetically.
Returns an empty list if no experiments have been saved.
- Return type:
Example
>>> saver = DataSaver("./data") >>> experiments = saver.list_experiments() >>> experiments ['exp_001', 'exp_002', 'exp_003']