make proc sweep general

This commit is contained in:
JohannesDittloff 2025-05-09 15:08:19 +02:00
parent b1ec523aaa
commit 14ca15e96d
9 changed files with 127 additions and 74 deletions

View File

@ -43,6 +43,7 @@ from .measurement import measure_spectrum as _measure_spectrum, set_offsets_lase
from .utility.prsdata import PrsData, plot_spectrum from .utility.prsdata import PrsData, plot_spectrum
from .utility.config_file import ConfigFile from .utility.config_file import ConfigFile
from .utility.device_select import select_device_interactive, connect_device_from_config_or_interactive from .utility.device_select import select_device_interactive, connect_device_from_config_or_interactive
from .utility import duration_to_string
from .update_funcs import Monitor from .update_funcs import Monitor
import logging import logging
@ -170,6 +171,7 @@ def measure_spectrum(metadata:dict={},
# measure/set offset # measure/set offset
full_scale_voltage = lockin_params["sensitivity_volt"] full_scale_voltage = lockin_params["sensitivity_volt"]
def set_offsets(name): def set_offsets(name):
# TODO: use multithreading and queues
shutter.close() shutter.close()
plt_monitor.set_fig_title(f"Measuring baseline with lamp off") plt_monitor.set_fig_title(f"Measuring baseline with lamp off")
R_offset_fs, phase_offset_deg = set_offsets_laser_only(lockin, shutter, measurement_params["wait_time_s"]) R_offset_fs, phase_offset_deg = set_offsets_laser_only(lockin, shutter, measurement_params["wait_time_s"])
@ -236,23 +238,24 @@ def measure_spectrum(metadata:dict={},
def sweep_ref(): def sweep_ref():
wavelenghts = [500, 550, 650, 660, 670, 680] wavelenghts = [500, 535, 565, 580, 700]
frequencies = list(range(27, 500, 5)) frequencies = list(range(27, 500, 2))
frequencies = [111, 444]
lockin_params = { lockin_params = {
"time_constant_s": 10, "time_constant_s": 10,
} }
measurement_params = { measurement_params = {
"wavelengths_nm": wavelenghts, "wavelengths_nm": wavelenghts,
} }
time_est = len(frequencies) * get_time_estimate(lockin_params=lockin_params, measurement_params=measurement_params, offset_with_laser_only=True, extra_wait_time_s=10) time_est = 1.15 * len(frequencies) * get_time_estimate(lockin_params=lockin_params, measurement_params=measurement_params, offset_with_laser_only=True, extra_wait_time_s=10)
print(f"Estimated time: {time_est}") print(f"Estimated time: {duration_to_string(time_est)}")
return t_start = time.time()
for f in frequencies: for f in frequencies:
dirname = f"2025-05-07_f-scan_f={f}_Hz" dirname = f"2025-05-09_f-scan_f={f}_Hz"
lockin_params["frequency"] = f lockin_params["frequency_Hz"] = f
measure_spectrum(lockin_params=lockin_params, measurement_params=measurement_params, dirname=dirname, name="Frequency scan $f = {f}$ Hz") measure_spectrum(lockin_params=lockin_params, measurement_params=measurement_params, dirname=dirname, name="Frequency scan $f = {f}$ Hz")
plt.close('all') plt.close('all')
duration = time.time() - t_start
print(f"Measurement took {duration_to_string(duration)} (estimate was {duration_to_string(time_est)})")
# DATA # DATA
def data_load(dirname:str) -> tuple[np.ndarray, dict]: def data_load(dirname:str) -> tuple[np.ndarray, dict]:

View File

@ -0,0 +1,87 @@
from prsctrl.utility.prsdata import PrsData
import os
import re
import numpy as np
import matplotlib.pyplot as plt
def process_results(
data_dir,
dir_regex=r".*",
out_dir=None,
get_varied_param=lambda data: data.metadata["lock-in_settings"]["frequency_Hz"],
fig_suptitle="Frequency scan: <qty>",
xlabel="$f$ [Hz]",
what=["theta", "stheta", "dR_R", "sdR_R"],
):
"""
Process the results of several spectra recorded with one varying measurement or lock-in parameter.
:param data_dir: Directory containing all data directories of the individual files
:param dir_regex: Regex for filtering data directories
:param out_dir: Directory into which the plots are saved
:param get_varied_param: lambda function getting the varied parameter value from a loaded PrsData object
:param fig_suptitle: Figure suptitle, <qty> will be replaced by the name of the quantity
:param xlabel: xlabel for the varied parameter
:param what: For which quantities to create plots
:return:
"""
data_dir = os.path.expanduser(data_dir)
if out_dir is None:
out_dir = data_dir
paths = os.listdir(data_dir)
data_dirs = []
for p in paths:
full_path = os.path.join(data_dir, p)
if not os.path.isdir(full_path): continue
m = re.fullmatch(dir_regex, p)
if m:
data_dirs.append(full_path)
else:
print(f"Unmatched directory {p}")
assert len(data_dirs) > 0
data_dirs.sort()
varied_params = []
data = {}
shape = None
wls = None
for d in data_dirs:
print(f"Getting data from {d}")
pd = PrsData(load_data_path=d)
# f = pd.metadata["lock-in_settings"]["frequency_Hz"]
varied_param = get_varied_param(pd)
# print(d, f)
sdata = pd.get_spectrum_data()
print(pd.wavelengths)
print(pd.data.keys())
if wls is None: wls = sdata[:,0]
if shape is None: shape = sdata.shape
else:
if shape != sdata.shape:
print(f"ERROR Shape mismatch for param={varied_param}: {shape} != {sdata.shape}")
continue
# raise ValueError(f"Shape mismatch for {d}: {shape} != {sdata.shape}")
varied_params.append(varied_param)
data[varied_param] = sdata
data_per_wl_and_vp = np.empty((shape[0], len(varied_params), shape[1]))
varied_params.sort()
for i in range(shape[0]):
for j, f in enumerate(varied_params):
data_per_wl_and_vp[i, j, :] = data[f][i,:]
print(f"Found wavelengths: {wls}")
n_cols = 2
n_rows = wls.shape[0] // n_cols + wls.shape[0] % n_cols
for qty in what:
fig, axs = plt.subplots(n_rows, n_cols, figsize=(8, 8))
axs = axs.flatten()
qty_idx = PrsData.default_spectrum_columns.index(qty)
fig.suptitle(fig_suptitle.replace("<qty>", PrsData.key_names[qty]))
for i, wl in enumerate(wls):
ax = axs[i]
ax.set_xlabel(xlabel)
ax.set_ylabel(PrsData.labels[qty])
ax.plot(varied_params, data_per_wl_and_vp[i, :, qty_idx])
ax.set_title(f"$\\lambda = {wl}$ nm")
fig.tight_layout()
fig.savefig(os.path.join(out_dir, f"result_{qty}.pdf"))
print(varied_params)

View File

@ -1,65 +0,0 @@
from prsctrl.utility.prsdata import PrsData
import os
import re
import numpy as np
import matplotlib.pyplot as plt
def process_results(data_dir, dir_regex=r"202.-..-.._f-scan_f=(\d+)_Hz", out_dir=None):
data_dir = os.path.expanduser(data_dir)
if out_dir is None:
out_dir = data_dir
paths = os.listdir(data_dir)
data_dirs = []
for p in paths:
full_path = os.path.join(data_dir, p)
if not os.path.isdir(full_path): continue
m = re.fullmatch(dir_regex, p)
if m:
data_dirs.append(full_path)
else:
print(f"Unmatched directory {p}")
assert len(data_dirs) > 0
data_dirs.sort()
frequencies = []
data = {}
shape = None
wls = None
for d in data_dirs:
print(f"Getting data from {d}")
pd = PrsData(load_data_path=d)
f = pd.metadata["lock-in_settings"]["frequency_Hz"]
# print(d, f)
sdata = pd.get_spectrum_data()
print(pd.wavelengths)
print(pd.data.keys())
if wls is None: wls = sdata[:,0]
if shape is None: shape = sdata.shape
else:
if shape != sdata.shape:
print(f"ERROR Shape mismatch for f={f}: {shape} != {sdata.shape}")
continue
# raise ValueError(f"Shape mismatch for {d}: {shape} != {sdata.shape}")
frequencies.append(f)
data[f] = sdata
data_per_wl_and_f = np.empty((shape[0], len(frequencies), shape[1]))
frequencies.sort()
for i in range(shape[0]):
for j, f in enumerate(frequencies):
data_per_wl_and_f[i, j, :] = data[f][i,:]
print(f"Found wavelengths: {wls}")
n_cols = 2
for qty in ["theta", "stheta", "dR_R", "sdR_R"]:
fig, axs = plt.subplots(wls.shape[0]//n_cols, n_cols, sharex=True, figsize=(8, 8))
axs = axs.flatten()
qty_idx = PrsData.default_spectrum_columns.index(qty)
fig.suptitle(f"Frequency scan: {PrsData.key_names[qty]}")
axs[-1].set_xlabel("Modulation Frequency $f$ [Hz]")
for i, wl in enumerate(wls):
ax = axs[i]
ax.set_ylabel(PrsData.labels[qty])
ax.plot(frequencies, data_per_wl_and_f[i, :, qty_idx])
ax.set_title(f"$\\lambda = {wl}$ nm")
fig.tight_layout()
fig.savefig(out_dir + f"result_{qty}.pdf")
print(frequencies)

View File

@ -0,0 +1,25 @@
timedelta = [("d", 24*3600), ("h", 3600), ("m", 60), ("s", 1)]
def duration_to_string(duration: float) -> str:
"""
Convert a duration in seconds to a string of the form "<days>d <hours>h <minutes>m <seconds>s"
where only the largest units are included.
Parameters
----------
duration: float
Duration in seconds.
Returns
-------
String representation of the duration.
"""
include = False
s = ""
sign = 1 if duration > 0 else -1
time_left = abs(int(duration))
for i, (unit, unit_seconds) in enumerate(timedelta):
t = int(time_left / unit_seconds)
if t != 0 or include or unit == "s":
s += f"{sign*t:02}{unit} "
include = True
time_left %= unit_seconds
return s.strip()

View File

@ -162,8 +162,11 @@ class PrsData:
"dR": r"$\Delta R$ [V]", "dR": r"$\Delta R$ [V]",
"R": r"$R$ [V]", "R": r"$R$ [V]",
"theta": r"$\theta$ [°]", "theta": r"$\theta$ [°]",
"sdR_R": r"$\sigma_{\Delta R/R}$",
"sdR": r"$\sigma_{\Delta R}$ [V]",
"sR": r"$\sigma_R$ [V]",
"stheta": r"$\sigma_\theta$ [°]",
} }
labels |= {f"s{k}": f"$\sigma_{{{v[1:]}}}" for k, v in labels.items()}
def get_spectrum_data(self, wavelengths=None, keys=None) -> np.ndarray: def get_spectrum_data(self, wavelengths=None, keys=None) -> np.ndarray:
""" """
Return the spectral data for the specified keys and wavelengths. Return the spectral data for the specified keys and wavelengths.