From 14ca15e96dc18a62d849896d28946a953200e455 Mon Sep 17 00:00:00 2001 From: JohannesDittloff Date: Fri, 9 May 2025 15:08:19 +0200 Subject: [PATCH] make proc sweep general --- prsctrl/prsctrl_interactive.py | 19 ++-- .../spectrum_sweeps.cpython-311.pyc | Bin 0 -> 5419 bytes .../sweep_frequency.cpython-311.pyc | Bin 4419 -> 0 bytes prsctrl/tests/spectrum_sweeps.py | 87 ++++++++++++++++++ prsctrl/tests/sweep_frequency.py | 65 ------------- prsctrl/utility/__init__.py | 25 +++++ .../__pycache__/__init__.cpython-311.pyc | Bin 213 -> 1364 bytes .../__pycache__/prsdata.cpython-311.pyc | Bin 26657 -> 26451 bytes prsctrl/utility/prsdata.py | 5 +- 9 files changed, 127 insertions(+), 74 deletions(-) create mode 100644 prsctrl/tests/__pycache__/spectrum_sweeps.cpython-311.pyc delete mode 100644 prsctrl/tests/__pycache__/sweep_frequency.cpython-311.pyc create mode 100644 prsctrl/tests/spectrum_sweeps.py delete mode 100644 prsctrl/tests/sweep_frequency.py 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 0000000000000000000000000000000000000000..7cfa9a9fb856b3619163cda9f0fd0ad2d4df43da GIT binary patch literal 5419 zcma(VTWs6bm6RxnvZ=RZ%Z{DUbt2bJY%J}H25u8Cjgz!_EsfD;&75kqNZE8KN+zi| z5-3b9HcT-DBpWhRKvO^%g3LoQz&``>HS}X2{UkjC<_H)FF!UpT9*X_+Yv)puO*u`m z%d2}X?>+aNbI;@6L;X1r=)s`;Qy*Sh2w>R1(MzZJ+MVyNLgx#N#aLXxa`+7#M}3#z zy5X{+`-azrKaN|z ztCBp+D9n|zYic5cx0xc~f}b0H-@Oja7nq7I18fx*ALw*Jl)XL>c3&xng?#4LBrm3A zPEmMqQMRb8#H|!LF;hyvUk0XRj#C&GxI&*mP)3aD@5M9I^B>8aB+s8_bG*pQio_^+ zX?~WIZz=i0{9Hb(tTGZef3>76<;4zI}+Cj7!B_T3+S2f;?3yeNVnCFu4VmIVJgle=q#x7&KL^$v(Clm6WQRUqGFE$KL(ke?8;T?dBu8%L{{L3UlhKF5JK za*>zHV;Fg|JBnkVU5dvl0T!NYkOkT~V-Z$tp3Y1ZreC*um_khhzKjLLIzjxYKjOZZ7+2si7*R##o%if9qo1 zS;uJsnh91(HMSh-P^$#RaW&{1xn+B?bkHdg>s6y)!jYy1q3v1^8ujkyU;n#Gy~1ar z`*GEn8c?I}f;jbmDMva|K`iZhk{V>)dnW4g9jcoeVEXqkv+nNMN*tv6zr0Bq-;<-8 zdpAY!vE!A5a@2vnPa?oLG!Kzm>8td^KKu8iQxh%wIIW|`)r8vD9(UV1q3dsS*`Y@b z{g4l{y_KXI+Ow)tV`{Pt68y-l7miujLS;bp&SLlRn_Oj3ncUmca(}0<4yZ|%dK!2P z9`a=Oth4ubpfaQmHIp4oC+zms%8=r~THaMibI`e_(^m((HC`E34>S*SW}Q$annTX_ zPM-~S@f+@ptAo3&vF`J=n>~M2Jb>9ht zV}N4XFnq_QPN8 ziz;-UgH1*|cuv@Dd7youdDuDM>A&JW`{Dc0#YWUYkR%F{fM4(3i`|yN!ISHY&e&1+ z#U><4EieMv9;R3Q}L0~dKZ=q!K7QrIOxJZ*LMMeZFB`5Ja0<*vg z({u~k`a`JuMRqP(U1AiF12o;zRhuj_8R#z~ag4&zw(?r5Bu*(xkkQ(2SIY5^#2lk! zmO#diwM(=FzKNBi=i6Gd4QJ~I()b|KP}=sO0;4RI51qYs?dR9%xh1B+(K%kWso9d- zwm4aKP19w99yv+BKuK#kb}nBO!HdC&0w*piOY$`R0`VJny+9eCgsx!py#k7s`7&4b zv~us>!}D#co&`(c50Z}mF1 zAfbHLB5rXd8NLCKDlC^+usmF@pp-0+#E6T)jT4JGh-rnh+yz0gNV&LR8wU0&Fp2^k zlPs5I-~`xXmB|amoCs7Q@VGl@P0RJr=561y{J=Gh980!(;6p6k@|{+{Wc!olwE@x% z1d-)OShoLKl)^7ADQSTzK>%7l6j`A5aoQqn10y~8p=m|6)VZuimSyRT`h@p)n&grjf_jlZe`-#z6D5PNfVgRm06*Y6o-sr%Wob zMGb9ILx4_=8Pu3&*Keta7Cp9(8mKxoX;71zUAKFKwbI>F^;3=0o4vzYr0s^R)di%`*>|8*Z~VTszr3^5|&eNaM)!P`u%J*t;1T z)k34&;e^)rt7jB6P`~lCpf>jlWA4*dUk@`zn9;(F8BRVY6I%`Y9|43@DUC;+E8Uq(KqR%8E zTSVU`(WethgGg#bayt;c|5hXN;Kbc@J-vguJa5|#GXHQRr^k;O@nbs}?uTJ$?#|Xu z*H+Alsr8koV;dtIr|oC-=`oEu*jQyof9$sA-j4M> zc(>_Y_dfG#u^Bx!W5j0ek~Md2&J0HG-CDbKH&@RAT6DmOzOfZOu^Bz_ET%_K8qt$A zpGo%Kqt>YV^IM7W&BXZn*x%kZ-kkk<&Uo_!J#oQET+qpj26<5bPcC zkbR3xZjwnbw_#fx?WT9F_mA(jgS20pIJt2KnpV~0r;PY1ot!bq8I7EQ&Nkt%P43zq zOb=B;{|k=ySpDt4u%?O%FF;Z-1@Gu&vu_`%50E7Ep<#L83MrJN6zWId=0XR$z%Rgm zT=voKZ9!=QEkH-oUoe}4eQ3eIV7WY76u48;O_)Nrv0Q@&yadNh?5NgVP0U}lA4eyu vVeM*ShqUgxLt?R{8IGF+*R_G`X5yF`8ZZOLHM{PFuHyK>4)$ZME$RONzz9DA literal 0 HcmV?d00001 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 37d479b70e20ffe0301cedc96bf5fdfeedf9e3d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4419 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 Hl?~&6SYH&c 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 23ea318eb50a209a1546e761cc11b6cf6e19d338..658c0088396ae5f91add1c12a573b57b5a6f570f 100644 GIT binary patch literal 1364 zcmZ`(-HY2s5MO=SvaM@;P12UoG^!6m4at#f;f@5?aGXggG$rk|lmr!{c-OW#Su(q; z1|Kr`p$}?EsZ&~F2nCaeG$r)CZ~Yg}a1TasPkp*K!@b>0Gg@2MT%dzAJ2Sg8^PACV ze_vY4BcLDuzSzE|AoNe7q@*r{qxV2~j9Lhzr_x=7B_d&&$W0Y1->Y{OtZt*SR#wY$ zEMaHxg@h8R?G)cYLh2k=z(29ELkXl<#{spu<9l(25*|>mk(g7`Kma7@s07x>kgX5d z`f`Y{vLg^t8LOUzwZ6h%@#Bflf>^S>G4Kq&=)_+rWmmr6X zbA2gHVx9#}eOL|UaoYd?0D|-k-WMkp=zQM33WerPpe^6~j!W$Ew(@wEcjxYTK-I953L6RXb*7;az1Vk>Y-vP-6O? z+Yon>^XSaM=D zX;5q=yuRpYEOC|t@1$Ts$IjLb>uUzRvNkb%0S9u}+^1HJupRDqt!=-_??aER+dba) zy>w|gydB)Lx@{j0lcq}=-1Zv8>QdI=)U^WcxDM}G^*Y4l_4;bJ7r&V{q|W{Nd?x5c z0Fw+V`wQ3r%`Tub=LVll_55IST6|};_OmfGqP1vkTpH94if4b_8tIR2{c>wkEbSLd zgHI1uuKamtva+$ivN1!NRuXKYUy7tj;Zjr^UO6z%40EH;CdCgXMtR>TN7A%l45bI_ z(faV4Xnm|cSDqHeg{iSLtc{i*-P~Kg49|$qSKt%lap}#emWS3ve?Q7aISBI8 z#)FL?u0OoKr=B|k?CA5dO5ry`u_op!R-i&r-XDl2cNu*b*s)&e_&9J$m6kwFE(?}i hR5L}Aq^VKe6VD(wli!xkkI&B#EYCj@%l|SF{Ur$AY5f2I delta 123 zcmcb@b(PU^IWI340}%8^NoRoQ#~=a*7@>^MY(U0zh7^Wi22Do4l?+8pK>lZtv2; z|9Q@Hp8K4|uh8`sl>T~p`X&SWrCfE4ynjADf{eEjeHJ-U6J0=OX7LtWirskYg4Lge zn{lRS8_fne{DMZjEzbFK@Ge{)NB&&gf-BgX$JR===Fb~~2v^0eemky?8~mGb4PybW zWh}&XjQ5L0Y%jMlC$@-Y{D9~PT5x@wJ6phxd0RgW;4juUY!^BFAk8ur*c+JSzY3@5 z!rSR?7Hf=~?~0YM-8&#wx$~abmXHB^&mdOtL#*`DdBYU)yZa2DR`os8uf?-7dF~-O z9E$LPgF_vnr0_0Zt?!U~#(t0cklQu5Z~sijdcddupUh=tuDgbm5mDipf9c$QkCpVW z5a|c_0S5qk00V$QfF-~vnCP|QT=hcf5fge#Z7++W>@Wn=7(7PO2zG=nmzSZ7>dkTm zA&EX+QEDHDFsFw#YcMQ{6TC9Qgs)b#qh&q=MFsJCr`iD_M{igm;BZ||N26w{-9Vm_g z<^eEx$@13@e-0dLY04U#&`+w+xCm)DZ*?`K(Ksv)PlkvB!aIrd7MPM3Slb@(?nF1$ z(_Y1#^QOUL(u_5AT8c0(Mfr%xZeQBcimX&4aplOV^mln4%tiPN%%#xxYu zjALV(F%~0DpbF8F*24TKGiNgQ61js{_?nQY1lYbm)ZW(bP_hLP`eku}`dFK4LMJKe zEVk*@`rrt2FdW5Xf!1~Au4}tH2T_iCw(}RxlDri?-Q(DX;k?ZNJ0PFIV__9aa?lkK>z2tMGEr7EGAk!PZqPz`V2d5V> zLZh8_TG!`718Q&Igc-d-m!9e>g0i$tGQ0DX#1D&-W|b!=l&~U+BuL%;r_c=jp#Pt& zQ6^&}2}_q}D>+Or4^+(_g}l1}ZGd*bBLESw51;~`V9-+8HweJDlF2YQKL+fDNJfad`(0$yU!?vt|>UI_~kQ5hM>#0`NceZlW)_#DFNfq)?K zvMh*_6bN__nPpxXG3+MIptY{U27wOs2Y)XrOxQe$Oiv=inaK96+EOzts|JR-uGL(1 zc<5(@sM>wtYiONQD9>RJ?CzIN9~GR#dRz4L`i0(wgY>WeAy^jx9|52Z8pp;*oFYr$ zxCGGqauKX$22WP2S~3D>eNzU=DP^Ip(~(-B;*x92LM`i+u9r%$CHxSLT=H& ZgNt>9LpiI4|L1SI|M!j2QMwZ9`xmi!y;J}I delta 1971 zcmZ{kYitx%6oBW>&TL<_EN!I)TUuy=E`87iT8b=7k*2}cAca_D3#`+#MAXh}gBP~IZ;19^Cs3EcVKrzIW2#HQYG%-9=5XaCo z){>ITMiyuFHwbU>nDY>5{bIR-;C0|-cq+->o-?5hh`1y!dvozqcy1hdpTNuUd_GR+ zV;LXw5<&oBXFS!Lj~B!R?+jedITKfKK8Y(i3rHc4l_%%Kb|T^aDl#iz#|z`~zsv9z zSpn5V#xDM!N~RErNC9!iL`4FRTJ%U%F`11a=5hWKUL2SH^OZSk_-B^0VN#* zu04(TY?tzRmnMW>$rGK2nv0|mb}fP zAnZoFMJ)88-8ksi{gF_YC)Sp|j%p2xu2el#LMJq+Yp*bx z)s%cLF1g^kpd@+@yAnN#p3(fOp_ZW?Lv)Cqd7i(FKNHThfd~6-Y+7TkA(rg7p|eI} zUKnNX;A)z|;S^~t$WfMBHXmI!s>^hQboQpRB(Dd|vSl_?13{H^Df%|<_`P#A8Zk;1 ze2em40vBmK1ElE(>|xmzmFTKbU$GXU>&BtVWeANL*Q$<+D8YQLRT(kx+6Cxi=Urv? zJs|F7-?^Sfw~T^Ce~9QH%c&`?ndAo^hX98GaE6lQkAM6XP>iQhYnG!QjJIosAx%?V zWhJCBWlY+m9kdS|A11O|U`tM5Jp4>u9lB%OuG6JF>gV208^w?y#UD|_N{H|d;1w&8 z%+9PBtbPO1MZs+IYu#o_2>Uw8PK^@K2S6`&cA8>$H(d^z2;1tJIlY^Uv8fZQ-9dC8 zkNwRU^xQ_tRz!^*_1A6a1oJe^Dz_?L52>U;FpTMGNVE>#Q`Crp^>FY+@FCTdFHW~K+VU|7?Yu%DQ+9_>;IRUuP8AP+E|!)fOeQMF_~Xb!!=@|!)V z*4Ws5M@H|m`j%5gFN5A%n`0oo26*r}`X2kcrO<6vIU6jn&b&ewA50mxk@j%;=%jT6 z-(`R<*1CQvYBdJecS-0ScE7c;04g=pHQuRSRcR-xnW{y*^q|fs5oXH9qo{}dx$(Y( zayP!AVQ6&DqX(FMQ$_zEfn#su*c3s|NL~;h)PC&# z$o-+`vWLIl$ZERDDK&8y(b(7aGeXCW&Mh~gv-Ry|B7KqVYADl=(I!z4R(5J_A@?`V^!uIGm2;0^j1YX(yCMs7AkJf3+9duYu23tjJ%E z2y6B)m4D)jea3))nrI&Z+aY#}6xt7f7-LsR`D$ygSv$3^SW#YZ|e`~xWi1Q7rL 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.