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

1#!/usr/bin/env python 

2""" 

3Spyk is a Spec emulator, providing Spec-like scanning functions to Larch 

4 

5 spec = SpykScan() 

6 spec.add_motors(x='XX:m1', y='XX:m2') 

7 spec.add_detector('XX:scaler1') 

8 spec.set_scanfile(outputfile) 

9 

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) 

14 

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) 

19 

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 

31 

32 

33from larch import Group 

34 

35from larch.utils import get_homedir 

36from larch.io import get_timestamp 

37 

38try: 

39 from epics import PV, caget, caput, get_pv, poll 

40 HAS_EPICS = True 

41except ImportError: 

42 HAS_EPICS = False 

43 

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 

52 

53 

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' 

62 

63 __sects = ('setup', 'motors', 'detectors', 'extra_pvs', 'counters') 

64 

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) 

76 

77 def get_default_configfile(self): 

78 return os.path.join(get_homedir(), self.SPYK_DIR, self.SPYK_INI) 

79 

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) 

107 

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) 

115 

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)) 

130 

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)) 

142 

143 def sections(self): 

144 return self.__sects.keys() 

145 

146 

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 

163 

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) 

170 

171 for label, pvname in self.config.counters.items(): 

172 self.bare_counters.append(Counter(pvname, label=label)) 

173 

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) 

184 

185 def save_config(self, filename=None): 

186 "save Spyk configuration file" 

187 print( 'save spyk config....') 

188 

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) 

193 

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)) 

197 

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)) 

201 

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) 

207 

208 def set_scanfile(self, filename): 

209 "set file name" 

210 self.datafilename = filename 

211 

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) 

217 

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) 

228 

229 self._scan.run(filename=self.datafilename) 

230 

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) 

237 

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) 

245 

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) 

254 

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 

262 

263 current2 = self.motors[motor2].current() 

264 start2 += current2 

265 finish2 += current2 

266 

267 self.a2scan(motor1, start1, finish1, 

268 motor2, start2, finish2, npts, dtime) 

269 

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) 

274 

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) 

282 

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) 

287 

288 current1 = self.motors[motor1].current() 

289 start1 += current1 

290 finish1 += current1 

291 

292 current2 = self.motors[motor2].current() 

293 start2 += current2 

294 finish2 += current2 

295 

296 current3 = self.motors[motor2].current() 

297 start3 += current3 

298 finish3 += current3 

299 

300 self.a3scan(motor1, start1, finish1, 

301 motor2, start2, finish2, 

302 motor3, start3, finish3, npts, dtime) 

303 

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) 

309 

310 self._scan.positioners = [self.motors[motor1], self.motors[motor2]] 

311 

312 fast = npts2* [np.linspace(start1, finish1, npts1)] 

313 slow = [[i]*npts1 for i in np.linspace(start2, finish2, npts2)] 

314 

315 self._scan.positioners[0].array = np.array(fast).flatten() 

316 self._scan.positioners[1].array = np.array(slow).flatten() 

317 

318 # set breakpoints to be the end of each row 

319 self._scan.breakpoints = [(i+1)*npts1 - 1 for i in range(npts2-1)] 

320 

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)