Coverage for /Users/Newville/Codes/xraylarch/larch/epics/spyk.py: 0%
211 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
1#!/usr/bin/env python
2"""
3Spyk is a Spec emulator, providing Spec-like scanning functions to Larch
5 spec = SpykScan()
6 spec.add_motors(x='XX:m1', y='XX:m2')
7 spec.add_detector('XX:scaler1')
8 spec.set_scanfile(outputfile)
10 spec.ascan('x', start, finish, npts, time)
11 spec.a2scan('x', s1, f1, 'y', s1, f1, npts, time)
12 spec.a3scan('x', s1, f1, 'y', s2, f2, 'z', s3, f3, npts, time)
13 spec.mesh('x', s1, f1, npts1, 'y', s2, f2, npts2, time)
15 spec.lup('x', start, finish, npts, time)
16 spec.dscan('x', start, finish, npts, time)
17 spec.d2scan('x', s1, f1, 'y', s1, f1, npts, time)
18 spec.d3scan('x', s1, f1, 'y', s2, f2, 'z', s3, f3, npts, time)
20yet to be implemented:
21 -- th2th tth_start_rel tth_finish_rel intervals time
22 -- automatic plotting
23 -- save/read configuration
24"""
25import os
26import sys
27import time
28import numpy as np
29from io import StringIO
30from configparser import ConfigParser
33from larch import Group
35from larch.utils import get_homedir
36from larch.io import get_timestamp
38try:
39 from epics import PV, caget, caput, get_pv, poll
40 HAS_EPICS = True
41except ImportError:
42 HAS_EPICS = False
44try:
45 from .larchscan import LarchStepScan
46 import epicsscan
47 from epicsscan.positioner import Positioner
48 from epicsscan.detectors import get_detector, Counter
49 HAS_EPICSSCAN = True
50except ImportError:
51 HAS_EPICSSCAN = False
54class SpykConfig(object):
55 """
56 Configuration file (INI format) for Spyk scanning
57 """
58 LEGEND = '# index = label || PVname'
59 DET_LEGEND = '# index = label || DetectorPV || options '
60 SPYK_DIR = '.spyk'
61 SPYK_INI = 'spyk.ini'
63 __sects = ('setup', 'motors', 'detectors', 'extra_pvs', 'counters')
65 def __init__(self, filename=None, text=None):
66 for s in self.__sects:
67 setattr(self, s, {})
68 self._cp = ConfigParser()
69 self.filename = filename
70 if self.filename is None:
71 cfile = self.get_default_configfile()
72 if (os.path.exists(cfile) and os.path.isfile(cfile)):
73 self.filename = cfile
74 if self.filename is not None:
75 self.Read(self.filename)
77 def get_default_configfile(self):
78 return os.path.join(get_homedir(), self.SPYK_DIR, self.SPYK_INI)
80 def Read(self, fname=None):
81 "read config"
82 if fname is None:
83 return
84 ret = self._cp.read(fname)
85 if len(ret) == 0:
86 time.sleep(0.25)
87 ret = self._cp.read(fname)
88 self.filename = fname
89 # process sections
90 for sect in self.__sects:
91 if not self._cp.has_section(sect):
92 continue
93 thissect = {}
94 for opt in self._cp.options(sect):
95 val = self._cp.get(sect, opt)
96 if '||' in val:
97 words = [i.strip() for i in val.split('||')]
98 label = words.pop(0)
99 if len(words) == 1:
100 words = words[0]
101 else:
102 words = tuple(words)
103 thissect[label] = words
104 else:
105 thissect[opt] = val
106 setattr(self, sect, thissect)
108 def Save(self, fname=None):
109 "save config file"
110 if fname is None:
111 fname = self.get_default_configfile()
112 path, fn = os.path.split(fname)
113 if not os.path.exists(path):
114 os.makedirs(path, mode=755)
116 out = ['###Spyke Configuration: %s' % (get_timestamp())]
117 for sect in self.__sects:
118 out.append('#-----------------------#\n[%s]' % sect)
119 if sect == 'setup':
120 for name, val in self.setup.items():
121 out.append("%s = %s" % (name, val))
122 elif sect == 'detectors':
123 out.append(self.DET_LEGEND)
124 idx = 0
125 for key, val in getattr(self, sect).items():
126 idx = idx + 1
127 if isinstance(val, (list, tuple)):
128 val = ' || '.join(val)
129 out.append("%i = %s || %s" % (idx, key, val))
131 else:
132 out.append(self.LEGEND)
133 idx = 0
134 for key, val in getattr(self, sect).items():
135 idx = idx + 1
136 if isinstance(val, (list, tuple)):
137 val = ' || '.join(val)
138 out.append("%i = %s || %s" % (idx, key, val))
139 out.append('#-----------------------#')
140 with open(fname, 'w') as fh:
141 fh.write('\n'.join(out))
143 def sections(self):
144 return self.__sects.keys()
147class Spyk(Group):
148 """Spyk is a set of Spec-like scanning tools for Larch"""
149 def __init__(self, filename='spykscan.001', configfile=None,
150 auto_increment=True, _larch=None, **kwargs):
151 Group.__init__(self, **kwargs)
152 self.motors = {}
153 self.detectors = []
154 self.bare_counters = []
155 self._scan = LarchStepScan(filename=filename,
156 auto_increment=auto_increment,
157 _larch=_larch)
158 self.datafilename = filename
159 if configfile is not None:
160 self.configfile = configfile
161 self.read_config(filename=configfile)
162 self.lup = self.dscan
164 def read_config(self, filename=None):
165 "read Spyk configuration file"
166 self.config = SpykConfig(filename=filename)
167 self.configfile = self.config.filename
168 for label, pvname in self.config.motors.items():
169 self.motors[label] = Positioner(pvname, label=label)
171 for label, pvname in self.config.counters.items():
172 self.bare_counters.append(Counter(pvname, label=label))
174 self.add_extra_pvs(self.config.extra_pvs.items())
175 for label, val in self.config.detectors.items():
176 opts = {'label': label}
177 prefix = val[0]
178 if len(val) > 1:
179 pairs = [w.strip() for w in val[1].split(',')]
180 for s in pairs:
181 skey, sval = s.split('=')
182 opts[skey.strip()] = sval.strip()
183 self.add_detector(prefix, **opts)
185 def save_config(self, filename=None):
186 "save Spyk configuration file"
187 print( 'save spyk config....')
189 def add_motors(self, **motors):
190 """add motors as keyword=value pairs: label=EpicsPVName"""
191 for label, pvname in motors.items():
192 self.motors[label] = Positioner(pvname, label=label)
194 def add_counter(self, name, label=None):
195 # self._scan.add_counter(name, label=label)
196 self.bare_counters.append(Counter(name, label=label))
198 def add_detector(self, name, kind=None, **kws):
199 "add detector, giving base name and detector type"
200 self.detectors.append(get_detector(name, kind=kind, **kws))
202 def add_extra_pvs(self, extra_pvs):
203 """add extra PVs to be recorded prior to each scan
204 extra_pvs should be list or tuple of (label, PVname)
205 """
206 self._scan.add_extra_pvs(extra_pvs)
208 def set_scanfile(self, filename):
209 "set file name"
210 self.datafilename = filename
212 def _checkmotors(self, *args):
213 "check that all args are motor names"
214 for mname in args:
215 if mname not in self.motors:
216 raise Exception("Error: unknown motor name '%s'" % mname)
218 def _run(self, dwelltime):
219 """internal function to start scans"""
220 self._scan.counters = []
221 self._scan.triggers = []
222 self._scan.dwelltime = dwelltime
223 for d in self.detectors:
224 self._scan.add_detector(d)
225 d.dwelltime = dwelltime
226 for c in self.bare_counters:
227 self._scan.add_counter(c)
229 self._scan.run(filename=self.datafilename)
231 def ascan(self, motor, start, finish, npts, dtime):
232 "ascan: absolute scan"
233 self._checkmotors(motor)
234 self._scan.positioners = [self.motors[motor]]
235 self._scan.positioners[0].array = np.linspace(start, finish, npts)
236 self._run(dtime)
238 def dscan(self, motor, start, finish, npts, dtime):
239 "dscan: relative scan"
240 self._checkmotors(motor)
241 current = self.motors[motor].current()
242 start += current
243 finish += current
244 self.ascan(motor, start, finish, npts, dtime)
246 def a2scan(self, motor1, start1, finish1,
247 motor2, start2, finish2, npts, dtime):
248 "a2scan: absolute scan of 2 motors"
249 self._checkmotors(motor1, motor2)
250 self._scan.positioners = [self.motors[motor1], self.motors[motor2]]
251 self._scan.positioners[0].array = np.linspace(start1, finish1, npts)
252 self._scan.positioners[1].array = np.linspace(start2, finish2, npts)
253 self._run(dtime)
255 def d2scan(self, motor1, start1, finish1,
256 motor2, start2, finish2, npts, dtime):
257 "d2scan: relative scan of 2 motors"
258 self._checkmotors(motor1, motor2)
259 current1 = self.motors[motor1].current()
260 start1 += current1
261 finish1 += current1
263 current2 = self.motors[motor2].current()
264 start2 += current2
265 finish2 += current2
267 self.a2scan(motor1, start1, finish1,
268 motor2, start2, finish2, npts, dtime)
270 def a3scan(self, motor1, start1, finish1, motor2, start2, finish2,
271 motor3, start3, finish3, npts, dtime):
272 "a3scan: absolute scan of 3 motors"
273 self._checkmotors(motor1, motor2, motor3)
275 self._scan.positioners = [self.motors[motor1],
276 self.motors[motor2],
277 self.motors[motor3]]
278 self._scan.positioners[0].array = np.linspace(start1, finish1, npts)
279 self._scan.positioners[1].array = np.linspace(start2, finish2, npts)
280 self._scan.positioners[2].array = np.linspace(start3, finish3, npts)
281 self._run(dtime)
283 def d3scan(self, motor1, start1, finish1, motor2, start2, finish2,
284 motor3, start3, finish3, npts, dtime):
285 "d3scan: relative scan of 3 motors"
286 self._checkmotors(motor1, motor2, motor3)
288 current1 = self.motors[motor1].current()
289 start1 += current1
290 finish1 += current1
292 current2 = self.motors[motor2].current()
293 start2 += current2
294 finish2 += current2
296 current3 = self.motors[motor2].current()
297 start3 += current3
298 finish3 += current3
300 self.a3scan(motor1, start1, finish1,
301 motor2, start2, finish2,
302 motor3, start3, finish3, npts, dtime)
304 def mesh(self, motor1, start1, finish1, npts1,
305 motor2, start2, finish2, npts2, dtime):
306 """mesh scan: absolute scan of motor1 at each
307 position for motor2"""
308 self._checkmotors(motor1, motor2)
310 self._scan.positioners = [self.motors[motor1], self.motors[motor2]]
312 fast = npts2* [np.linspace(start1, finish1, npts1)]
313 slow = [[i]*npts1 for i in np.linspace(start2, finish2, npts2)]
315 self._scan.positioners[0].array = np.array(fast).flatten()
316 self._scan.positioners[1].array = np.array(slow).flatten()
318 # set breakpoints to be the end of each row
319 self._scan.breakpoints = [(i+1)*npts1 - 1 for i in range(npts2-1)]
321 # add print statement at end of each row
322 def show_meshstatus(breakpoint=None):
323 print('finished row %i of %i' % (1+(breakpoint/npts1), npts2))
324 time.sleep(0.25)
325 self._scan.at_break_methods.append(show_meshstatus)
326 self._run(dtime)