From dc5df0ca2a6f76341b6d1ea20b29709b6ad76ffc Mon Sep 17 00:00:00 2001 From: reiche Date: Tue, 13 Jun 2023 14:18:09 +0200 Subject: [PATCH] Support for various app and slic wrapper --- app/__init__.py | 2 + app/adaptiveorbit.py | 8 +- app/dispersiontools.py | 175 +++++++++++++++++++++++++++++++++++++++ app/hero.py | 111 +++++++++++++++++++++++++ app/spectralanalysis.py | 5 +- ext/__init__.py | 4 +- ext/camacquisition.py | 41 +++++++++ ext/counteradjustable.py | 23 +++++ ext/magnet.py | 21 +++++ ext/reichebscombined.py | 12 --- interface/__init__.py | 1 + interface/save.py | 23 ++--- interface/slic.py | 93 +++++++++++++++++++++ 13 files changed, 488 insertions(+), 31 deletions(-) create mode 100644 app/dispersiontools.py create mode 100644 app/hero.py create mode 100644 ext/camacquisition.py create mode 100644 ext/counteradjustable.py create mode 100644 ext/magnet.py delete mode 100644 ext/reichebscombined.py create mode 100644 interface/slic.py diff --git a/app/__init__.py b/app/__init__.py index f986faa..ac9d226 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,2 +1,4 @@ from .adaptiveorbit import AdaptiveOrbit from .spectralanalysis import SpectralAnalysis +from .hero import LaserPower,EnergyModulation +from .dispersiontools import Dispersion diff --git a/app/adaptiveorbit.py b/app/adaptiveorbit.py index 286d4b6..0c74146 100644 --- a/app/adaptiveorbit.py +++ b/app/adaptiveorbit.py @@ -39,7 +39,7 @@ class AdaptiveOrbit: def initBSStream(self,channels): print("Initializing BSstream") - bs = BSCache() + bs = BSCache(100000,10000) # 1 second time out, capazity for 100 second. bs.stop() for cnl in channels[1:]: if not is_available(cnl): @@ -63,10 +63,8 @@ class AdaptiveOrbit: return pvs def flush(self): - with self.bsAR.pt.queue.mutex: - self.bsAR.pt.queue.queue.clear() - with self.bsAT.pt.queue.mutex: - self.bsAT.pt.queue.queue.clear() + self.bsAR.flush() + self.bsAT.flush() def terminate(self): print('Stopping BSStream Thread...') diff --git a/app/dispersiontools.py b/app/dispersiontools.py new file mode 100644 index 0000000..e6e4ef6 --- /dev/null +++ b/app/dispersiontools.py @@ -0,0 +1,175 @@ +import datetime +import re +import numpy as np + +from bsread import dispatcher +import epics + +from slic.core.adjustable import PVAdjustable +from slic.core.acquisition import BSAcquisition +from slic.core.scanner import Scanner + + +def getAux(pvs=None): + if not pvs: + return + ret={} + val = epics.caget_many(pvs) + for i,pv in enumerate(pvs): + if val[i]: # filter out None values + ret[pv]=float(val[i]) + epics.ca.clear_cache() + return ret + + +def getBSChannels(regexp): + prog = re.compile(regexp) + res = [] + for bs in dispatcher.get_current_channels(): + if prog.match(bs['name']): + res.append(bs['name']) + return res + + +class Dispersion: + def __init__(self, branch = 'Aramis'): + self.scanname = 'Dispersion' + self.branch = 'None' + dirpath= datetime.datetime.now().strftime('/sf/data/measurements/%Y/%m/%d/slic_sfbd') + self.scandir='%s/%s' % (dirpath,self.scanname) + + self.setBranch() + self.sc = None + self.Nsteps = 2 + self.Nsamples = 1 + + + def setBranch(self,branch = 'Aramis'): + if branch == 'Athos Dump': + self.setupAthosDump() + elif branch == 'Aramis': + self.setupAramis() + else: + self.branch = 'None' + + + def setupAramis(self): + # pre-scan item + self.pre = {} + self.pre['SFB_BEAM_DUMP_AR:ONOFF1']={'Val':0,'InitVal':0} + self.pre['SFB_BEAM_ENERGY_ECOL_AR:ONOFF1']={'Val':0,'InitVal':0} + self.pre['SFB_ORBIT_S30:ONOFF1']={'Val':0,'InitVal':0} + self.pre['SFB_ORBIT_SAR:ONOFF1']={'Val':0,'InitVal':0} + for pv in self.pre.keys(): + self.pre[pv]['adj']=PVAdjustable(pv) + # adjustable + self.adjSV = 'S30:SET-E-GAIN-OP' + self.adjRB = 'S30:GET-E-GAIN-OP' + self.adj = PVAdjustable(self.adjSV,pvname_readback = self.adjRB, accuracy = 0.1) + self.amp = 20 # the amplitude of the scan, which can be scaled + # acquisition + sensor1 = getBSChannels('SAR.*DBPM.*:[XY]1$') + sensor2 = getBSChannels('S[23].*-RLLE-DSP:.*-VS$') + self.sensor = sensor1+sensor2 + self.acq = [BSAcquisition("machine","sfbd", default_channels=self.sensor)] + # auxiliar data to be read one + self.aux=[] + for sen in sensor2: + if 'PHASE-VS' in sen: + self.aux.append(sen.replace('PHASE-VS','GET-VSUM-PHASE-OFFSET').replace('RLLE-DSP','RSYS')) + self.aux.append(sen.replace('PHASE-VS','GET-VSUM-AMPLT-SCALE').replace('RLLE-DSP','RSYS')) + self.aux.append(sen.replace('PHASE-VS','SM-SET').replace('RLLE-DSP','RMSM')) + self.aux.append('S10BC02-MBND100:ENERGY-OP') + + # scanner + self.branch='Aramis' + self.path = '%s-%s' % (self.scanname,self.branch) + self.scanner = Scanner(data_base_dir=self.path,scan_info_dir=self.path, + make_scan_sub_dir=True, + default_acquisitions=self.acq) + + def setupAthosDump(self): + # pre-scan item + self.pre = {} + self.pre['SFB_BEAM_DUMP_AT:ONOFF1']={'Val':0,'InitVal':0} + self.pre['SFB_ORBIT_SAT:ONOFF1']={'Val':0,'InitVal':0} + for i in range(1,5): + self.pre['SFB_ORBIT_SAT_%2.2d:ONOFF1' % i ]={'Val':0,'InitVal':0} + for pv in self.pre.keys(): + self.pre[pv]['adj']=PVAdjustable(pv) + # adjustable + self.adjSV = 'SATCB01-RSYS:SET-BEAM-PHASE' + self.adjRB = 'SATCB01-RSYS:GET-BEAM-PHASE' + self.adj = PVAdjustable(self.adjSV,pvname_readback = self.adjRB, accuracy = 0.1) + self.amp = 20 # the amplitude of the scan, which can be scaled + # acquisition + self.sensor = ['SATBD02-DBPM010:Y2','SATBD02-DBPM040:Y2'] + self.acq = [BSAcquisition("machine","sfbd", default_channels=self.sensor)] + # auxiliar data to be read one + aux = ['SATCL01-MBND100:ENERGY-OP'] + # scanner + self.branch='Athos_Dump' + self.path = '%s-%s' % (self.scanname,self.branch) + self.scanner = Scanner(data_base_dir=self.path,scan_info_dir=self.path, + make_scan_sub_dir=True, + default_acquisitions=self.acq) + + + + + def setup(self,scl = 1, Nsteps=5, Nsamples=5): + val = self.adj.get_current_value(readback=False) + dval = self.amp*scl + self.N = Nsteps + self.Ns= Nsamples + self.values=np.linspace(val-dval,val+dval,num=self.N) + + + def preaction(self): + for key in self.pre.keys(): + self.pre[key]['InitVal'] = self.pre[key]['adj'].get_current_value(readback = False) + self.pre[key]['adj'].set_target_value(self.pre[key]['Val']) + + def postaction(self): + for key in self.pre.keys(): + self.pre[key]['adj'].set_target_value(self.pre[key]['InitVal']) + + + def scan(self): + self.sc=self.scanner.ascan_list(self.adj,self.values, + filename=self.scanname,start_immediately = False, + n_pulses=self.Ns,return_to_initial_values=True) + self.preaction() + self.sc.run() + self.auxdata = getAux(self.aux) + self.postaction() + + + def stop(self): + if self.sc is None: + return + self.sc.stop() + + def running(self): + return self.sc.running + + def status(self): + si = self.sc.scan_info.to_dict() + steps = 0 + if 'scan_values' in si: + steps=len(si['scan_values']) + return steps,self.N + + def info(self): + return self.sc.scan_info.to_dict() + + + + + + + + + + + diff --git a/app/hero.py b/app/hero.py new file mode 100644 index 0000000..4f2ba5b --- /dev/null +++ b/app/hero.py @@ -0,0 +1,111 @@ +import datetime +import numpy as np + +from slic.core.acquisition import PVAcquisition +from slic.core.acquisition import BSAcquisition +from slic.devices.general import motor +from slic.core.scanner import Scanner +from sfbd.ext import CamAcquisition + +# some temporary wrapper +class PollingPVAcquisition(PVAcquisition): + def _acquire(self, *args, polling=True, **kwargs): + return super()._acquire(*args, polling=polling, **kwargs) + +class LaserScanBase: + def __init__(self): + print('Init Base Class') + self.SV= 'SSL-LMOT-M1104:MOT' + self.pol = motor.Motor(self.SV) + + def stop(self): + if self.sc is None: + return + self.sc.stop() + + def running(self): + return self.sc.running + + def status(self): + si = self.sc.scan_info.to_dict() + steps = 0 + if 'scan_values' in si: + steps=len(si['scan_values']) + return steps,self.N + + def info(self): + return self.sc.scan_info.to_dict() + + def setup(self,amax=21,Nsteps=5,Nsamples=5): + amin = 0 + self.N = Nsteps + self.Ns= Nsamples + self.values=np.linspace(19,21,num=self.N) # needs a change + +# measuring the pulse energy as a function of the controling PV. Note that the power should be limited to 300 uJ +# thus limiting the value of the actuaor defining the lase rpulse energy in the EnergyModulaiton class. + +class LaserPower(LaserScanBase): + def __init__(self): + super(LaserPower,self).__init__() + + self.scanname = 'HEROLaserEnergy' + dirpath= datetime.datetime.now().strftime('/sf/data/measurements/%Y/%m/%d/slic_sfbd') + self.scandir='%s/%s' % (dirpath,self.scanname) + + self.RB = 'SSL-LENG-SLNK1:VAL_GET' + self.erg = PollingPVAcquisition("machine","sfbd", default_channels=[self.RB]) + + self.scanner = Scanner(data_base_dir=self.scandir,scan_info_dir=self.scandir,make_scan_sub_dir=True, + default_acquisitions=[self.erg]) + + def scan(self): + self.sc=self.scanner.ascan_list(self.pol,self.values, + filename=self.scanname,start_immediately = False, + n_pulses=self.Ns,return_to_initial_values=True) + self.sc.run() + + + + +# measuring the coherent emission/space charge blow-up as a function of the hero energy modulation + +class EnergyModulation(LaserScanBase): + def __init__(self, acq = 0): + super(EnergyModulation,self).__init__() + self.scanname = 'HEROEnergyModulation' + dirpath= datetime.datetime.now().strftime('/sf/data/measurements/%Y/%m/%d/slic_sfbd') + self.scandir='%s/%s' % (dirpath,self.scanname) + + self.acq = acq + if self.acq == 0: + self.RB ='SATFE10-PEPG046-EVR0:CALCI' + self.erg = BSAcquisition("machine","sfbd", default_channels=[self.RB]) + elif self.acq == 1: + self.RB ='SATBD02-DBPM040:Y2' + self.erg = BSAcquisition("machine","sfbd", default_channels=[self.RB]) + elif self.acq == 2: + self.RB = 'SATBD01-DSCR210' + self.erg = CamAcquisition("machine","sfbd", default_channels=[self.RB]) + self.erg.getConnection(self.RB) + else: + self.RB = 'SATBD02-DSCR050' + self.erg = CamAcquisition("machine","sfbd", default_channels=[self.RB]) + self.erg.getConnection(self.RB) + + self.scanner = Scanner(data_base_dir=self.scandir,scan_info_dir=self.scandir,make_scan_sub_dir=True, + default_acquisitions=[self.erg]) + + def scan(self): + self.sc=self.scanner.ascan_list(self.pol,self.values, + filename=self.scanname,start_immediately = False, + n_pulses=self.Ns,return_to_initial_values=True) + self.sc.run() + + + + + + + + diff --git a/app/spectralanalysis.py b/app/spectralanalysis.py index 7d16478..4899cc7 100644 --- a/app/spectralanalysis.py +++ b/app/spectralanalysis.py @@ -11,7 +11,7 @@ class SpectralAnalysis: """ def __init__(self): - self.bs = BSCache() + self.bs = BSCache(100000,10000) # 100 second timeout, size for 100 second data taken self.bs.stop() self.channel = '' @@ -35,8 +35,7 @@ class SpectralAnalysis: self.bs.pt.running.clear() # for some reason I have to def flush(self): - with self.bs.pt.queue.mutex: - self.bs.pt.queue.queue.clear() + self.bs.flush() def read(self): data=self.bs.__next__() diff --git a/ext/__init__.py b/ext/__init__.py index ae07e6b..01b2db3 100644 --- a/ext/__init__.py +++ b/ext/__init__.py @@ -1 +1,3 @@ -from .reichebscombined import ReicheBSCombined +from .magnet import Magnet +from .camacquisition import CamAcquisition +from .counteradjustable import CounterAdjustable diff --git a/ext/camacquisition.py b/ext/camacquisition.py new file mode 100644 index 0000000..1f8d487 --- /dev/null +++ b/ext/camacquisition.py @@ -0,0 +1,41 @@ +from time import sleep +from tqdm import trange +import h5py + +from cam_server_client import PipelineClient +from cam_server_client.utils import get_host_port_from_stream_address +from bsread import source, SUB + + +from slic.core.acquisition.acquisition import Acquisition +class CamAcquisition(Acquisition): + + def getConnection(self,cam): + pipeline_client = PipelineClient() + cam_instance_name = str(cam) + "_sp1" + stream_address = pipeline_client.get_instance_stream(cam_instance_name) + self.host, self.port = get_host_port_from_stream_address(stream_address) + print(self.host,self.port) + + def _acquire(self, filename, channels=None, data_base_dir=None, scan_info=None, n_pulses=100, **kwargs): + print("my routine") + print("extra kwargs:", kwargs) + args = (filename, n_pulses, channels) + args = ", ".join(repr(i) for i in args) + print("acquire({})".format(args)) + print(f"dummy acquire to {filename}:") + +# stream_host,stream_port = getPipeLine(channels[0]) +# time.wait(1) + data= [] + with source(host=self.host, port=self.port, mode=SUB) as input_stream: + input_stream.connect() + for i in range(n_pulses): + print('Camera Images', i) + message = input_stream.receive() + data.append(message.data.data) + hid = h5py.File(filename,'w') + gid = hid.create_group(channels[0]) + for key in data[0].keys(): + gid.create_dataset(key, data = [rec[key].value for rec in data]) + hid.close() diff --git a/ext/counteradjustable.py b/ext/counteradjustable.py new file mode 100644 index 0000000..990a733 --- /dev/null +++ b/ext/counteradjustable.py @@ -0,0 +1,23 @@ +from slic.core.adjustable import Adjustable + +class CounterAdjustable(Adjustable): + def __init__(self, adjustable1, adjustable2): + self.adj1=adjustable1 + self.adj2=adjustable2 + self.ref_values() # implementation needs reference values to convert absolute scan to relative scan + + def ref_value(self): + self.val1 = self.adj1.get_current_value(readback = False) + self.val2 = self.adj2.get_current_value(readback = False) + + def set_target_value(self, value): + t1 = self.adj1.set_target_value(self.val1 + value) + t2 = self.adj2.set_target_value(self.val2 - value) + t1.wait() + t2.wait() + + def get_current_value(self): + return self.adj1.get_current_value() + + def is_moving(self): + return any([self.adj1.is_moving(),self.adj2.is_moving()]) diff --git a/ext/magnet.py b/ext/magnet.py new file mode 100644 index 0000000..2243b39 --- /dev/null +++ b/ext/magnet.py @@ -0,0 +1,21 @@ +from slic.core.adjustable import PVAdjustable +from slic.utils import typename + +class Magnet(PVAdjustable): + + def __init__(self,name): + self.name=name + pvsv='%s:I-SET' % name + pvrb='%s:I-READ' % name + tol = 0.075 + super().__init__(pvsv,pvname_readback=pvrb,accuracy=tol,internal=True) + + + + @property + def status(self): + return "Cycling" + + def __repr__(self): + tn = typename(self) + return f"{tn} \"{self.name}\" is {self.status}" diff --git a/ext/reichebscombined.py b/ext/reichebscombined.py deleted file mode 100644 index 1541705..0000000 --- a/ext/reichebscombined.py +++ /dev/null @@ -1,12 +0,0 @@ -from slic.core.sensor.bsmonitor import BSMonitor - -class ReicheBSCombined(BSMonitor): - - # Du brauchst kein extra init. BSMonitor tut schon das richtige... - - def _unpack(self, data): - # data ist ein dict mit allen Deinen Kanälen - pid = data["pid"] # der effektive Channel-Name der Pulse ID - # hier dein Code - # am Ende sollte eine Zahl rauskommen: - return data diff --git a/interface/__init__.py b/interface/__init__.py index a439ca0..3aa44be 100644 --- a/interface/__init__.py +++ b/interface/__init__.py @@ -2,3 +2,4 @@ from .snap import getSnap from .save import saveDataset from .load import loadDataset from .elog import writeElog +from .slic import SlicScan diff --git a/interface/save.py b/interface/save.py index 648a146..b6abdb0 100644 --- a/interface/save.py +++ b/interface/save.py @@ -50,7 +50,8 @@ def saveDataset(program,data,actuator=None,snap=None,analysis=None,figures=None) writeSnap(hid,snap) hid.close() - writeFigure(filename,figures) + if figures: + writeFigure(filename,figures) return filename @@ -83,6 +84,16 @@ def openDataset(program): def writeData(hid, data, scanrun=1): + # write the sensor raw value + for ele in data.keys(): + name=ele.split(':') + if len(name)>1: + dset=hid.create_dataset('scan_%d/data/%s/%s' % (scanrun, name[0], name[1]), data=data[ele]) + else: + dset=hid.create_dataset('scan_%d/data/%s' % (scanrun, name[0]), data=data[ele]) + dset.attrs['system'] = getDatasetSystem(name[0]) + dset.attrs['units'] = 'unknown' + # this part is obsolete - dimension should be given from the individual datasets if not 'pid' in data.keys(): return shape = data['pid'].shape @@ -95,15 +106,7 @@ def writeData(hid, data, scanrun=1): hid.create_dataset("scan_%d/method/samples" % scanrun,data=[nsam]) hid.create_dataset("scan_%d/method/dimension" % scanrun,data=[ndim]) hid.create_dataset("scan_%d/method/reducedData" % scanrun,data=[0]) # indicating that there is at least a 2D array for scalar data - # write the sensor raw value - for ele in data.keys(): - name=ele.split(':') - if len(name)>1: - dset=hid.create_dataset('scan_%d/data/%s/%s' % (scanrun, name[0], name[1]), data=data[ele]) - else: - dset=hid.create_dataset('scan_%d/data/%s' % (scanrun, name[0]), data=data[ele]) - dset.attrs['system'] = getDatasetSystem(name[0]) - dset.attrs['units'] = 'unknown' + def writeActuator(hid,act,scanrun=1): if not act: diff --git a/interface/slic.py b/interface/slic.py new file mode 100644 index 0000000..420f32e --- /dev/null +++ b/interface/slic.py @@ -0,0 +1,93 @@ +import h5py +import numpy as np +import time +from threading import Thread +from PyQt5.QtCore import QObject, pyqtSignal + +# to do: +# 1 - check if scan thread is running +# 2 - import of BSread data + +from sfbd.interface import getSnap + +class SlicScan(QObject): + + siginc = pyqtSignal(int, int) # signal for increment + sigterm = pyqtSignal(int) # signal for termination + sigsnap = pyqtSignal(bool) + + def __init__(self): + QObject.__init__(self) + self.clear() + + def clear(self): + self.daq = None + self.data = None + self.act = None + self.snap = None + + def start(self,daq,snap=False): + self.clear() + Thread(target=self.Tmonitor).start() + self.startSnap(snap) + + def startSnap(self,snap=False): + if not snap: + Thread(target=self.Tsnap).start() + + def Tsnap(self): + self.snap = getSnap() + self.sigsnap.emit(True) + + def Tmonitor(self): + mythread = Thread(target=self.Tscanner).start() + time.sleep(1) + ostep = -1 + while(self.daq.running()): + istep,nstep=self.daq.status() + if istep>ostep: + ostep=istep + self.siginc.emit(istep,nstep) + time.sleep(1) + if not mythread == None: # wait till scanning thread is done + mythread.join() + istep,nstep=self.daq.status() + self.siginc.emit(istep,nstep) + self.data,self.act = importSlicScan(self.daq.info()) + if hasattr(self.daq,'auxdata'): + self.data.update(self.daq.auxdata) + self.sigterm.emit(istep==nstep) + + def Tscanner(self): + self.daq.scan() + + def stop(self): + self.daq.stop() + + +def importSlicScan(scan_info): + if not isinstance(scan_info,dict): + return None,None + if not 'scan_files' in scan_info.keys(): + return None,None + sfiles = scan_info['scan_files'] + data = {} + for istep, sfile in enumerate(sfiles): + hid = h5py.File(sfile[0],'r') + for name, h5obj in hid.items(): + if isinstance(h5obj,h5py.Dataset): # pv channels + data[name] = addDatasetToData(data,name,h5obj) + elif isinstance(h5obj,h5py.Group): # bs read channels + if 'data' in h5obj: + data[name] = addDatasetToData(data,name,h5obj['data']) + actuator = {} + name = scan_info['scan_parameters']['name'][0] + actuator[name]=np.array(scan_info['scan_values']) + data[name]=np.array(scan_info['scan_readbacks']) + return data,actuator + +def addDatasetToData(data,name,h5obj): + if not name in data: + return np.array([h5obj[()]]) + else: + return np.append(data[name],np.array([h5obj[()]]),axis=0)