From 6226a39e82241cd3351818f0f3a6b51e0d83a972 Mon Sep 17 00:00:00 2001 From: lemke_h Date: Thu, 30 Nov 2017 18:19:25 +0100 Subject: [PATCH 1/2] lxt, scan --- devices_general/detectors.py | 6 +++--- instruments/bernina.py | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/devices_general/detectors.py b/devices_general/detectors.py index 24d12e9..dc36270 100755 --- a/devices_general/detectors.py +++ b/devices_general/detectors.py @@ -14,10 +14,10 @@ from threading import Thread from ..acquisition.utilities import Acquisition try: - import sys + import sys, os tpath = os.path.dirname(__file__) - sys.path.insert(0,os.path.join(tpath,'../../detector_integration_api') - sys.path.insert(0,os.path.join(tpath,'../../jungfrau_utils') + sys.path.insert(0,os.path.join(tpath,'../../detector_integration_api')) + sys.path.insert(0,os.path.join(tpath,'../../jungfrau_utils')) from detector_integration_api import DetectorIntegrationClient except: print('NB: detector integration could not be imported!') diff --git a/instruments/bernina.py b/instruments/bernina.py index 31281cd..e5daa13 100755 --- a/instruments/bernina.py +++ b/instruments/bernina.py @@ -93,3 +93,7 @@ bsdaqJF = JF_BS_writer('bsdaqJF') scansJF = _scan.Scans(data_base_dir='/sf/bernina/config/com/data/scan_data',scan_info_dir='/sf/bernina/config/com/data/scan_info',default_counters=[bsdaqJF]) scans = _scan.Scans(data_base_dir='/sf/bernina/config/com/data/scan_data',scan_info_dir='/sf/bernina/config/com/data/scan_info',default_counters=[bsdaq]) + +from ..timing.lasertiming import Lxt as _Lxt + +lxt = _Lxt() From 7bdd19e4de21142c21d68bc6379a9845f97920a1 Mon Sep 17 00:00:00 2001 From: lemke_h Date: Thu, 30 Nov 2017 18:23:26 +0100 Subject: [PATCH 2/2] added lxt, scan whould work also with newst versions of jfutils --- devices_general/utilities.py | 28 ++++ timing/lasertiming.py | 248 +++++++++++++++++++++++++++++++++++ 2 files changed, 276 insertions(+) create mode 100644 devices_general/utilities.py create mode 100644 timing/lasertiming.py diff --git a/devices_general/utilities.py b/devices_general/utilities.py new file mode 100644 index 0000000..1c965b4 --- /dev/null +++ b/devices_general/utilities.py @@ -0,0 +1,28 @@ +from threading import Thread + + +class Changer: + def __init__(self, target=None, parent=None, changer=None, hold=True, stopper=None): + self.target = target + self._changer = changer + self._stopper = stopper + self._thread = Thread(target=self._changer,args=(target,)) + if not hold: + self._thread.start() + + def wait(self): + self._thread.join() + + def start(self): + self._thread.start() + + def status(self): + if self._thread.ident is None: + return 'waiting' + else: + if self.thread.isAlive: + return 'changing' + else: + return 'done' + def stop(self): + self._stopper() diff --git a/timing/lasertiming.py b/timing/lasertiming.py new file mode 100644 index 0000000..b81e81e --- /dev/null +++ b/timing/lasertiming.py @@ -0,0 +1,248 @@ +from epics import PV +import os +import numpy as np +import time +from ..devices_general.utilities import Changer + +_basefolder = "/sf/bernina/config/par/lasertiming" + +def timeToStr(value,n=12): + fmt = "%%+.%df" % n + value = fmt%value + #print(value) + idx_point = value.find(".") + ret_str = value[:idx_point] + " ." + ngroups = (len(value)-idx_point)//3 + for n in range(ngroups): + ret_str += " %s" % value[idx_point+1+3*n:idx_point+1+3*(n+1)] + #print(idx_point+1+3*n,idx_point+1*3*(n-1),ret_str) + return ret_str + +def niceTimeToStr(delay,fmt="%+.0f"): + a_delay = abs(delay) + if a_delay >= 1: + ret = fmt % delay + "s" + elif 1e-3 <= a_delay < 1: + ret = fmt % (delay*1e3) + "ms" + elif 1e-6 <= a_delay < 1e-3: + ret = fmt % (delay*1e6) + "us" + elif 1e-9 <= a_delay < 1e-6: + ret = fmt % (delay*1e9) + "ns" + elif 1e-12 <= a_delay < 1e-9: + ret = fmt % (delay*1e12) + "ps" + elif 1e-15 <= a_delay < 1e-12: + ret = fmt % (delay*1e12) + "fs" + elif 1e-18 <= a_delay < 1e-15: + ret = fmt % (delay*1e12) + "as" + else: + ret = str(delay) +"s" + return ret + + +class Storage(object): + def __init__(self,pvname): + self._filename = os.path.join(_basefolder,pvname) + self.pvname = pvname + self.last_read_time = -1 + + @property + def last_modified_time(self): + if os.path.isfile(self._filename): + return os.stat(self._filename).st_mtime + else: + return -1 + + @property + def value(self): + lmod = self.last_modified_time + if os.path.isfile(self._filename): + # need to read again ? + if self.last_read_time == -1 or lmod > self.last_read_time : + #print("actually reading") + value = float(np.loadtxt(self._filename)) + self.last_read_time = lmod + self.last_read = value + else: + value = self.last_read + else: + print("could not read",self._filename) + value = 0 + return value + + def store(self,value): + with open(self._filename,"w") as f: + f.write("# %s\n"%time.asctime()) + f.write("%.15f"%value) + + +class Pockels_trigger(PV): + """ this class is needed to store the offset in files and read in s """ + def __init__(self,pv_basename): + pvname = pv_basename + "-RB" + PV.__init__(self,pvname) + self._pv_setvalue = PV(pv_basename + "-SP") + self._filename = os.path.join(_basefolder,pvname) + self._storage = Storage(pvname) + + @property + def offset(self): return self._storage.value + + def get_dial(self): + return np.round(super().get()*1e-6,9) + + def get(self): + """ convert time to sec """ + return self.get_dial()-self.offset + + def store(self,value=None): + if value == None: value = self.get_dial() + self._storage.store( value ) + + def move(self,value): + dial = value + self.offset + self._pv_setvalue.put(dial*1e6) + + def set(self,value): + newoffset = self.get_dial()-value + self.store(newoffset) + + + + def __repr__(self): + dial = timeToStr( self.get_dial(),n=12 ) + user = timeToStr( self.get(),n=12 ) + return "Pockel Trigger PV: %s user , dial = %s, %s"%(self.pvname,user,dial) + +_OSCILLATOR_PERIOD = 1/71.368704e6 + +class Phase_shifter(PV): + """ this class is needed to store the offset in files and read in ps """ + def __init__(self,pv_basename="SLAAR01-TSPL-EPL",dial_max=14.05e-9): + pvname = pv_basename+":CURR_DELTA_T" + PV.__init__(self,pvname) + self._filename = os.path.join(_basefolder,pvname) + self._pv_setvalue = PV(pv_basename + ":NEW_DELTA_T") + self._pv_execute = PV(pv_basename + ":SET_NEW_PHASE.PROC") + self._storage = Storage(pvname) + self.dial_max = dial_max + + @property + def offset(self): return self._storage.value + + + def get_dial(self): + return super().get()*1e-12 + + def get(self): + """ convert time to sec """ + return self.get_dial()-self.offset + + def store(self,value=None): + if value == None: value = self.get_dial() + self._storage.store( value ) + + def move(self,value): + dial = value + self.offset + dial = np.mod(dial,_OSCILLATOR_PERIOD) + if dial > self.dial_max: dial = self.dial_max + dial_ps = dial*1e12 + self._pv_setvalue.put(dial_ps) + time.sleep(0.1) + self._pv_execute.put(1) + while( np.abs(self.get_dial()-dial) > 100e-15 ): time.sleep(0.2) + + def set(self,value): + newoffset = self.get_dial()-value + newoffset = np.mod(newoffset,_OSCILLATOR_PERIOD) + self.store(newoffset) + + + def __repr__(self): + dial = timeToStr( self.get_dial(),n=15 ) + user = timeToStr( self.get(),n=15 ) + return "Phase Shifter: user,dial = %s , %s"%(user,dial) + + +_slicer_gate = Pockels_trigger("SLAAR-LTIM01-EVR0:Pul2-Delay") +_sdg1 = Pockels_trigger("SLAAR-LTIM01-EVR0:Pul3-Delay") +_phase_shifter = Phase_shifter("SLAAR01-TSPL-EPL") + + +_POCKELS_CELL_RESOLUTION = 7e-9 + + +class Lxt(object): + def __init__(self): + self.sdg1 = _sdg1 + self.slicer_gate = _slicer_gate + self.phase_shifter = _phase_shifter + self.Id = 'SLAAR01-TSPL-EPL' + self.name = 'lxt' + self.elog = None + + def move_sdg(self,value): + self.sdg1.move(value) + + def move(self,value): + self.sdg1.move(-value) + self.slicer_gate.move(-value) + self.phase_shifter.move(value) + + def set(self,value): + self.phase_shifter.set(value) + self.sdg1.set(-value) + + def get(self): + # pulses are at SOME_IDX*OSCILLATOR_PERIOD-PHASESHITER + # the -PHASESHITER is due to the inverted sign + phase_shifter = self.phase_shifter.get() + sdg1_delay = self.sdg1.get() + + idx_pulse = (sdg1_delay+phase_shifter)/_OSCILLATOR_PERIOD + + delay = int(idx_pulse)*_OSCILLATOR_PERIOD - phase_shifter + return -delay + + def changeTo(self, value, hold=False): + """ Adjustable convention""" + + changer = lambda value: self.move(\ + value) + return Changer( + target=value, + parent=self, + mover=changer, + hold=hold, + stopper=None) + + + def get_current_value(self): + return self.get() + + def set_current_value(self,value): + self.set(value) + + def changeTo(self, value, hold=False): + """ Adjustable convention""" + + changer = lambda value: self.move(\ + value) + return Changer( + target=value, + parent=self, + changer=changer, + hold=hold, + stopper=None) + + + def get_current_value(self): + return self.get() + + def set_current_value(self,value): + self.set(value) + + def __repr__(self): + delay = niceTimeToStr(self.get()) + return "delay = %s"%(delay) + +