diff --git a/prsctrl/prsctrl_interactive.py b/prsctrl/prsctrl_interactive.py index 730ee97..5f08a34 100644 --- a/prsctrl/prsctrl_interactive.py +++ b/prsctrl/prsctrl_interactive.py @@ -43,6 +43,7 @@ from .measurement import measure_spectrum as _measure_spectrum, set_offsets_lase from .utility.prsdata import PrsData, plot_spectrum from .utility.config_file import ConfigFile 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 import logging @@ -170,6 +171,7 @@ def measure_spectrum(metadata:dict={}, # measure/set offset full_scale_voltage = lockin_params["sensitivity_volt"] def set_offsets(name): + # TODO: use multithreading and queues shutter.close() 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"]) @@ -236,23 +238,24 @@ def measure_spectrum(metadata:dict={}, def sweep_ref(): - wavelenghts = [500, 550, 650, 660, 670, 680] - frequencies = list(range(27, 500, 5)) - frequencies = [111, 444] + wavelenghts = [500, 535, 565, 580, 700] + frequencies = list(range(27, 500, 2)) lockin_params = { "time_constant_s": 10, } measurement_params = { "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) - print(f"Estimated time: {time_est}") - return + 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: {duration_to_string(time_est)}") + t_start = time.time() for f in frequencies: - dirname = f"2025-05-07_f-scan_f={f}_Hz" - lockin_params["frequency"] = f + dirname = f"2025-05-09_f-scan_f={f}_Hz" + lockin_params["frequency_Hz"] = f measure_spectrum(lockin_params=lockin_params, measurement_params=measurement_params, dirname=dirname, name="Frequency scan $f = {f}$ Hz") plt.close('all') + duration = time.time() - t_start + print(f"Measurement took {duration_to_string(duration)} (estimate was {duration_to_string(time_est)})") # DATA def data_load(dirname:str) -> tuple[np.ndarray, dict]: diff --git a/prsctrl/tests/__pycache__/spectrum_sweeps.cpython-311.pyc b/prsctrl/tests/__pycache__/spectrum_sweeps.cpython-311.pyc new file mode 100644 index 0000000..7cfa9a9 Binary files /dev/null and b/prsctrl/tests/__pycache__/spectrum_sweeps.cpython-311.pyc differ diff --git a/prsctrl/tests/__pycache__/sweep_frequency.cpython-311.pyc b/prsctrl/tests/__pycache__/sweep_frequency.cpython-311.pyc deleted file mode 100644 index 37d479b..0000000 Binary files a/prsctrl/tests/__pycache__/sweep_frequency.cpython-311.pyc and /dev/null differ diff --git a/prsctrl/tests/spectrum_sweeps.py b/prsctrl/tests/spectrum_sweeps.py new file mode 100644 index 0000000..033d6bc --- /dev/null +++ b/prsctrl/tests/spectrum_sweeps.py @@ -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: ", + 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, 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("", 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) diff --git a/prsctrl/tests/sweep_frequency.py b/prsctrl/tests/sweep_frequency.py deleted file mode 100644 index e655849..0000000 --- a/prsctrl/tests/sweep_frequency.py +++ /dev/null @@ -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) diff --git a/prsctrl/utility/__init__.py b/prsctrl/utility/__init__.py index e69de29..58e2faa 100644 --- a/prsctrl/utility/__init__.py +++ b/prsctrl/utility/__init__.py @@ -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 "d h m 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() diff --git a/prsctrl/utility/__pycache__/__init__.cpython-311.pyc b/prsctrl/utility/__pycache__/__init__.cpython-311.pyc index 23ea318..658c008 100644 Binary files a/prsctrl/utility/__pycache__/__init__.cpython-311.pyc and b/prsctrl/utility/__pycache__/__init__.cpython-311.pyc differ diff --git a/prsctrl/utility/__pycache__/prsdata.cpython-311.pyc b/prsctrl/utility/__pycache__/prsdata.cpython-311.pyc index ba03300..ee2282b 100644 Binary files a/prsctrl/utility/__pycache__/prsdata.cpython-311.pyc and b/prsctrl/utility/__pycache__/prsdata.cpython-311.pyc differ diff --git a/prsctrl/utility/prsdata.py b/prsctrl/utility/prsdata.py index 4ac0d50..9aa2e6b 100644 --- a/prsctrl/utility/prsdata.py +++ b/prsctrl/utility/prsdata.py @@ -162,8 +162,11 @@ class PrsData: "dR": r"$\Delta R$ [V]", "R": r"$R$ [V]", "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: """ Return the spectral data for the specified keys and wavelengths.