Coverage for /Users/Newville/Codes/xraylarch/larch/io/mda.py: 5%

1184 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-11-09 10:08 -0600

1#!/usr/bin/env python 

2# adopted with few changes from Tim Mooney's mda.py 

3 

4# version 2.1 Tim Mooney 2/15/2012 

5# merge of mda.py and mda_f.py 

6# - supports reading, writing, and arithmetic operations for 

7# up to 4-dimensional MDA files. 

8 

9__version__ = '2.1' 

10import sys 

11import os 

12import string 

13import numpy 

14from xdrlib import Packer, Unpacker 

15 

16import copy 

17from larch import Group 

18 

19 

20################################################################################ 

21# classes 

22# scanDim holds all of the data associated with a single execution of a single sscan record. 

23class scanDim: 

24 def __init__(self): 

25 self.rank = 0 # [1..n] 1 means this is the "innermost" or only scan dimension 

26 self.dim = 0 # dimensionality of data (numerically same as rank) 

27 self.npts = 0 # number of data points planned 

28 self.curr_pt = 0 # number of data points actually acquired 

29 self.plower_scans = 0 # file offsets of next lower rank scans 

30 self.name = "" # name of sscan record that acquired the data 

31 self.time = "" # time at which scan (dimension) started 

32 self.np = 0 # number of positioners 

33 self.p = [] # list of scanPositioner instances 

34 self.nd = 0 # number of detectors 

35 self.d = [] # list of scanDetector instances 

36 self.nt = 0 # number of detector triggers 

37 self.t = [] # list of scanTrigger instances 

38 

39 def __str__(self): 

40 if self.name != '': 

41 s = "%dD data from \"%s\" acquired on %s:\n%d/%d pts; %d positioners, %d detectors" % ( 

42 self.dim, self.name, self.time, self.curr_pt, self.npts, self.np, self.nd) 

43 else: 

44 s = "%dD data (not read in)" % (self.dim) 

45 

46 return s 

47 

48# scanPositioner holds all the information associated with a single positioner, and 

49# all the data written and acquired by that positioner during an entire (possibly 

50# multidimensional) scan. 

51class scanPositioner: 

52 def __init__(self): 

53 self.number = 0 # positioner number in sscan record 

54 self.fieldName = "" # name of sscanRecord PV 

55 self.name = "" # name of EPICS PV this positioner wrote to 

56 self.desc = "" # description of 'name' PV 

57 self.step_mode = "" # 'LINEAR', 'TABLE', or 'FLY' 

58 self.unit = "" # units of 'name' PV 

59 self.readback_name = "" # name of EPICS PV this positioner read from, if any 

60 self.readback_desc = "" # description of 'readback_name' PV 

61 self.readback_unit = "" # units of 'readback_name' PV 

62 self.data = [] # list of values written to 'name' PV. If rank==2, lists of lists, etc. 

63 

64 def __str__(self): 

65 data = self.data 

66 

67 n = data.ndim 

68 if n==1: 

69 dimString = '(' + str(data.shape[0]) + ')' 

70 else: 

71 dimString = str(data.shape) 

72 

73 s = "positioner <scanRecord>.%s\nPV name '%s'\nPV desc. '%s'\nPV units '%s'\nstep mode: %s\nRB name '%s'\nRB desc. '%s'\nRB units '%s'\ndata: %dD array %s\n" % (self.fieldName, 

74 self.name, self.desc, self.unit, self.step_mode, self.name, self.desc, self.unit, n, dimString) 

75 return s 

76 

77# scanDetector holds all the information associated with a single detector, and 

78# all the data acquired by that detector during an entire (possibly multidimensional) scan. 

79class scanDetector: 

80 def __init__(self): 

81 self.number = 0 # detector number in sscan record 

82 self.fieldName = "" # name of sscanRecord PV 

83 self.name = "" # name of EPICS PV this detector read from 

84 self.desc = "" # description of 'name' PV 

85 self.unit = "" # units of 'name' PV 

86 self.data = [] # list of values read from 'name' PV. If rank==2, lists of lists, etc. 

87 

88 def __str__(self): 

89 data = self.data 

90 n = data.ndim 

91 if n==1: 

92 dimString = '(' + str(data.shape[0]) + ')' 

93 else: 

94 dimString = str(data.shape) 

95 s = "detector <scanRecord>.%s\nPV name '%s'\nPV desc. '%s'\nPV units '%s'\ndata: %dD array %s\n" % (self.fieldName, 

96 self.name, self.desc, self.unit, n, dimString) 

97 return s 

98 

99# scanTrigger holds all the information associated with a single detector trigger. 

100class scanTrigger: 

101 def __init__(self): 

102 self.number = 0 # detector-trigger number in sscan record 

103 self.name = "" # name of sscanRecord PV 

104 self.command = 0.0 # value written to 'name' PV 

105 

106 def __str__(self): 

107 s = "trigger %d (%s), command=%f\n" % (self.number, 

108 self.name, self.command) 

109 return s 

110 

111# scanBuf is a private data structure used to assemble data that will be written to an MDA file. 

112class scanBuf: 

113 def __init__(self): 

114 self.npts = 0 

115 self.offset = 0 

116 self.bufLen = 0 

117 self.preamble = None 

118 self.pLowerScans = [] 

119 self.pLowerScansBuf = "" 

120 self.postamble = None 

121 self.data = None 

122 self.inner = [] # inner scans, if any 

123 

124# mdaBuf is a private data structure used to assemble data that will be written to an MDA file. 

125class mdaBuf: 

126 def __init__(self): 

127 self.header = None 

128 self.pExtra = None # file offset to extraPV section 

129 self.scan = None 

130 self.extraPV = None # extraPV section 

131 

132################################################################################ 

133# read MDA file 

134 

135# Given a detector number, return the name of the associated sscanRecord PV, 'D01'-'D99'. 

136# (Currently, only 70 detectors are ever used.) 

137def detName(i): 

138 if i < 100: 

139 return "D%02d"%(i+1) 

140 else: 

141 return "?" 

142 

143# Given a detector number, return the name of the associated sscanRecord PV, for the 

144# old sscanRecord, which had only 15 detectors 'D1'-'DF'. 

145def oldDetName(i): 

146 if i < 15: 

147 return string.upper("D%s"%(hex(i+1)[2])) 

148 elif i < 85: 

149 return "D%02d"%(i-14) 

150 else: 

151 return "?" 

152 

153# Given a positioner number, , return the name of the associated sscanRecord PV, "P1'-'P4' 

154def posName(i): 

155 if i < 4: 

156 return "P%d" % (i+1) 

157 else: 

158 return "?" 

159 

160def verboseData(data, out=sys.stdout): 

161 if ((len(data)>0) and (type(data[0]) == type([]))): 

162 for i in len(data): 

163 verboseData(data[i], out) 

164 else: 

165 out.write("[") 

166 for datum in data: 

167 if (type(datum) == type(0)): 

168 out.write(" %d" % datum) 

169 else: 

170 out.write(" %.5f" % datum) 

171 out.write(" ]\n") 

172 

173def readScan(scanFile, verbose=0, out=sys.stdout, unpacker=None): 

174 """usage: (scan,num) = readScan(scanFile, verbose=0, out=sys.stdout)""" 

175 

176 scan = scanDim() # data structure to hold scan info and data 

177 buf = scanFile.read(10000) # enough to read scan header 

178 if unpacker == None: 

179 u = Unpacker(buf) 

180 else: 

181 u = unpacker 

182 u.reset(buf) 

183 

184 scan.rank = u.unpack_int() 

185 if (scan.rank > 20) or (scan.rank < 0): 

186 print("* * * readScan('%s'): rank > 20. probably a corrupt file" % scanFile.name) 

187 return None 

188 

189 scan.npts = u.unpack_int() 

190 scan.curr_pt = u.unpack_int() 

191 if verbose: 

192 print("scan.rank = ", scan.rank) 

193 print("scan.npts = ", scan.npts) 

194 print("scan.curr_pt = ", scan.curr_pt) 

195 

196 if (scan.rank > 1): 

197 # if curr_pt < npts, plower_scans will have garbage for pointers to 

198 # scans that were planned for but not written 

199 scan.plower_scans = u.unpack_farray(scan.npts, u.unpack_int) 

200 if verbose: 

201 print("scan.plower_scans = ", scan.plower_scans) 

202 namelength = u.unpack_int() 

203 scan.name = u.unpack_string() 

204 if verbose: 

205 print("scan.name = ", scan.name) 

206 timelength = u.unpack_int() 

207 scan.time = u.unpack_string() 

208 if verbose: 

209 print("scan.time = ", scan.time) 

210 scan.np = u.unpack_int() 

211 if verbose: 

212 print("scan.np = ", scan.np) 

213 scan.nd = u.unpack_int() 

214 if verbose: 

215 print("scan.nd = ", scan.nd) 

216 scan.nt = u.unpack_int() 

217 if verbose: 

218 print("scan.nt = ", scan.nt) 

219 for j in range(scan.np): 

220 scan.p.append(scanPositioner()) 

221 scan.p[j].number = u.unpack_int() 

222 scan.p[j].fieldName = posName(scan.p[j].number) 

223 if verbose: 

224 print("positioner ", j) 

225 length = u.unpack_int() # length of name string 

226 if length: 

227 scan.p[j].name = u.unpack_string() 

228 if verbose: 

229 print("scan.p[%d].name = %s" % (j, scan.p[j].name)) 

230 length = u.unpack_int() # length of desc string 

231 if length: 

232 scan.p[j].desc = u.unpack_string() 

233 if verbose: 

234 print("scan.p[%d].desc = %s" % (j, scan.p[j].desc)) 

235 length = u.unpack_int() # length of step_mode string 

236 if length: 

237 scan.p[j].step_mode = u.unpack_string() 

238 if verbose: 

239 print("scan.p[%d].step_mode = %s" % (j, scan.p[j].step_mode)) 

240 length = u.unpack_int() # length of unit string 

241 if length: 

242 scan.p[j].unit = u.unpack_string() 

243 if verbose: 

244 print("scan.p[%d].unit = %s" % (j, scan.p[j].unit)) 

245 length = u.unpack_int() # length of readback_name string 

246 if length: 

247 scan.p[j].readback_name = u.unpack_string() 

248 if verbose: 

249 print("scan.p[%d].readback_name = %s" % (j, scan.p[j].readback_name)) 

250 length = u.unpack_int() # length of readback_desc string 

251 if length: 

252 scan.p[j].readback_desc = u.unpack_string() 

253 if verbose: 

254 print("scan.p[%d].readback_desc = %s" % (j, scan.p[j].readback_desc)) 

255 length = u.unpack_int() # length of readback_unit string 

256 if length: 

257 scan.p[j].readback_unit = u.unpack_string() 

258 if verbose: 

259 print("scan.p[%d].readback_unit = %s" % (j, scan.p[j].readback_unit)) 

260 

261 file_loc_det = scanFile.tell() - (len(buf) - u.get_position()) 

262 

263 for j in range(scan.nd): 

264 scan.d.append(scanDetector()) 

265 scan.d[j].number = u.unpack_int() 

266 scan.d[j].fieldName = detName(scan.d[j].number) 

267 if verbose: 

268 print("detector ", j) 

269 length = u.unpack_int() # length of name string 

270 if length: 

271 scan.d[j].name = u.unpack_string() 

272 if verbose: 

273 print("scan.d[%d].name = %s" % (j, scan.d[j].name)) 

274 length = u.unpack_int() # length of desc string 

275 if length: 

276 scan.d[j].desc = u.unpack_string() 

277 if verbose: 

278 print("scan.d[%d].desc = %s" % (j, scan.d[j].desc)) 

279 length = u.unpack_int() # length of unit string 

280 if length: 

281 scan.d[j].unit = u.unpack_string() 

282 if verbose: 

283 print("scan.d[%d].unit = %s" % (j, scan.d[j].unit)) 

284 

285 for j in range(scan.nt): 

286 scan.t.append(scanTrigger()) 

287 scan.t[j].number = u.unpack_int() 

288 if verbose: 

289 print("trigger ", j) 

290 length = u.unpack_int() # length of name string 

291 if length: 

292 scan.t[j].name = u.unpack_string() 

293 if verbose: 

294 print("scan.t[%d].name = %s" % (j, scan.t[j].name)) 

295 scan.t[j].command = u.unpack_float() 

296 if verbose: 

297 print("scan.t[%d].command = %s" % (j, scan.t[j].command)) 

298 

299 ### read data 

300 # positioners 

301 file_loc_data = scanFile.tell() - (len(buf) - u.get_position()) 

302 scanFile.seek(file_loc_data) 

303 buf = scanFile.read(scan.npts * (scan.np * 8 + scan.nd *4)) 

304 u.reset(buf) 

305 

306 data = u.unpack_farray(scan.npts*scan.np, u.unpack_double) 

307 start = 0 

308 end = scan.npts 

309 for j in range(scan.np): 

310 start = j*scan.npts 

311 scan.p[j].data = data[start:end] 

312 start = end 

313 end += scan.npts 

314 

315 # detectors 

316 data = u.unpack_farray(scan.npts*scan.nd, u.unpack_float) 

317 start = 0 

318 end = scan.npts 

319 for j in range(scan.nd): 

320 scan.d[j].data = data[start:end] 

321 start = end 

322 end += scan.npts 

323 

324 return (scan, (file_loc_data-file_loc_det)) 

325 

326useDetToDatOffset = 1 

327def readScanQuick(scanFile, unpacker=None, detToDat_offset=None): 

328 """usage: readScanQuick(scanFile, unpacker=None)""" 

329 

330 scan = scanDim() # data structure to hold scan info and data 

331 buf = scanFile.read(10000) # enough to read scan header 

332 if unpacker is None: 

333 u = Unpacker(buf) 

334 else: 

335 u = unpacker 

336 u.reset(buf) 

337 

338 scan.rank = u.unpack_int() 

339 if (scan.rank > 20) or (scan.rank < 0): 

340 print("* * * readScanQuick('%s'): rank > 20. probably a corrupt file" % scanFile.name) 

341 return None 

342 

343 scan.npts = u.unpack_int() 

344 scan.curr_pt = u.unpack_int() 

345 

346 if (scan.rank > 1): 

347 scan.plower_scans = u.unpack_farray(scan.npts, u.unpack_int) 

348 

349 namelength = u.unpack_int() 

350 scan.name = u.unpack_string() 

351 timelength = u.unpack_int() 

352 scan.time = u.unpack_string() 

353 

354 scan.np = u.unpack_int() 

355 scan.nd = u.unpack_int() 

356 scan.nt = u.unpack_int() 

357 

358 for j in range(scan.np): 

359 scan.p.append(scanPositioner()) 

360 scan.p[j].number = u.unpack_int() 

361 n = u.unpack_int() # length of name string 

362 if n: u.set_position(u.get_position()+4+(n+3)//4*4) 

363 n = u.unpack_int() # length of desc string 

364 if n: u.set_position(u.get_position()+4+(n+3)//4*4) 

365 n = u.unpack_int() # length of step_mode string 

366 if n: u.set_position(u.get_position()+4+(n+3)//4*4) 

367 n = u.unpack_int() # length of unit string 

368 if n: u.set_position(u.get_position()+4+(n+3)//4*4) 

369 n = u.unpack_int() # length of readback_name string 

370 if n: u.set_position(u.get_position()+4+(n+3)//4*4) 

371 n = u.unpack_int() # length of readback_desc string 

372 if n: u.set_position(u.get_position()+4+(n+3)//4*4) 

373 n = u.unpack_int() # length of readback_unit string 

374 if n: u.set_position(u.get_position()+4+(n+3)//4*4) 

375 

376 file_loc_det = scanFile.tell() - (len(buf) - u.get_position()) 

377 

378 if (detToDat_offset == None) or (not useDetToDatOffset): 

379 for j in range(scan.nd): 

380 scan.d.append(scanDetector()) 

381 scan.d[j].number = u.unpack_int() 

382 scan.d[j].fieldName = detName(scan.d[j].number) 

383 n = u.unpack_int() # length of name string 

384 if n: u.set_position(u.get_position()+4+(n+3)//4*4) 

385 n = u.unpack_int() # length of desc string 

386 if n: u.set_position(u.get_position()+4+(n+3)//4*4) 

387 n = u.unpack_int() # length of unit string 

388 if n: u.set_position(u.get_position()+4+(n+3)//4*4) 

389 

390 for j in range(scan.nt): 

391 scan.t.append(scanTrigger()) 

392 scan.t[j].number = u.unpack_int() 

393 n = u.unpack_int() # length of name string 

394 if n: u.set_position(u.get_position()+4+(n+3)//4*4) 

395 scan.t[j].command = u.unpack_float() 

396 

397 ### read data 

398 # positioners 

399 

400 file_loc = scanFile.tell() - (len(buf) - u.get_position()) 

401 diff = file_loc - (file_loc_det + detToDat_offset) 

402 if diff != 0: 

403 print("oldSeek, newSeek, o-n=", file_loc, file_loc_det + detToDat_offset, diff) 

404 scanFile.seek(file_loc) 

405 else: 

406 for j in range(scan.nd): 

407 scan.d.append(scanDetector()) 

408 scanFile.seek(file_loc_det + detToDat_offset) 

409 

410 buf = scanFile.read(scan.npts * (scan.np * 8 + scan.nd *4)) 

411 u.reset(buf) 

412 

413 data = u.unpack_farray(scan.npts*scan.np, u.unpack_double) 

414 for j in range(scan.np): 

415 start = j*scan.npts 

416 scan.p[j].data = data[j*scan.npts : (j+1)*scan.npts] 

417 

418 # detectors 

419 data = u.unpack_farray(scan.npts*scan.nd, u.unpack_float) 

420 for j in range(scan.nd): 

421 scan.d[j].data = data[j*scan.npts : (j+1)*scan.npts] 

422 

423 return scan 

424 

425EPICS_types_dict = { 

4260: "DBR_STRING", 

4271: "DBR_SHORT", 

4282: "DBR_FLOAT", 

4293: "DBR_ENUM", 

4304: "DBR_CHAR", 

4315: "DBR_LONG", 

4326: "DBR_DOUBLE", 

4337: "DBR_STS_STRING", 

4348: "DBR_STS_SHORT", 

4359: "DBR_STS_FLOAT", 

43610: "DBR_STS_ENUM", 

43711: "DBR_STS_CHAR", 

43812: "DBR_STS_LONG", 

43913: "DBR_STS_DOUBLE", 

44014: "DBR_TIME_STRING", 

44115: "DBR_TIME_SHORT", 

44216: "DBR_TIME_FLOAT", 

44317: "DBR_TIME_ENUM", 

44418: "DBR_TIME_CHAR", 

44519: "DBR_TIME_LONG", 

44620: "DBR_TIME_DOUBLE", 

44721: "DBR_GR_STRING", 

44822: "DBR_GR_SHORT", 

44923: "DBR_GR_FLOAT", 

45024: "DBR_GR_ENUM", 

45125: "DBR_GR_CHAR", 

45226: "DBR_GR_LONG", 

45327: "DBR_GR_DOUBLE", 

45428: "DBR_CTRL_STRING", 

45529: "DBR_CTRL_SHORT", 

45630: "DBR_CTRL_FLOAT", 

45731: "DBR_CTRL_ENUM", 

45832: "DBR_CTRL_CHAR", 

45933: "DBR_CTRL_LONG", 

46034: "DBR_CTRL_DOUBLE" 

461} 

462 

463def EPICS_types(n): 

464 if EPICS_types_dict.has_key(n): 

465 return EPICS_types_dict[n] 

466 else: 

467 return ("Unexpected type %d" % n) 

468 

469def readMDA(fname=None, maxdim=4, verbose=0, showHelp=0, outFile=None, 

470 useNumpy=None, readQuick=False): 

471 """usage readMDA(fname=None, maxdim=4, verbose=0, showHelp=0, 

472 outFile=None, readQuick=False)""" 

473 dim = [] 

474 if fname is None: 

475 print("No file specified, and no file dialog could be opened") 

476 return None 

477 if (not os.path.isfile(fname)): 

478 if (not fname.endswith('.mda')): 

479 fname = fname + '.mda' 

480 if (not os.path.isfile(fname)): 

481 print(fname, "not found") 

482 return None 

483 

484 if (outFile == None): 

485 out = sys.stdout 

486 else: 

487 out = open(outFile, 'w') 

488 

489 scanFile = open(fname, 'rb') 

490 if verbose: out.write("verbose=%d output for MDA file '%s'\n" % (verbose, fname)) 

491 buf = scanFile.read(100) # to read header for scan of up to 5 dimensions 

492 u = Unpacker(buf) 

493 

494 # read file header 

495 version = u.unpack_float() 

496 if verbose: out.write("MDA version = %.3f\n" % version) 

497 if abs(version - 1.3) > .01: 

498 print("I can't read MDA version %f. Is this really an MDA file?" % version) 

499 return None 

500 

501 scan_number = u.unpack_int() 

502 if verbose: out.write("scan_number = %d\n" % scan_number) 

503 rank = u.unpack_int() 

504 if verbose: out.write("rank = %d\n" % rank) 

505 dimensions = u.unpack_farray(rank, u.unpack_int) 

506 if verbose: 

507 out.write("dimensions = ") 

508 verboseData(dimensions, out) 

509 isRegular = u.unpack_int() 

510 if verbose: out.write("isRegular = %d\n" % isRegular) 

511 pExtra = u.unpack_int() 

512 if verbose: out.write("pExtra = %d (0x%x)\n" % (pExtra, pExtra)) 

513 pmain_scan = scanFile.tell() - (len(buf) - u.get_position()) 

514 

515 # collect 1D data 

516 scanFile.seek(pmain_scan) 

517 (s,n) = readScan(scanFile, max(0,verbose-1), out, unpacker=u) 

518 dim.append(s) 

519 dim[0].dim = 1 

520 

521 for p in dim[0].p: 

522 p.data = numpy.array(p.data) 

523 for d in dim[0].d: 

524 d.data = numpy.array(d.data) 

525 

526 if ((rank > 1) and (maxdim > 1)): 

527 # collect 2D data 

528 for i in range(dim[0].curr_pt): 

529 scanFile.seek(dim[0].plower_scans[i]) 

530 if (i==0): 

531 (s,detToDat) = readScan(scanFile, max(0,verbose-1), out, unpacker=u) 

532 dim.append(s) 

533 dim[1].dim = 2 

534 # replace data arrays [1,2,3] with [[1,2,3]] 

535 for j in range(dim[1].np): 

536 dim[1].p[j].data = [dim[1].p[j].data] 

537 for j in range(dim[1].nd): 

538 dim[1].d[j].data = [dim[1].d[j].data] 

539 else: 

540 if readQuick: 

541 s = readScanQuick(scanFile, unpacker=u, detToDat_offset=detToDat) 

542 else: 

543 (s,junk) = readScan(scanFile, max(0,verbose-1), out, unpacker=u) 

544 # append data arrays 

545 # [ [1,2,3], [2,3,4] ] -> [ [1,2,3], [2,3,4], [3,4,5] ] 

546 numP = min(s.np, len(dim[1].p)) 

547 if (s.np > numP): 

548 print("First scan had %d positioners; This one only has %d." % (s.np, numP)) 

549 for j in range(numP): 

550 dim[1].p[j].data.append(s.p[j].data) 

551 numD = min(s.nd, len(dim[1].d)) 

552 if (s.nd > numD): 

553 print("First scan had %d detectors; This one only has %d." % (s.nd, numD)) 

554 for j in range(numD): dim[1].d[j].data.append(s.d[j].data) 

555 

556 for p in dim[1].p: 

557 p.data = numpy.array(p.data) 

558 for d in dim[1].d: 

559 d.data = numpy.array(d.data) 

560 

561 if ((rank > 2) and (maxdim > 2)): 

562 # collect 3D data 

563 #print("dim[0].curr_pt=",dim[0].curr_pt) 

564 for i in range(dim[0].curr_pt): 

565 #print("i=%d of %d points" % (i, dim[0].curr_pt)) 

566 scanFile.seek(dim[0].plower_scans[i]) 

567 (s1,detToDat) = readScan(scanFile, max(0,verbose-1), out, unpacker=u) 

568 #print("s1.curr_pt=", s1.curr_pt) 

569 for j in range(s1.curr_pt): 

570 #print("j=%d of %d points" % (j, s1.curr_pt)) 

571 scanFile.seek(s1.plower_scans[j]) 

572 if (j==0) or not readQuick: 

573 (s, detToDat) = readScan(scanFile, max(0,verbose-1), out, unpacker=u) 

574 else: 

575 s = readScanQuick(scanFile, unpacker=u, detToDat_offset=detToDat) 

576 if ((i == 0) and (j == 0)): 

577 dim.append(s) 

578 dim[2].dim = 3 

579 # replace data arrays [1,2,3] with [[[1,2,3]]] 

580 for k in range(dim[2].np): 

581 dim[2].p[k].data = [[dim[2].p[k].data]] 

582 for k in range(dim[2].nd): 

583 dim[2].d[k].data = [[dim[2].d[k].data]] 

584 else: 

585 # append data arrays 

586 numP = min(s.np, len(dim[2].p)) 

587 if (s.np > numP): 

588 print("First scan had %d positioners; This one only has %d." % (s.np, numP)) 

589 for k in range(numP): 

590 if j==0: dim[2].p[k].data.append([]) 

591 dim[2].p[k].data[i].append(s.p[k].data) 

592 numD = min(s.nd, len(dim[2].d)) 

593 if (s.nd > numD): 

594 print("First scan had %d detectors; This one only has %d." % (s.nd, numD)) 

595 for k in range(numD): 

596 if j==0: dim[2].d[k].data.append([]) 

597 dim[2].d[k].data[i].append(s.d[k].data) 

598 

599 for p in dim[2].p: 

600 p.data = numpy.array(p.data) 

601 for d in dim[2].d: 

602 d.data = numpy.array(d.data) 

603 

604 if ((rank > 3) and (maxdim > 3)): 

605 # collect 4D data 

606 for i in range(dim[0].curr_pt): 

607 scanFile.seek(dim[0].plower_scans[i]) 

608 (s1, detToDat) = readScan(scanFile, max(0,verbose-1), out, unpacker=u) 

609 for j in range(s1.curr_pt): 

610 scanFile.seek(s1.plower_scans[j]) 

611 (s2, detToDat) = readScan(scanFile, max(0,verbose-1), out, unpacker=u) 

612 for k in range(s2.curr_pt): 

613 scanFile.seek(s2.plower_scans[k]) 

614 if (k==0) or not readQuick: 

615 (s, detToDat) = readScan(scanFile, max(0,verbose-1), out, unpacker=u) 

616 else: 

617 s = readScanQuick(scanFile, unpacker=u, detToDat_offset=detToDat) 

618 if ((i == 0) and (j == 0) and (k == 0)): 

619 dim.append(s) 

620 dim[3].dim = 4 

621 for m in range(dim[3].np): 

622 dim[3].p[m].data = [[[dim[3].p[m].data]]] 

623 for m in range(dim[3].nd): 

624 dim[3].d[m].data = [[[dim[3].d[m].data]]] 

625 else: 

626 # append data arrays 

627 if j==0 and k==0: 

628 for m in range(dim[3].np): 

629 dim[3].p[m].data.append([[]]) 

630 dim[3].p[m].data[i][0].append(s.p[m].data) 

631 for m in range(dim[3].nd): 

632 dim[3].d[m].data.append([[]]) 

633 dim[3].d[m].data[i][0].append(s.d[m].data) 

634 else: 

635 for m in range(dim[3].np): 

636 if k==0: dim[3].p[m].data[i].append([]) 

637 dim[3].p[m].data[i][j].append(s.p[m].data) 

638 for m in range(dim[3].nd): 

639 if k==0: dim[3].d[m].data[i].append([]) 

640 dim[3].d[m].data[i][j].append(s.d[m].data) 

641 

642 for p in dim[3].p: 

643 p.data = numpy.array(p.data) 

644 for d in dim[3].d: 

645 d.data = numpy.array(d.data) 

646 

647 

648 

649 # Collect scan-environment variables into a dictionary 

650 dict = {} 

651 dict['sampleEntry'] = ("description", "unit string", "value", "EPICS_type", "count") 

652 dict['filename'] = fname 

653 dict['version'] = version 

654 dict['scan_number'] = scan_number 

655 dict['rank'] = rank 

656 dict['dimensions'] = dimensions 

657 acq_dimensions = [] 

658 for d in dim: 

659 acq_dimensions.append(d.curr_pt) 

660 dict['acquired_dimensions'] = acq_dimensions 

661 dict['isRegular'] = isRegular 

662 dict['ourKeys'] = ['sampleEntry', 'filename', 'version', 'scan_number', 'rank', 'dimensions', 'acquired_dimensions', 'isRegular', 'ourKeys'] 

663 if pExtra: 

664 scanFile.seek(pExtra) 

665 buf = scanFile.read() # Read all scan-environment data 

666 u.reset(buf) 

667 numExtra = u.unpack_int() 

668 if verbose: out.write("\nnumber of 'Extra' PV's = %d\n" % numExtra) 

669 for i in range(numExtra): 

670 if verbose: out.write("env PV #%d -------\n" % (i)) 

671 name = '' 

672 n = u.unpack_int() # length of name string 

673 if n: name = u.unpack_string() 

674 if verbose: out.write("\tname = '%s'\n" % name) 

675 desc = '' 

676 n = u.unpack_int() # length of desc string 

677 if n: desc = u.unpack_string() 

678 if verbose: out.write("\tdesc = '%s'\n" % desc) 

679 EPICS_type = u.unpack_int() 

680 if verbose: out.write("\tEPICS_type = %d (%s)\n" % (EPICS_type, EPICS_types(EPICS_type))) 

681 

682 unit = '' 

683 value = '' 

684 count = 0 

685 if EPICS_type != 0: # not DBR_STRING; array is permitted 

686 count = u.unpack_int() # 

687 if verbose: out.write("\tcount = %d\n" % count) 

688 n = u.unpack_int() # length of unit string 

689 if n: unit = u.unpack_string() 

690 if verbose: out.write("\tunit = '%s'\n" % unit) 

691 

692 if EPICS_type == 0: # DBR_STRING 

693 n = u.unpack_int() # length of value string 

694 if n: value = u.unpack_string() 

695 elif EPICS_type == 32: # DBR_CTRL_CHAR 

696 #value = u.unpack_fstring(count) 

697 vect = u.unpack_farray(count, u.unpack_int) 

698 value = "" 

699 for i in range(len(vect)): 

700 # treat the byte array as a null-terminated string 

701 if vect[i] == 0: break 

702 value = value + chr(vect[i]) 

703 elif EPICS_type == 29: # DBR_CTRL_SHORT 

704 value = u.unpack_farray(count, u.unpack_int) 

705 elif EPICS_type == 33: # DBR_CTRL_LONG 

706 value = u.unpack_farray(count, u.unpack_int) 

707 elif EPICS_type == 30: # DBR_CTRL_FLOAT 

708 value = u.unpack_farray(count, u.unpack_float) 

709 elif EPICS_type == 34: # DBR_CTRL_DOUBLE 

710 value = u.unpack_farray(count, u.unpack_double) 

711 if verbose: 

712 if (EPICS_type == 0): 

713 out.write("\tvalue = '%s'\n" % (value)) 

714 else: 

715 out.write("\tvalue = ") 

716 verboseData(value, out) 

717 

718 dict[name] = (desc, unit, value, EPICS_type, count) 

719 scanFile.close() 

720 

721 dim.reverse() 

722 dim.append(dict) 

723 dim.reverse() 

724 if verbose or showHelp: 

725 print("\n%s is a %d-D file; %d dimensions read in." % (fname, dim[0]['rank'], len(dim)-1)) 

726 print("dim[0] = dictionary of %d scan-environment PVs" % (len(dim[0]))) 

727 print(" usage: dim[0]['sampleEntry'] ->", dim[0]['sampleEntry']) 

728 for i in range(1,len(dim)): 

729 print("dim[%d] = %s" % (i, str(dim[i]))) 

730 print(" usage: dim[1].p[2].data -> 1D array of positioner 2 data") 

731 print(" usage: dim[2].d[7].data -> 2D array of detector 7 data") 

732 

733 if showHelp: 

734 print(" ") 

735 print(" each scan dimension (i.e., dim[1], dim[2], ...) has the following fields: ") 

736 print(" time - date & time at which scan was started: %s" % (dim[1].time)) 

737 print(" name - name of scan record that acquired this dimension: '%s'" % (dim[1].name)) 

738 print(" curr_pt - number of data points actually acquired: %d" % (dim[1].curr_pt)) 

739 print(" npts - number of data points requested: %d" % (dim[1].npts)) 

740 print(" nd - number of detectors for this scan dimension: %d" % (dim[1].nd)) 

741 print(" d[] - list of detector-data structures") 

742 print(" np - number of positioners for this scan dimension: %d" % (dim[1].np)) 

743 print(" p[] - list of positioner-data structures") 

744 print(" nt - number of detector triggers for this scan dimension: %d" % (dim[1].nt)) 

745 print(" t[] - list of trigger-info structures") 

746 

747 print(" ") 

748 print(" each detector-data structure (e.g., dim[1].d[0]) has the following fields: ") 

749 print(" desc - description of this detector") 

750 print(" data - data list") 

751 print(" unit - engineering units associated with this detector") 

752 print(" fieldName - scan-record field (e.g., 'D01')") 

753 

754 print(" ") 

755 print(" each positioner-data structure (e.g., dim[1].p[0]) has the following fields: ") 

756 print(" desc - description of this positioner") 

757 print(" data - data list") 

758 print(" step_mode - scan mode (e.g., Linear, Table, On-The-Fly)") 

759 print(" unit - engineering units associated with this positioner") 

760 print(" fieldName - scan-record field (e.g., 'P1')") 

761 print(" name - name of EPICS PV (e.g., 'xxx:m1.VAL')") 

762 print(" readback_desc - description of this positioner") 

763 print(" readback_unit - engineering units associated with this positioner") 

764 print(" readback_name - name of EPICS PV (e.g., 'xxx:m1.VAL')") 

765 

766 if (outFile): 

767 out.close() 

768 return dim 

769 

770################################################################################ 

771# skim MDA file to get dimensions (planned and actually acquired), and other info 

772def skimScan(dataFile): 

773 """usage: skimScan(dataFile)""" 

774 scan = scanDim() # data structure to hold scan info and data 

775 buf = dataFile.read(10000) # enough to read scan header 

776 u = Unpacker(buf) 

777 scan.rank = u.unpack_int() 

778 if (scan.rank > 20) or (scan.rank < 0): 

779 print("* * * skimScan('%s'): rank > 20. probably a corrupt file" % dataFile.name) 

780 return None 

781 scan.npts = u.unpack_int() 

782 scan.curr_pt = u.unpack_int() 

783 if (scan.curr_pt == 0): 

784 #print("mda:skimScan: curr_pt = 0") 

785 return None 

786 if (scan.rank > 1): 

787 scan.plower_scans = u.unpack_farray(scan.npts, u.unpack_int) 

788 namelength = u.unpack_int() 

789 scan.name = u.unpack_string() 

790 timelength = u.unpack_int() 

791 scan.time = u.unpack_string() 

792 scan.np = u.unpack_int() 

793 scan.nd = u.unpack_int() 

794 scan.nt = u.unpack_int() 

795 return scan 

796 

797def skimMDA(fname=None, verbose=False): 

798 """usage skimMDA(fname=None)""" 

799 #print("skimMDA: filename=", fname) 

800 dim = [] 

801 if (fname == None): 

802 print("No file specified") 

803 return None 

804 if (not os.path.isfile(fname)): 

805 if (not fname.endswith('.mda')): 

806 fname = fname + '.mda' 

807 if (not os.path.isfile(fname)): 

808 print(fname, "not found") 

809 return None 

810 

811 try: 

812 dataFile = open(fname, 'rb') 

813 except: 

814 print("mda_f:skimMDA: failed to open file '%s'" % fname) 

815 return None 

816 

817 buf = dataFile.read(100) # to read header for scan of up to 5 dimensions 

818 u = Unpacker(buf) 

819 

820 # read file header 

821 version = u.unpack_float() 

822# if version < 1.299 or version > 1.301: 

823# print(fname, " has file version", version) 

824# return None 

825 scan_number = u.unpack_int() 

826 rank = u.unpack_int() 

827 dimensions = u.unpack_farray(rank, u.unpack_int) 

828 isRegular = u.unpack_int() 

829 pExtra = u.unpack_int() 

830 pmain_scan = dataFile.tell() - (len(buf) - u.get_position()) 

831 

832 # collect 1D data 

833 dataFile.seek(pmain_scan) 

834 scan = skimScan(dataFile) 

835 if (scan == None): 

836 if verbose: print(fname, "contains no data") 

837 return None 

838 

839 dim.append(scan) 

840 dim[0].dim = 1 

841 

842 if (rank > 1): 

843 dataFile.seek(dim[0].plower_scans[0]) 

844 dim.append(skimScan(dataFile)) 

845 if (dim[1]): 

846 dim[1].dim = 2 

847 else: 

848 if verbose: print("had a problem reading 2d from ", fname) 

849 return None 

850 

851 if (rank > 2): 

852 dataFile.seek(dim[1].plower_scans[0]) 

853 dim.append(skimScan(dataFile)) 

854 if (dim[2]): 

855 dim[2].dim = 3 

856 else: 

857 if verbose: print("had a problem reading 3d from ", fname) 

858 return None 

859 

860 if (rank > 3): 

861 dataFile.seek(dim[2].plower_scans[0]) 

862 dim.append(skimScan(dataFile)) 

863 if (dim[3]): 

864 dim[3].dim = 4 

865 else: 

866 if verbose: print("had a problem reading 4d from ", fname) 

867 return None 

868 

869 dataFile.close() 

870 dict = {} 

871 dict['filename'] = fname 

872 dict['version'] = version 

873 dict['scan_number'] = scan_number 

874 dict['rank'] = rank 

875 dict['dimensions'] = dimensions 

876 dimensions = [] 

877 for d in dim: 

878 dimensions.append(d.curr_pt) 

879 dict['acquired_dimensions'] = dimensions 

880 dict['isRegular'] = isRegular 

881 dim.reverse() 

882 dim.append(dict) 

883 dim.reverse() 

884 return dim 

885 

886################################################################################ 

887# Write MDA file 

888def packScanHead(scan): 

889 s = scanBuf() 

890 s.npts = scan.npts 

891 

892 # preamble 

893 p = Packer() 

894 p.pack_int(scan.rank) 

895 p.pack_int(scan.npts) 

896 p.pack_int(scan.curr_pt) 

897 s.preamble = p.get_buffer() 

898 

899 # file offsets to lower level scans (if any) 

900 p.reset() 

901 if (scan.rank > 1): 

902 # Pack zeros for now, so we'll know how much 

903 # space the real offsets will use. 

904 for j in range(scan.npts): 

905 p.pack_int(0) 

906 s.pLowerScansBuf = p.get_buffer() 

907 

908 # postamble 

909 p.reset() 

910 n = len(scan.name); p.pack_int(n) 

911 if (n): p.pack_string(scan.name) 

912 n = len(scan.time); p.pack_int(n) 

913 if (n): p.pack_string(scan.time) 

914 p.pack_int(scan.np) 

915 p.pack_int(scan.nd) 

916 p.pack_int(scan.nt) 

917 

918 for j in range(scan.np): 

919 p.pack_int(scan.p[j].number) 

920 

921 n = len(scan.p[j].name); p.pack_int(n) 

922 if (n): p.pack_string(scan.p[j].name) 

923 

924 n = len(scan.p[j].desc); p.pack_int(n) 

925 if (n): p.pack_string(scan.p[j].desc) 

926 

927 n = len(scan.p[j].step_mode); p.pack_int(n) 

928 if (n): p.pack_string(scan.p[j].step_mode) 

929 

930 n = len(scan.p[j].unit); p.pack_int(n) 

931 if (n): p.pack_string(scan.p[j].unit) 

932 

933 n = len(scan.p[j].readback_name); p.pack_int(n) 

934 if (n): p.pack_string(scan.p[j].readback_name) 

935 

936 n = len(scan.p[j].readback_desc); p.pack_int(n) 

937 if (n): p.pack_string(scan.p[j].readback_desc) 

938 

939 n = len(scan.p[j].readback_unit); p.pack_int(n) 

940 if (n): p.pack_string(scan.p[j].readback_unit) 

941 

942 for j in range(scan.nd): 

943 p.pack_int(scan.d[j].number) 

944 n = len(scan.d[j].name); p.pack_int(n) 

945 if (n): p.pack_string(scan.d[j].name) 

946 n = len(scan.d[j].desc); p.pack_int(n) 

947 if (n): p.pack_string(scan.d[j].desc) 

948 n = len(scan.d[j].unit); p.pack_int(n) 

949 if (n): p.pack_string(scan.d[j].unit) 

950 

951 for j in range(scan.nt): 

952 p.pack_int(scan.t[j].number) 

953 n = len(scan.t[j].name); p.pack_int(n) 

954 if (n): p.pack_string(scan.t[j].name) 

955 p.pack_float(scan.t[j].command) 

956 

957 s.postamble = p.get_buffer() 

958 s.bufLen = len(s.preamble) + len(s.pLowerScansBuf) + len(s.postamble) 

959 return s 

960 

961def packScanData(scan, cpt): 

962 p = Packer() 

963 if (len(cpt) == 0): # 1D array 

964 for i in range(scan.np): 

965 p.pack_farray(scan.npts, scan.p[i].data, p.pack_double) 

966 for i in range(scan.nd): 

967 p.pack_farray(scan.npts, scan.d[i].data, p.pack_float) 

968 

969 elif (len(cpt) == 1): # 2D array 

970 j = cpt[0] 

971 for i in range(scan.np): 

972 p.pack_farray(scan.npts, scan.p[i].data[j], p.pack_double) 

973 for i in range(scan.nd): 

974 p.pack_farray(scan.npts, scan.d[i].data[j], p.pack_float) 

975 

976 elif (len(cpt) == 2): # 3D array 

977 j = cpt[0] 

978 k = cpt[1] 

979 for i in range(scan.np): 

980 p.pack_farray(scan.npts, scan.p[i].data[j][k], p.pack_double) 

981 for i in range(scan.nd): 

982 p.pack_farray(scan.npts, scan.d[i].data[j][k], p.pack_float) 

983 

984 return(p.get_buffer()) 

985 

986def writeMDA(dim, fname=None): 

987 m = mdaBuf() 

988 p = Packer() 

989 

990 p.reset() 

991 if (type(dim) != type([])): print("writeMDA: first arg must be a scan") 

992 if ((fname != None) and (type(fname) != type(""))): 

993 print("writeMDA: second arg must be a filename or None") 

994 rank = dim[0]['rank'] # rank of scan as a whole 

995 # write file header 

996 p.pack_float(dim[0]['version']) 

997 p.pack_int(dim[0]['scan_number']) 

998 p.pack_int(dim[0]['rank']) 

999 p.pack_farray(rank, dim[0]['dimensions'], p.pack_int) 

1000 p.pack_int(dim[0]['isRegular']) 

1001 m.header = p.get_buffer() 

1002 

1003 p.reset() 

1004 p.pack_int(0) # pExtra 

1005 m.pExtra = p.get_buffer() 

1006 

1007 m.scan = packScanHead(dim[1]) 

1008 m.scan.offset = len(m.header) + len(m.pExtra) 

1009 m.scan.data = packScanData(dim[1], []) 

1010 m.scan.bufLen = m.scan.bufLen + len(m.scan.data) 

1011 prevScan = m.scan 

1012 #print("\n m.scan=", m.scan) 

1013 #print("\n type(m.scan.pLowerScans)=", type(m.scan.pLowerScans)) 

1014 

1015 if (rank > 1): 

1016 for i in range(m.scan.npts): 

1017 m.scan.inner.append(packScanHead(dim[2])) 

1018 thisScan = m.scan.inner[i] 

1019 thisScan.offset = prevScan.offset + prevScan.bufLen 

1020 m.scan.pLowerScans.append(thisScan.offset) 

1021 thisScan.data = packScanData(dim[2], [i]) 

1022 thisScan.bufLen = thisScan.bufLen + len(thisScan.data) 

1023 prevScan = thisScan 

1024 

1025 if (rank > 2): 

1026 for j in range(m.scan.inner[i].npts): 

1027 m.scan.inner[i].inner.append(packScanHead(dim[3])) 

1028 thisScan = m.scan.inner[i].inner[j] 

1029 thisScan.offset = prevScan.offset + prevScan.bufLen 

1030 m.scan.inner[i].pLowerScans.append(thisScan.offset) 

1031 thisScan.data = packScanData(dim[3], [i,j]) 

1032 thisScan.bufLen = thisScan.bufLen + len(thisScan.data) 

1033 prevScan = thisScan 

1034 

1035 if (rank > 3): 

1036 for k in range(m.scan.inner[i].inner[j].npts): 

1037 m.scan.inner[i].inner[j].append(packScanHead(dim[4])) 

1038 thisScan = m.scan.inner[i].inner[j].inner[k] 

1039 thisScan.offset = prevScan.offset + prevScan.bufLen 

1040 m.scan.inner[i].inner[j].pLowerScans.append(thisScan.offset) 

1041 thisScan.data = packScanData(dim[4], [i,j,k]) 

1042 thisScan.bufLen = thisScan.bufLen + len(thisScan.data) 

1043 prevScan = thisScan 

1044 

1045 # Now we know where the extraPV section must go. 

1046 p.reset() 

1047 p.pack_int(prevScan.offset + prevScan.bufLen) # pExtra 

1048 m.pExtra = p.get_buffer() 

1049 

1050 # pack scan-environment variables from dictionary 

1051 p.reset() 

1052 

1053 numKeys = 0 

1054 for name in dim[0].keys(): 

1055 if not (name in dim[0]['ourKeys']): 

1056 numKeys = numKeys + 1 

1057 p.pack_int(numKeys) 

1058 

1059 for name in dim[0].keys(): 

1060 # Note we don't want to write the dict entries we made for our own 

1061 # use in the scanDim object. 

1062 if not (name in dim[0]['ourKeys']): 

1063 desc = dim[0][name][0] 

1064 unit = dim[0][name][1] 

1065 value = dim[0][name][2] 

1066 EPICS_type = dim[0][name][3] 

1067 count = dim[0][name][4] 

1068 n = len(name); p.pack_int(n) 

1069 if (n): p.pack_string(name) 

1070 n = len(desc); p.pack_int(n) 

1071 if (n): p.pack_string(desc) 

1072 p.pack_int(EPICS_type) 

1073 if EPICS_type != 0: # not DBR_STRING, so pack count and units 

1074 p.pack_int(count) 

1075 n = len(unit); p.pack_int(n) 

1076 if (n): p.pack_string(unit) 

1077 if EPICS_type == 0: # DBR_STRING 

1078 n = len(value); p.pack_int(n) 

1079 if (n): p.pack_string(value) 

1080 elif EPICS_type == 32: # DBR_CTRL_CHAR 

1081 # write null-terminated string 

1082 v = [] 

1083 for i in range(len(value)): v.append(ord(value[i:i+1])) 

1084 v.append(0) 

1085 p.pack_farray(count, v, p.pack_int) 

1086 elif EPICS_type == 29: # DBR_CTRL_SHORT 

1087 p.pack_farray(count, value, p.pack_int) 

1088 elif EPICS_type == 33: # DBR_CTRL_LONG 

1089 p.pack_farray(count, value, p.pack_int) 

1090 elif EPICS_type == 30: # DBR_CTRL_FLOAT 

1091 p.pack_farray(count, value, p.pack_float) 

1092 elif EPICS_type == 34: # DBR_CTRL_DOUBLE 

1093 p.pack_farray(count, value, p.pack_double) 

1094 

1095 m.extraPV = p.get_buffer() 

1096 

1097 # Now we have to repack all the scan offsets 

1098 if (rank > 1): # 2D scan 

1099 #print("m.scan.pLowerScans", m.scan.pLowerScans) 

1100 p.reset() 

1101 p.pack_farray(m.scan.npts, m.scan.pLowerScans, p.pack_int) 

1102 m.scan.pLowerScansBuf = p.get_buffer() 

1103 if (rank > 2): # 3D scan 

1104 for i in range(m.scan.npts): 

1105 p.reset() 

1106 p.pack_farray(m.scan.inner[i].npts, m.scan.inner[i].pLowerScans, p.pack_int) 

1107 m.scan.inner[i].pLowerScansBuf = p.get_buffer() 

1108 if (rank > 3): # 4D scan 

1109 for j in range(m.scan.inner[i].npts): 

1110 p.reset() 

1111 p.pack_farray(m.scan.inner[i].inner[j].npts, m.scan.inner[i].inner[j].pLowerScans, p.pack_int) 

1112 m.scan.inner[i].inner[j].pLowerScansBuf = p.get_buffer() 

1113 

1114 # Write 

1115 if (fname == None): fname = tkFileDialog.SaveAs().show() 

1116 f = open(fname, 'wb') 

1117 

1118 f.write(m.header) 

1119 f.write(m.pExtra) 

1120 s0 = m.scan 

1121 f.write(s0.preamble) 

1122 if len(s0.pLowerScansBuf): f.write(s0.pLowerScansBuf) 

1123 f.write(s0.postamble) 

1124 f.write(s0.data) 

1125 for s1 in s0.inner: 

1126 f.write(s1.preamble) 

1127 if len(s1.pLowerScansBuf): f.write(s1.pLowerScansBuf) 

1128 f.write(s1.postamble) 

1129 f.write(s1.data) 

1130 for s2 in s1.inner: 

1131 f.write(s2.preamble) 

1132 if len(s2.pLowerScansBuf): f.write(s2.pLowerScansBuf) 

1133 f.write(s2.postamble) 

1134 f.write(s2.data) 

1135 for s3 in s2.inner: 

1136 f.write(s3.preamble) 

1137 if len(s3.pLowerScansBuf): f.write(s3.pLowerScansBuf) 

1138 f.write(s3.postamble) 

1139 f.write(s3.data) 

1140 f.write(m.extraPV) 

1141 f.close() 

1142 return 

1143 

1144################################################################################ 

1145# write Ascii file 

1146def getFormat(d, rank): 

1147 # number of positioners, detectors 

1148 np = d[rank].np 

1149 nd = d[rank].nd 

1150 

1151 min_column_width = 15 

1152 # make sure there's room for the names, etc. 

1153 phead_fmt = [] 

1154 dhead_fmt = [] 

1155 pdata_fmt = [] 

1156 ddata_fmt = [] 

1157 columns = 1 

1158 for i in range(np): 

1159 cw = max(min_column_width, len(d[rank].p[i].name)+1) 

1160 cw = max(cw, len(d[rank].p[i].desc)+1) 

1161 cw = max(cw, len(d[rank].p[i].fieldName)+1) 

1162 phead_fmt.append("%%-%2ds" % cw) 

1163 pdata_fmt.append("%%- %2d.8f" % cw) 

1164 columns = columns + cw 

1165 for i in range(nd): 

1166 cw = max(min_column_width, len(d[rank].d[i].name)+1) 

1167 cw = max(cw, len(d[rank].d[i].desc)+1) 

1168 cw = max(cw, len(d[rank].d[i].fieldName)+1) 

1169 dhead_fmt.append("%%-%2ds" % cw) 

1170 ddata_fmt.append("%%- %2d.8f" % cw) 

1171 columns = columns + cw 

1172 return (phead_fmt, dhead_fmt, pdata_fmt, ddata_fmt, columns) 

1173 

1174def writeAscii(d, fname=None): 

1175 if (type(d) != type([])): 

1176 print("writeMDA: first arg must be a scan") 

1177 return 

1178 

1179 if (fname == None): 

1180 f = sys.stdout 

1181 else: 

1182 f = open(fname, 'wb') 

1183 

1184 (phead_fmt, dhead_fmt, pdata_fmt, ddata_fmt, columns) = getFormat(d, 1) 

1185 # header 

1186 f.write("### %s is a %d-dimensional file\n" % (d[0]['filename'], d[0]['rank'])) 

1187 f.write("### Number of data points = [") 

1188 for i in range(d[0]['rank'],1,-1): f.write("%-d," % d[i].curr_pt) 

1189 f.write("%-d]\n" % d[1].curr_pt) 

1190 

1191 f.write("### Number of detector signals = [") 

1192 for i in range(d[0]['rank'],1,-1): f.write("%-d," % d[i].nd) 

1193 f.write("%-d]\n" % d[1].nd) 

1194 

1195 # scan-environment PV values 

1196 f.write("#\n# Scan-environment PV values:\n") 

1197 ourKeys = d[0]['ourKeys'] 

1198 maxKeyLen = 0 

1199 for i in d[0].keys(): 

1200 if (i not in ourKeys): 

1201 if len(i) > maxKeyLen: maxKeyLen = len(i) 

1202 for i in d[0].keys(): 

1203 if (i not in ourKeys): 

1204 f.write("#%s%s%s\n" % (i, (maxKeyLen-len(i))*' ', d[0][i])) 

1205 

1206 f.write("\n#%s\n" % str(d[1])) 

1207 f.write("# scan date, time: %s\n" % d[1].time) 

1208 sep = "#"*columns + "\n" 

1209 f.write(sep) 

1210 

1211 # 1D data table head 

1212 f.write("#") 

1213 for j in range(d[1].np): 

1214 f.write(phead_fmt[j] % (d[1].p[j].fieldName)) 

1215 for j in range(d[1].nd): 

1216 f.write(dhead_fmt[j] % (d[1].d[j].fieldName)) 

1217 f.write("\n") 

1218 

1219 f.write("#") 

1220 for j in range(d[1].np): 

1221 f.write(phead_fmt[j] % (d[1].p[j].name)) 

1222 for j in range(d[1].nd): 

1223 f.write(dhead_fmt[j] % (d[1].d[j].name)) 

1224 f.write("\n") 

1225 

1226 f.write("#") 

1227 for j in range(d[1].np): 

1228 f.write(phead_fmt[j] % (d[1].p[j].desc)) 

1229 for j in range(d[1].nd): 

1230 f.write(dhead_fmt[j] % (d[1].d[j].desc)) 

1231 f.write("\n") 

1232 

1233 f.write(sep) 

1234 

1235 # 1D data 

1236 for i in range(d[1].curr_pt): 

1237 f.write("") 

1238 for j in range(d[1].np): 

1239 f.write(pdata_fmt[j] % (d[1].p[j].data[i])) 

1240 for j in range(d[1].nd): 

1241 f.write(ddata_fmt[j] % (d[1].d[j].data[i])) 

1242 f.write("\n") 

1243 

1244 # 2D data 

1245 if (len(d) > 2): 

1246 f.write("\n# 2D data\n") 

1247 for i in range(d[2].np): 

1248 f.write("\n# Positioner %d (.%s) PV:'%s' desc:'%s'\n" % (i, d[2].p[i].fieldName, d[2].p[i].name, d[2].p[i].desc)) 

1249 for j in range(d[1].curr_pt): 

1250 for k in range(d[2].curr_pt): 

1251 f.write("%f " % d[2].p[i].data[j][k]) 

1252 f.write("\n") 

1253 

1254 for i in range(d[2].nd): 

1255 f.write("\n# Detector %d (.%s) PV:'%s' desc:'%s'\n" % (i, d[2].d[i].fieldName, d[2].d[i].name, d[2].d[i].desc)) 

1256 for j in range(d[1].curr_pt): 

1257 for k in range(d[2].curr_pt): 

1258 f.write("%f " % d[2].d[i].data[j][k]) 

1259 f.write("\n") 

1260 

1261 if (len(d) > 3): 

1262 f.write("\n# Can't write 3D (or higher) data\n") 

1263 

1264 if (fname != None): 

1265 f.close() 

1266 

1267 

1268################################################################################ 

1269# misc 

1270def showEnv(dict, all=0): 

1271 if type(dict) == type([]) and type(dict[0]) == type({}): 

1272 dict = dict[0] 

1273 fieldLen = 0 

1274 for k in dict.keys(): 

1275 if len(k) > fieldLen: 

1276 fieldLen = len(k) 

1277 format = "%%-%-ds %%s" % fieldLen 

1278 for k in dict.keys(): 

1279 if not (k in dict['ourKeys']): 

1280 if type(dict[k]) == type((1,2,3)): 

1281 value = dict[k][2] 

1282 else: 

1283 value = dict[k] 

1284 if type(value) == type([]) and len(value) == 1: 

1285 value = value[0] 

1286 if all: 

1287 print(format % (k,dict[k])) 

1288 else: 

1289 print(format % (k,value)) 

1290 return 

1291 

1292def fixMDA(d): 

1293 """usage: d=fixMDA(d), where d is a list returned by readMDA()""" 

1294 dimensions = [] 

1295 for i in range(1,len(d)): 

1296 npts = d[i].curr_pt 

1297 d[i].npts = npts 

1298 dimensions.append(npts) 

1299 for j in range(d[i].np): 

1300 if (len(d[i].p[j].data) > npts): 

1301 d[i].p[j].data = d[i].p[j].data[0:npts] 

1302 for j in range(d[i].nd): 

1303 if (len(d[i].d[j].data) > npts): 

1304 d[i].d[j].data = d[i].d[j].data[0:npts] 

1305 dimensions.reverse() 

1306 d[0]['dimensions'] = dimensions 

1307 return(d) 

1308 

1309# translate mca-ROI PV's to mca-ROI description PV's, scaler signal PV'ss to scaler signal description PV's 

1310descDict = {'R1':'R1NM', 'R2':'R2NM', 'R3':'R3NM', 'R4':'R4NM', 'R5':'R5NM', 

1311 'R6':'R6NM', 'R7':'R7NM', 'R8':'R8NM', 'R9':'R9NM', 'R10':'R10NM', 

1312 'R11':'R11NM', 'R12':'R12NM', 'R13':'R13NM', 'R14':'R14NM', 'R15':'R15NM', 

1313 'R16':'R16NM', 'R17':'R17NM', 'R18':'R18NM', 'R19':'R19NM', 'R20':'R20NM', 

1314 'R21':'R21NM', 'R22':'R22NM', 'R23':'R23NM', 'R24':'R24NM', 'R25':'R25NM', 

1315 'R26':'R26NM', 'R27':'R27NM', 'R28':'R28NM', 'R29':'R29NM', 'R30':'R30NM', 

1316 'R31':'R31NM', 'R32':'R32NM', 

1317 'S1':'NM1', 'S2':'NM2', 'S3':'NM3', 'S4':'NM4', 'S5':'NM5', 'S6':'NM6', 'S7':'NM7', 'S8':'NM8', 'S9':'NM9', 'S10':'NM10', 

1318 'S11':'NM11', 'S12':'NM12', 'S13':'NM13', 'S14':'NM14', 'S15':'NM15', 'S16':'NM16', 'S17':'NM17', 'S18':'NM18', 'S19':'NM19', 'S20':'NM20', 

1319 'S21':'NM21', 'S22':'NM22', 'S23':'NM23', 'S24':'NM24', 'S25':'NM25', 'S26':'NM26', 'S27':'NM27', 'S28':'NM28', 'S29':'NM29', 'S30':'NM30', 

1320 'S31':'NM31', 'S32':'NM32', 'S33':'NM33', 'S34':'NM34', 'S35':'NM35', 'S36':'NM36', 'S37':'NM37', 'S38':'NM38', 'S39':'NM39', 'S40':'NM40', 

1321 'S41':'NM41', 'S42':'NM42', 'S43':'NM43', 'S44':'NM44', 'S45':'NM45', 'S46':'NM46', 'S47':'NM47', 'S48':'NM48', 'S49':'NM49', 'S50':'NM50', 

1322 'S51':'NM51', 'S52':'NM52', 'S53':'NM53', 'S54':'NM54', 'S55':'NM55', 'S56':'NM56', 'S57':'NM57', 'S58':'NM58', 'S59':'NM59', 'S60':'NM60', 

1323 'S61':'NM61', 'S62':'NM62', 'S63':'NM63', 'S64':'NM64'} 

1324 

1325def findDescInEnv(name, env): 

1326 try: 

1327 (record, field) = name.split('.') 

1328 except: 

1329 return "" 

1330 try: 

1331 descField = descDict[field] 

1332 except: 

1333 return "" 

1334 try: 

1335 desc = env[record+'.'+descField] 

1336 except: 

1337 return "" 

1338 if desc[2] == "" or desc[2].isspace(): 

1339 return "" 

1340 return "{%s}" % desc[2] 

1341 

1342def getDescFromEnv(data): 

1343 if (data): 

1344 for d in data[1:]: 

1345 for p in d.p: 

1346 if not p.desc: 

1347 p.desc = findDescInEnv(p.name, data[0]) 

1348 for d in d.d: 

1349 if not d.desc: 

1350 d.desc = findDescInEnv(d.name, data[0]) 

1351 

1352######################## 

1353# opMDA and related code 

1354######################## 

1355def isScan(d): 

1356 if type(d) != type([]): return(0) 

1357 if type(d[0]) != type({}): return(0) 

1358 if 'rank' not in d[0].keys(): return(0) 

1359 if len(d) < 2: return(0) 

1360 if type(d[1]) != type(scanDim()): return(0) 

1361 return(1) 

1362 

1363def isScalar(d): 

1364 if (type(d) == type(1)) or (type(d) == type(1.0)): return(1) 

1365 return(0) 

1366 

1367def add(a,b): return(a+b) 

1368def sub(a,b): return(a-b) 

1369def mul(a,b): return(a*b) 

1370def div(a,b): return(a/b) 

1371 

1372def setOp(op): 

1373 if (op == '+') or (op == 'add'): return(add) 

1374 if (op == '-') or (op == 'sub'): return(sub) 

1375 if (op == '*') or (op == 'x') or (op == 'mul'): return(mul) 

1376 if (op == '/') or (op == 'div'): return(div) 

1377 if (op == '>') or (op == 'max'): return(max) 

1378 if (op == '<') or (op == 'min'): return(min) 

1379 print("opMDA: unrecognized op = ", op) 

1380 return None 

1381 

1382def opMDA_usage(): 

1383 print("opMDA() usage:") 

1384 print(" result = opMDA(op, scan1, scan2)") 

1385 print(" OR") 

1386 print(" result = opMDA(op, scan1, scalar_value)") 

1387 print("\nwhere:") 

1388 print(" op is one of '+', '-', '*', '/', '>', '<'") 

1389 print(" scan1, scan2 are scans, i.e., structures returned by mda.readMDA()") 

1390 print(" result is a copy of scan1, modified by the operation\n") 

1391 print("\n examples:") 

1392 print(" r = opMDA('+', scan1, scan2) -- adds all detector data from scan1 and scan2") 

1393 print(" r = opMDA('-', scan1, 2.0) -- subtracts 2 from all detector data from scan1") 

1394 print(" r = opMDA('>', r, 0) -- 'r' data or 0, whichever is greater") 

1395 

1396def opMDA_scalar(op, d1, scalar): 

1397 op = setOp(op) 

1398 if (op == None): 

1399 opMDA_usage() 

1400 return None 

1401 

1402 s = copy.deepcopy(d1) 

1403 

1404 # 1D op 

1405 for i in range(s[1].nd): 

1406 for j in range(s[1].npts): 

1407 s[1].d[i].data[j] = op(s[1].d[i].data[j], scalar) 

1408 

1409 if (len(s) == 2): return s 

1410 # 2D op 

1411 for i in range(s[2].nd): 

1412 for j in range(s[1].npts): 

1413 for k in range(s[2].npts): 

1414 s[2].d[i].data[j][k] = op(s[2].d[i].data[j][k], scalar) 

1415 

1416 if (len(s) == 3): return s 

1417 # 3D op 

1418 for i in range(s[3].nd): 

1419 for j in range(s[1].npts): 

1420 for k in range(s[2].npts): 

1421 for l in range(s[3].npts): 

1422 s[3].d[i].data[j][k][l] = op(s[3].d[i].data[j][k][l], scalar) 

1423 

1424 if (len(s) == 4): return s 

1425 # 4D op 

1426 for i in range(s[4].nd): 

1427 for j in range(s[1].npts): 

1428 for k in range(s[2].npts): 

1429 for l in range(s[3].npts): 

1430 for m in range(s[4].npts): 

1431 s[4].d[i].data[j][k][l][m] = op(s[4].d[i].data[j][k][l][m], scalar) 

1432 

1433 if (len(s) > 4): 

1434 print("opMDA supports up to 4D scans") 

1435 return s 

1436 

1437def opMDA(op, d1, d2): 

1438 """opMDA() is a function for performing arithmetic operations on MDA files, 

1439 or on an MDA file and a scalar value. 

1440 

1441 For examples, type 'opMDA_usage()'. 

1442 """ 

1443 if isScan(d1) and isScalar(d2): return(opMDA_scalar(op,d1,d2)) 

1444 if (not isScan(d1)) : 

1445 print("opMDA: first operand is not a scan") 

1446 opMDA_usage() 

1447 return None 

1448 

1449 if (not isScan(d2)): 

1450 print("opMDA: second operand is neither a scan nor a scalar") 

1451 opMDA_usage() 

1452 return None 

1453 

1454 if len(d1) != len(d2): 

1455 print("scans do not have same dimension") 

1456 return None 

1457 

1458 op = setOp(op) 

1459 if (op == None): 

1460 opMDA_usage() 

1461 return None 

1462 

1463 s = copy.deepcopy(d1) 

1464 

1465 # 1D op 

1466 if s[1].nd != d2[1].nd: 

1467 print("scans do not have same number of 1D detectors") 

1468 return None 

1469 if s[1].npts != d2[1].npts: 

1470 print("scans do not have same number of data points") 

1471 return None 

1472 for i in range(s[1].nd): 

1473 s[1].d[i].data = map(op, s[1].d[i].data, d2[1].d[i].data) 

1474 

1475 if (len(s) == 2): return s 

1476 # 2D op 

1477 if s[2].nd != d2[2].nd: 

1478 print("scans do not have same number of 2D detectors") 

1479 return None 

1480 if s[2].npts != d2[2].npts: 

1481 print("scans do not have same number of data points") 

1482 return None 

1483 for i in range(s[2].nd): 

1484 for j in range(s[1].npts): 

1485 s[2].d[i].data[j] = map(op, s[2].d[i].data[j], d2[2].d[i].data[j]) 

1486 

1487 if (len(s) == 3): return s 

1488 # 3D op 

1489 if s[3].nd != d2[3].nd: 

1490 print("scans do not have same number of 3D detectors") 

1491 return None 

1492 if s[3].npts != d2[3].npts: 

1493 print("scans do not have same number of data points") 

1494 return None 

1495 for i in range(s[3].nd): 

1496 for j in range(s[1].npts): 

1497 for k in range(s[2].npts): 

1498 s[3].d[i].data[j][k] = map(op, s[3].d[i].data[j][k], d2[3].d[i].data[j][k]) 

1499 

1500 if (len(s) == 4): return s 

1501 # 3D op 

1502 if s[4].nd != d2[4].nd: 

1503 print("scans do not have same number of 4D detectors") 

1504 return None 

1505 if s[4].npts != d2[4].npts: 

1506 print("scans do not have same number of data points") 

1507 return None 

1508 for i in range(s[4].nd): 

1509 for j in range(s[1].npts): 

1510 for k in range(s[2].npts): 

1511 for l in range(s[3].npts): 

1512 s[4].d[i].data[j][k][l] = map(op, s[4].d[i].data[j][k][l], d2[4].d[i].data[j][k][l]) 

1513 

1514 if (len(s) > 5): 

1515 print("opMDA supports up to 4D scans") 

1516 return s 

1517 

1518 

1519def read_mda(fname, maxdim=3, verbose=False): 

1520 """read an MDA file from the Epics Scan Record 

1521 

1522 Arguments 

1523 --------- 

1524 filname (str) name of file 

1525 maxdim (integer) max number of dimensions [default=3] 

1526 

1527 Returns 

1528 ------- 

1529 group containing `scan1` and possibly `scan2`, etc objects 

1530 

1531 Notes 

1532 ----- 

1533 not very well tested for scans of dimension > 2 

1534 

1535 """ 

1536 print("WARNING: support for MDA files will expire in 2024") 

1537 out = readMDA(fname, maxdim=maxdim, verbose=verbose) 

1538 group = Group(name=f'MDA_file {fname}') 

1539 group.extra_pvs = out[0] 

1540 group.scan1 = out[1] 

1541 group.dimension = dim = len(out) - 1 

1542 

1543 if dim > 1: 

1544 group.scan2 = out[2] 

1545 if dim > 2: 

1546 group.scan3 = out[3] 

1547 if dim > 3: 

1548 group.scan4 = out[4] 

1549 

1550 if dim == 1: 

1551 data, array_labels, field_names, pv_names = [], [], [], [] 

1552 for x in group.scan1.p + group.scan1.d: 

1553 data.append(x.data) 

1554 pv_names.append(x.name) 

1555 field_names.append(x.fieldName) 

1556 label = x.desc 

1557 if label in (None, 'None', ''): 

1558 label = x.fieldName 

1559 array_labels.append(label) 

1560 

1561 group.data = numpy.array(data) 

1562 group.pv_names = pv_names 

1563 group.field_names = field_names 

1564 group.array_labels = array_labels 

1565 return group