JohannesDittloff 7f7561e4d9 rename prsctrl
2025-05-08 13:07:22 +02:00

375 lines
15 KiB
Python

if __name__ == "__main__":
import sys
if __package__ is None:
# make relative imports work as described here: https://peps.python.org/pep-0366/#proposed-change
__package__ = "photoreflectance"
from os import path
filepath = path.realpath(path.abspath(__file__))
sys.path.insert(0, 'C:\\Users\Administrator\Desktop\Software\Python\Python\github')
from time import sleep
import pyvisa
import numpy as np
import scipy as scp
from Bentham import Bentham
from prsctrl.devices.lamp.impl.xenon import Xenon
from prsctrl.devices.shutter import Shutter
from prsctrl.devices.lamp.impl.xenon import Xenon
from .update_funcs import Monitor
from prsctrl.devices.lock_in.impl.sr830 import SR830
from prsctrl.devices.lock_in.impl.model7260 import Model7260
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] [%(name)s] %(message)s",
handlers=[
# logging.FileHandler(log_path),
logging.StreamHandler()
]
)
log = logging.getLogger(__name__)
def set_measurement_params(lockin: SR830, p: dict={}, **kwargs):
params = p | kwargs
key_to_setter = {
"time_constant_s": lockin.set_time_constant_s,
"filter_slope": lockin.set_filter_slope,
"sync_filter": lockin.set_sync_filter,
"reserve": lockin.set_reserve,
"sensitivity_volt": lockin.set_sensitivity_volt,
}
for k, v in params.items():
if k not in key_to_setter.keys():
raise KeyError(f"Invalid parameter {k}")
key_to_setter[k](v)
def set_offset_laser_only(lockin: SR830, shutter: Shutter, wait_time_s):
"""
Set the R offset from the signal when only the laser is on.
This signal should be stray laser light and laser induced PL
:return: Offset as percentage of the full scale R
"""
log.info("Setting offset when the lamp is off.")
shutter.close()
sleep(wait_time_s + 10)
lockin.run("AOFF 3") # auto offset R
R_offset_fs = float(lockin.query("OEXP? 3").split(",")[0]) # returns R offset and expand
return R_offset_fs
def _measure_both_sim(monochromator: Bentham, lockin: SR830, shutter: Shutter, wl_range=(400, 750, 25), aux_DC="Aux In 4", offset_with_laser_only=True, monitor=None):
data = {}
lockin_params = {
"time_constant_s": 10,
# "time_constant_s": 100e-3,
"sensitivity_volt": 50e-6,
"filter_slope": 12,
"sync_filter": 1,
"reserve": "Normal",
}
measurement_params = {
"measurement_time_s": 30,
"sample_rate_Hz": 512,
}
set_measurement_params(lockin, lockin_params)
measurement_time_s = measurement_params["measurement_time_s"]
sample_rate_AC = measurement_params["sample_rate_Hz"]
n_bins_AC = measurement_time_s * sample_rate_AC # x sec messen mit <sample_rate> werte pro sekunde
timeout_s = 60
timeout_interval = 0.5
# trigger on the falling edge, since the light comes through when the ref signal is low
# could of course also trigger on rising and apply 180° shift
lockin.run("RSLP 2")
# since we dont expect changes in our signal, we can use larger time constants and aggressive filter slope
# for better signal to noise
wait_time_s = lockin.get_wait_time_s()
def run_lockin_cmd(cmd, n_try=2):
com_success = n_try
e = None
while com_success > 0:
try:
return cmd()
except pyvisa.VisaIOError as e:
lockin.try_recover_from_communication_error(e)
com_success -= 1
raise e
# 5s for setting buffer,
# 5s for get values and plot
print(f"Time estimate {(measurement_time_s + wait_time_s + 5 + 5)/60 * ((wl_range[1]-wl_range[0])/wl_range[2])} minutes")
input("Make sure the laser is turned on and press enter > ")
mon = monitor if monitor is not None else Monitor(r"$\lambda$ [nm]", [
dict(ax=0, ylabel=r"$\Delta R$", color="green"),
dict(ax=1, ylabel=r"$\sigma_{\Delta R}$", color="green"),
dict(ax=2, ylabel=r"$R$", color="blue"),
dict(ax=3, ylabel=r"$\sigma_R$", color="blue"),
dict(ax=4, ylabel=r"$\Delta R/R$", color="red"),
dict(ax=5, ylabel=r"$\sigma_{\Delta R/R}$", color="red"),
])
mon.set_fig_title(f"Turn on laser and plug detector into A and {aux_DC} ")
data["lock-in-params"] = lockin_params
data["measurement-params"] = measurement_params
full_scale_voltage = lockin_params["sensitivity_volt"]
if offset_with_laser_only:
mon.set_fig_title(f"Measuring baseline with lamp off")
R_offset_volt = set_offset_laser_only(lockin, shutter, wait_time_s) * full_scale_voltage
data["R_offset_volt_before"] = R_offset_volt
print(f"R_offset_volt_before {R_offset_volt}")
data["reference_freq_Hz_before"] = lockin.get_frequency()
shutter.open()
for i_wl, wl in enumerate(range(*wl_range)):
mon.set_ax_title(f"$\\lambda = {wl}$ nm")
run_lockin_cmd(lambda: lockin.buffer_setup(CH1="R", CH2=aux_DC, length=n_bins_AC, sample_rate=sample_rate_AC))
mon.set_fig_title(f"Setting wavelength to {wl} nm")
monochromator.drive(wl)
mon.set_fig_title(f"Waiting for signal to stabilize")
# wait the wait time
sleep(wait_time_s)
mon.set_fig_title(f"Measuring...")
run_lockin_cmd(lambda: lockin.buffer_start_fill())
t = timeout_s
while t > 0:
t -= timeout_interval
sleep(timeout_interval)
if run_lockin_cmd(lambda: lockin.buffer_is_done()):
break
if t < 0: raise RuntimeError("Timed out waiting for buffer measurement to finish")
# ToDo Phase messen
arr = run_lockin_cmd(lambda: lockin.buffer_get_data(CH1=True, CH2=True))
data[wl] = {}
data[wl]["raw"] = arr
# calculate means
means = np.mean(arr, axis=1)
errs = np.std(arr, axis=1)
dR = means[0]
R = means[1]
sdR = errs[0]
sR = errs[1]
data[wl] |= {"dR": dR, "sdR": sdR, "R": R, "sR": sR}
dR_R = dR / R
sdR_R = np.sqrt((sdR / R) + (dR * sR/R**2))
data[wl] |= {"dR_R": dR_R, "sdR_R": sdR_R}
mon.update(wl, dR, sdR, R, sR, dR_R, sdR_R)
# if it fails, we still want the data returned
try:
if offset_with_laser_only:
mon.set_fig_title(f"Measuring baseline with lamp off")
R_offset_volt = set_offset_laser_only(lockin, shutter, wait_time_s) * full_scale_voltage
data["R_offset_volt_after"] = R_offset_volt
print(f"R_offset_volt_after {R_offset_volt}")
data["reference_freq_Hz_after"] = lockin.get_frequency()
except Exception as e:
print(e)
mon.set_fig_title("Photoreflectance")
mon.set_ax_title("")
return data, mon
def _measure_both(monochromator: Bentham, lockin: SR830, shutter: Shutter, wl_range=(400, 750, 25), AC=True, DC=True, monitor=None):
mon = monitor if monitor is not None else Monitor(r"$\lambda$ [nm]", [
# dict(ax=0, ylabel="Wavelength [nm]", color="red"),
# dict(ax=1, ylabel="Ref", color="blue", lim=(0, 5)),
dict(ax=0, ylabel=r"$\Delta R$", color="green"),
dict(ax=1, ylabel=r"$\sigma_{\Delta R}$", color="green"),
dict(ax=2, ylabel=r"$R$", color="blue"),
dict(ax=3, ylabel=r"$\sigma_R$", color="blue"),
dict(ax=4, ylabel=r"$\Delta R/R$", color="red"),
# dict(ax=3, ylabel="R", color="blue"),
dict(ax=5, ylabel=r"$\theta$", color="orange", lim=(-180, 180)),
dict(ax=6, ylabel=r"$\sigma_\theta$", color="orange")
])
shutter.open()
data_raw = []
data_wl = {}
# TODO these are only printed, not set!
time_constant = 30e-3
filter_slope = 24
sensitivity = 1.0
SYNC = 1
sample_rate_AC = 64
sample_rate_DC = 512
n_bins_AC = 3 * sample_rate_AC # x sec messen mit <sample_rate> werte pro sekunde
n_bins_DC = 10 * sample_rate_DC
timeout_s = 60
timeout_interval = 0.5
# lockin.run("SENS 17") # 1 mV
# lockin.run("SENS 20") # 10 mV
# lockin.run("SENS 21") # 20 mV
lockin.run("SENS 26") # 1 V
# trigger on the falling edge, since the light comes through when the ref signal is low
# could of course also trigger on rising and apply 180° shift
lockin.run("RSLP 2")
# since we dont expect changes in our signal, we can use larger time constants and aggressive filter slope
# for better signal to noise
# lockin.run("OFLT 5") # 3 ms
lockin.run("OFLT 7") # 30 ms
# lockin.run("OFLT 8") # 100 ms
# lockin.run("OFLT 10") # 1 s
lockin.run("RMOD 2") # low noise (small reserve)
# lockin.run("OFSL 0") # 6dB/Oct
lockin.run("OFSL 3") # 24dB/Oct
lockin.run(f"SYNC {SYNC}") # sync filter
print(f"Time estimate {40 * (wl_range[1]-wl_range[0])/(wl_range[2]*60)} minutes")
if AC:
input("Plug the detector into lock-in port 'A/I' (front panel) and press enter > ")
input("Make sure the laser is turned on and press enter > ")
mon.set_fig_title("Turn on laser and plug detector into A")
for i_wl, wl in enumerate(range(*wl_range)):
mon.set_ax_title(f"$\\lambda = {wl}$ nm")
lockin.buffer_setup(CH1="R", CH2="Theta", length=n_bins_AC, sample_rate=sample_rate_AC)
mon.set_fig_title(f"Setting wavelength to {wl} nm")
monochromator.drive(wl)
mon.set_fig_title(f"Waiting for signal to stabilize")
# wait time depends on filter and time constant, for 24dB/Oct and Sync on the minimum is ~12 time constants
sleep(1.0)
mon.set_fig_title(f"Measuring...")
lockin.buffer_start_fill()
t = timeout_s
while t > 0:
t -= timeout_interval
sleep(timeout_interval)
if lockin.buffer_is_done():
break
if t < 0: raise RuntimeError("Timed out waiting for buffer measurement to finish")
arr = lockin.buffer_get_data(CH1=True, CH2=True)
data_raw.append([wl, arr])
# calculate means, for theta use circular mean
dR = np.mean(arr[0,:])
sdR = np.std(arr[0,:])
theta = scp.stats.circmean(arr[1,:], low=-180, high=180)
stheta = scp.stats.circstd(arr[1,:], low=-180, high=180)
data_wl[wl] = {"dR": dR, "Theta": theta, "sdR": sdR, "sTheta": stheta}
# wl - dR, sdR, R, sR, dR/R, Theta
mon.update(wl, dR, sdR, None, None, None, theta, stheta)
if DC:
mon.set_ax_title("")
mon.set_fig_title("Turn off laser and plug detector into Aux 1")
input("Turn off the laser and press enter > ")
input("Plug the detector into lock-in port 'Aux In 1' (rear panel) and press enter > ")
for i_wl, wl in enumerate(range(*wl_range)):
mon.set_ax_title(f"$\\lambda = {wl}$ nm")
lockin.buffer_setup(CH1="Aux In 1", CH2="Theta", length=n_bins_DC, sample_rate=sample_rate_DC)
mon.set_fig_title(f"Setting wavelength to {wl} nm")
monochromator.drive(wl)
sleep(0.5)
mon.set_fig_title(f"Measuring...")
lockin.buffer_start_fill()
t = timeout_s
while t > 0:
t -= timeout_interval
sleep(timeout_interval)
if lockin.buffer_is_done():
break
if t < 0: raise RuntimeError("Timed out waiting for buffer measurement to finish")
arr = lockin.buffer_get_data(CH1=True, CH2=False)
if AC:
data_raw[i_wl].append(arr)
else:
data_raw.append([wl, arr])
means = np.mean(arr, axis=1)
errs = np.std(arr, axis=1)
if not wl in data_wl: data_wl[wl] = {}
data_wl[wl] |= {"R": means[0], "sR": errs[0]}
# wl - dR, sdR, R, sR, dR/R, Theta
if AC:
dR_R = data_wl[wl]["dR"] / data_wl[wl]["R"]
mon.override(wl, None, None, data_wl[wl]["R"], data_wl[wl]["sR"], dR_R, None, None)
else:
mon.update(wl, None, None, data_wl[wl]["R"], data_wl[wl]["sR"], None, None, None)
mon.set_fig_title(f"Time constant = {time_constant} s\nFilter slope = {filter_slope} dB/oct\nSync Filter = {SYNC}\nSensitivity = {sensitivity*1e3} mV")
mon.set_ax_title("")
return data_wl, data_raw, mon
def _measure(monochromator: Bentham, lamp: Xenon, lockin: SR830, shutter: Shutter, wl_range=(400, 750, 25)):
data = []
mon = Monitor(r"$\lambda$ [nm]", [
# dict(ax=0, ylabel="Wavelength [nm]", color="red"),
# dict(ax=1, ylabel="Ref", color="blue", lim=(0, 5)),
dict(ax=0, ylabel=r"$\Delta R$", color="green"),
dict(ax=1, ylabel=r"$\sigma_{\Delta R}$", color="green"),
dict(ax=2, ylabel="Phase", color="orange", lim=(-180, 180))
# dict(ax=3, ylabel="R", color="blue"),
])
N_bins = 100
dt = 0.01
i = 0
shutter.open()
if isinstance(lockin, SR830):
lockin.run("SENS 17") # 1 mV/nA
lockin.run("OFLT 5")
for wl in range(*wl_range):
arr = np.empty((N_bins, 2))
monochromator.drive(wl)
# lockin.auto_gain()
for j in range(N_bins):
dR, theta = lockin.snap(what="3,4")
arr[j,:] = (dR, theta)
i += 1
# sleep(dt)
means = np.mean(arr, axis=0)
errs = np.std(arr, axis=0)
mon.update(wl, means[0], errs[0], means[1])
data.append((wl, arr))
elif isinstance(lockin, Model7260):
# lockin.run("SEN 21") # 10 mV/nA
lockin.run("SEN 24") # 100 mV/nA
for wl in range(*wl_range):
monochromator.drive(wl)
lockin.buffer_setup(MAG=1, PHASE=1, ADC1=1, ADC2=1, interval_ms=5, length=1000)
lockin.buffer_start_fill()
timeout_s = 60
timeout_interval = 0.5
while timeout_s > 0:
timeout_s -= timeout_interval
sleep(timeout_interval)
if lockin.buffer_is_done():
break
arr = lockin.buffer_get_data()
length = arr.shape[0]
# for j in range(length):
# # wl, ref, dR, R, theta
# mon.update(i*length+j, wl, arr[j][3], arr[j][0], arr[j][2], arr[j][1])
mon.update_array(range(i * length, (i+1)*length), [wl for _ in range(length)], arr[:,3], arr[:,0], arr[:,2], arr[:,1])
data.append((wl, arr))
i += 1
shutter.close()
return data, mon
lockin = None
lamp = None
mcm = None
shutter = None
def measure(wl_range=(400, 500, 2)):
return _measure(mcm, lamp, lockin, shutter, wl_range=wl_range)
def measure_both(**kwargs):
return _measure_both(mcm, lockin, shutter, **kwargs)
def measure_both_sim(**kwargs):
return _measure_both_sim(mcm, lockin, shutter, **kwargs)
if __name__ == "__main__":
mcm = Bentham()
shutter = module_shutter.connect_device(module_shutter.TYPENAME_DAQ, "TAS Lamp Shutter")
# mcm.park()
lamp = Xenon()
lockin = SR830.connect_device(SR830.enumerate_devices()[0])
# lockin = Model7260.connect_device(Model7260.enumerate_devices()[0])
# mcm = DummyBentham()
# shutter = DummyShutter()