DataSet.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947
  1. import logging
  2. import time
  3. import os
  4. import math
  5. import numpy as np
  6. from numpy.polynomial.polynomial import polyval
  7. import h5py
  8. import traceback
  9. try:
  10. #Compatibility Python3 and Python 2
  11. from .CalibrationHandle import theCalibration
  12. from .constants import KARA
  13. from . import board
  14. from .board import available_boards
  15. except:
  16. from CalibrationHandle import theCalibration
  17. from constants import KARA
  18. #FIXME: This is device dependent and needs to be in the config file!!
  19. HEADER_SIZE_BYTES = 32
  20. #FIXME: Python Dictionaries? Never heard of them...
  21. RAW_FILE = 1
  22. RAW_FILE_NPY = 2
  23. TIMESCAN = 3
  24. class DataSet(object):
  25. """
  26. Most usefull functions:
  27. train(adc,frm,to,calibrate)
  28. heatmap(adc, frm,to)
  29. fft(adc,frm,to,trunbyturn,mean)
  30. readFromLog()
  31. ! Be aware: parameter ADC is in range of 0 to 7
  32. """
  33. def __init__(self, filename=None, decodedData=None, rawData=None, stringData=None, delays=None, shiftFMC2=0, tRev=KARA.trev, bunchesPerTurn=KARA.h, calibrationFile=""):
  34. """
  35. Initialise the dataset
  36. use one of:
  37. :param filename: string of the rawfile to open. If available it reads automatically the meta information from the Logfile
  38. :param decodedData: array of already decoded Data
  39. :param rawData: filedescriptor (eg. np.memmap) or array of rawdata
  40. :param stringData: handling rawdata given as string (eg. from bif._bif_read_data)
  41. :param delays: dict containing the delaysettings. default is read from LogFile or if not present {'c330':0, 'c25':0, 'c25b':4, 'f':[0,0,0,0,0,0,0,0]}
  42. :param shiftFMC2: compensate Bucketmissmatch between channel 1-4 and 5-8 (can later be done by setShiftFMC2)
  43. :param tRev: default KARA.trev
  44. :param bunchesPerTrun: default KARA.h
  45. :param calibrationFile: define the to use calibration. by default it looks for "calibration.hdf" in the same dic as the filename
  46. :return: -
  47. """
  48. self.fileName = filename
  49. if filename is not None:
  50. self.path = os.path.dirname(filename)
  51. self.type = 0
  52. self.header = None
  53. self.adcNumber = 4
  54. self.adcSwap = False #compatibility for old data
  55. self.log = None
  56. self.stepper = None
  57. self.invert = False
  58. self.datax = None
  59. self.isAcquisition=False
  60. self.skippedTurns = 0
  61. self.tRev = tRev
  62. self.bunchesPerTurn = bunchesPerTurn
  63. self.shiftFMC2 = shiftFMC2
  64. self.array = []
  65. self.frm = 0
  66. self.to = 0
  67. self.v = False
  68. self._heatmaps = {}
  69. self._ffts = {}
  70. self._fftsnobunching = False
  71. self._f = 0
  72. self._t = 1
  73. self.c330 = 0
  74. self.c25 = 0
  75. self.c25b = 4
  76. self.f = []
  77. self.dataRead = False
  78. self.noFile = False
  79. self.calibId = "current"
  80. if calibrationFile != "":
  81. self.calibId = theCalibration.openFile(calibrationFile)
  82. else:
  83. if filename is not None:
  84. self.calibId = theCalibration.openFile(os.path.join(self.path,'calibration.hdf'))
  85. else:
  86. print('calibrationFile not defined - no calibration possible')
  87. if delays is not None:
  88. try:
  89. self.c330 = delays['c330']
  90. self.c25 = delays['c25']
  91. self.c25b = delays['c25b']
  92. self.f = delays['f']
  93. except:
  94. pass
  95. if decodedData is not None:
  96. self.array = decodedData
  97. self.adcNumber = len(decodedData[1])-1
  98. self.dataRead = True
  99. self.noFile = True
  100. print('load decodedData with ADCs', self.adcNumber)
  101. elif stringData is not None:
  102. logging.vinfo("Read data directly from device.")
  103. logging.vinfo("Read %i bytes of data" % len(stringData))
  104. rawData = np.fromstring(stringData, dtype=np.uint32)
  105. self.loadFromRawData(rawData)
  106. self.dataRead = True
  107. self.noFile = True
  108. elif rawData is not None:
  109. self.loadFromRawData(rawData)
  110. self.dataRead = True
  111. self.noFile = True
  112. elif filename is not None:
  113. if not os.path.isfile(self.fileName):
  114. print('file {} does not exist'.format(self.fileName))
  115. return
  116. else:
  117. self.getFromLog()
  118. else:
  119. print('DataSet nothing given!')
  120. return
  121. def setShiftFMC2(self, shiftFMC2):
  122. self.shiftFMC2 = shiftFMC2
  123. self.to = 0 #force new decode
  124. def setDelays(self, delays):
  125. try:
  126. self.c330 = delays['c330']
  127. self.c25 = delays['c25']
  128. self.c25b = delays['c25b']
  129. self.f = np.array([float(v) for v in delays['f'][1:-1].split(',')])
  130. except:
  131. traceback.print_exc()
  132. pass
  133. def fineDelays(self):
  134. #FIXME: THIS IS A TRIAGE!
  135. # This is a bandaid patch to make peak reconstruction at least *somewhat*
  136. # working again. We will only use the finedelays from current
  137. # configuration. This will inevitably be incorrect, if data is stored to
  138. # harddrive and then opened again in a new session with different settings
  139. # and configuration. Or on a different KAPTURE system alltogether. So we
  140. # will eventually need to think about a way to couple calibration,
  141. # configuration and measurment data together into one 'container' so it
  142. # can be re-used and moved between system boundaries, without losing
  143. # details.
  144. # if len(self.f):
  145. # return self.f
  146. # if self.header:
  147. # return self.header['fine_delay_adc']
  148. # else:
  149. # return None
  150. adcdelays_ps = [0 for i in range(self.adcNumber)]
  151. #FIXME: I am just *guessing* that available_boards[0] is the right one,
  152. # since support for multiple boards has been phazed out a while ago, but
  153. # still lingers in the codebase as a remnant. So [0] is a good guess, for
  154. # now.
  155. board_config = board.get_board_config(available_boards[0])
  156. bunches_per_turn = board_config.get("bunches_per_turn")
  157. time_between_bunches_s = self.tRev / bunches_per_turn
  158. #Convert seconds to picosecods
  159. time_between_bunches_ps = time_between_bunches_s * (1000**4)
  160. finedelay_factor = board_config.get("chip_delay_factor")
  161. #TRIAGE:
  162. #FMC Banks are currently (18.12.2020) switched, so the slots for the
  163. #ADC delay slots 1,2,3,4 are for ADCs 5,6,7,8 and vice versa
  164. fdelays = board_config.get("chip_delay")
  165. finedelays = fdelays[4:] + fdelays[:4]
  166. finedelays = [i * finedelay_factor for i in finedelays]
  167. #FIXME:
  168. #NOTE:
  169. # Michele Caselle requested, that the 'dead space' between each
  170. # individual bunch be removed. This means, we do not calculate the actual
  171. # time in ps between each bunch, based on the machine geometry and
  172. # configuration, but just assume that the next bunch starts exactly 3ps
  173. # (the smallest possible fine-delay) after the last bunch has ended.
  174. # Therefore, we just assume that the time between bunches is at max the
  175. # highest fine-delay we con configure + 3
  176. time_between_bunches_ps = 3
  177. finedelay_max = board_config.get("chip_delay_max")
  178. time_between_bunches_ps += finedelay_max * finedelay_factor
  179. #FIXME: We need a better way to understand how the ADCs are distributed
  180. # across the FMC connectors. Currently, I have to know that we have 8 ADCs
  181. # distributed as 4 ADCs onto 2 FMCs, and get the delays from the board
  182. # config accordingly. Not good. We should find a way to do this
  183. # programmatically.
  184. fmc1_delay = board_config.get("delay_330_th") * board_config.get("delay_330_factor")
  185. fmc1_delay += board_config.get("delay_25_th") * board_config.get("delay_25_factor")
  186. fmc2_delay = board_config.get("delay_330_th_2") * board_config.get("delay_330_factor")
  187. fmc2_delay += board_config.get("delay_25_th_2") * board_config.get("delay_25_factor")
  188. adcdelays_ps[:4] = [i + fmc1_delay for i in finedelays[:4]]
  189. adcdelays_ps[4:] = [i + fmc2_delay for i in finedelays[4:]]
  190. #NOTE:
  191. # The Track-and-Hold (TH) timings do not have an impact on the actual
  192. # distribution of data across the time domain! The THs only "latches" an
  193. # analogue value at a certain point in time and holds it, so the ADCs
  194. # have time to convert the analogue value into a digital one.
  195. adcdelays_ps = finedelays
  196. #Express the delays as fractions of full timing distance between bunches
  197. adcdelays_ps = [float(i) / time_between_bunches_ps for i in adcdelays_ps]
  198. #print("###############################")
  199. #print(finedelays)
  200. #print(adcdelays_ps)
  201. #print(board_config.dump())
  202. #print("###############################")
  203. return adcdelays_ps
  204. def getCalibratedDelay(self):
  205. if self.datax is not None:
  206. return self.datax*1e-12
  207. out = []
  208. for i,v in enumerate(self.f):
  209. out.append(theCalibration.calibrateX(i, self.c330, self.c25, v, self.c25b))
  210. return np.array(out)
  211. def numOfBunches(self):
  212. return self.array.shape[0]
  213. def numOfTurns(self):
  214. return self.numOfBunches() / self.bunchesPerTurn
  215. def heatmap(self, adc=1, frm=0, to=-1, bunch_frm=0, bunch_to=-1):
  216. newone = self.loadFromFile(0, to*self.bunchesPerTurn)
  217. if isinstance(adc, list):
  218. adc = adc[0]
  219. if not 0 <= adc <= self.adcNumber:
  220. raise ValueError('adc must be in [0,{:}]'.format(self.adcNumber-1))
  221. l = len(self.array)
  222. l = (l//self.bunchesPerTurn)*self.bunchesPerTurn
  223. if not adc in self._heatmaps or newone:
  224. heatmap = self.array[:l,adc].reshape(-1, self.bunchesPerTurn).transpose()
  225. self._heatmaps[adc] = heatmap
  226. return self._heatmaps[adc][bunch_frm:bunch_to, frm:to]
  227. def fft(self, adc=0, frm=0, to=-1, drop_first_bin=True, nobunching=False):
  228. """
  229. :param adc: single int in [0:7] or list.
  230. :param nobunching: False - generate img with fft for every bunch; True - generate one fft without splitting into bunches
  231. :return: if param adc is a list it returns a list with lenght 8 in which the requested elements containing the array with the fft
  232. """
  233. if nobunching:
  234. if not isinstance(adc, list):
  235. adc=[adc]
  236. if self._fftsnobunching != nobunching:
  237. self._fftsnobunching = nobunching
  238. self._ffts = {}
  239. if isinstance(adc, list):
  240. #print('list', adc)
  241. out = [[],[],[],[], [],[],[],[]]
  242. for i in adc:
  243. if i in self._ffts:
  244. if self._ffts[i][0] == frm and self._ffts[i][1] == to:
  245. #print('use stored FFT')
  246. out[i]=self._ffts[i][2]
  247. continue
  248. if self._fftsnobunching:
  249. heatmap = self.train(i,frm,to)
  250. else:
  251. heatmap = self.heatmap(i, frm, to)
  252. if drop_first_bin:
  253. if self._fftsnobunching:
  254. self._ffts[i] = [frm, to, self._fft(heatmap, self._fftsnobunching)[1:] ]
  255. else:
  256. self._ffts[i] = [frm, to, self._fft(heatmap, self._fftsnobunching)[:, 1:] ]
  257. else:
  258. self._ffts[i] = [frm, to, self._fft(heatmap, self._fftsnobunching)]
  259. out[i]=self._ffts[i][2]
  260. return out
  261. else:
  262. #print('single', adc)
  263. heatmap = self.heatmap(adc, frm, to)
  264. if drop_first_bin:
  265. return self._fft(heatmap)[:, 1:]
  266. else:
  267. return self._fft(heatmap)
  268. def fftMaxFreq(self):
  269. mult=1.0
  270. if self._fftsnobunching:
  271. mult = 184.0
  272. return ((self.numOfTurns()* mult) // 2 + 1) * self.fftFreqDist()
  273. def fftFreqDist(self):
  274. mult=1.0
  275. #if self._fftsnobunching:
  276. # mult = 1.0/184.0
  277. return 1.0/(self.numOfTurns() * (self.skippedTurns + 1) * self.tRev * mult)
  278. def train(self, adc=0, frm=0, to=-1, calibrate=False):
  279. #FIXME: This method seems pretty superfluous (with maybe the exception of
  280. # the calibration stuff).
  281. # Essentially all it does is just truncating data for a second time, and
  282. # reorders data so we access ADC data as 'data[adc][bunch]' rather than
  283. # 'data[:,adc][bunch]'. What's the point? Really just to save TWO characters
  284. # of coding? (:,)
  285. # Also, returning different data structures depending on the given
  286. # parameters is VERY DANGEROUS!!
  287. # If we give 'adc' as a single integer, it returns a 1-Dimensional array
  288. # (basically, a list) and if we give 'adc' as a list, it reutrns a
  289. # 2-dimensional array?
  290. # This is basically guaranteed to sooner or later cause trouble down the
  291. # line...
  292. """
  293. params: adc: single int in [0:7] or list. if adc is a list (eg. [1,3]) it returns a list of length 8 with the requested elements filled
  294. """
  295. self.loadFromFile(frm, to)
  296. #FIXME: Why are there two truncation parameters?
  297. #One passed in from the function call and one from the DatSet object?
  298. if to != self.to:
  299. to -= self.frm
  300. pdata = self.array[frm-self.frm:to, :-1]
  301. else:
  302. pdata = self.array[frm-self.frm:, :-1]
  303. #print("train", adc)
  304. #print(pdata)
  305. if isinstance(adc, list):
  306. #FIXME: What happens if we have more than 8 ADCs in the future?
  307. #This should NOT be hardcoded!!!
  308. data = [[],[],[],[], [],[],[],[]]
  309. for item in range(len(data)):
  310. if item in adc:
  311. if calibrate:
  312. data[item] = theCalibration.calibrateY(pdata[:,item], item, self.calibId)
  313. else:
  314. data[item] = pdata[:,item]
  315. else:
  316. data[item] = np.zeros(len(pdata))
  317. #print('train', len(data))
  318. #Note: Data is now stored as
  319. #[[ADC 1: Bunch1, Bunch2, Bunch3 ... Bunch n]
  320. # [ADC 2: Bunch1, Bunch2, Bunch3 ... Bunch n]
  321. # [ADC 3: Bunch1, Bunch2, Bunch3 ... Bunch n]
  322. # ...
  323. # [ADC m: Bunch1, Bunch2, Bunch3 ... Bunch n]]
  324. return np.array(data)
  325. if calibrate:
  326. return theCalibration.calibrateY(pdata[:,adc], adc, self.calibId)
  327. #FIXME: Missing range checking for ADC
  328. #This should ideally happen at the BEGINNING of the function
  329. return pdata[:,adc]
  330. def combined(self, adc=0, frm=0, to=-1, calibrate=False, turnbyturn=False, mean=False, workingChannels=[0,1,2,3,4,5,6,7]):
  331. """
  332. generates one array with all adc
  333. :param adc: select the adc that will be return solo
  334. :param turnbyturn: default False. if set to one it repeats the x value with the maximum bunchnumber. so to overlay the turns
  335. :param mean: only used with turnbyturn set to True. If set to 1 it calculates the mean of all used turns
  336. :param working_channels: to dissable channels in the Combined data (List)
  337. :return: 2D List [0] contains X data and Y data of all. [1] only for selected adc
  338. """
  339. if len(workingChannels) < 2:
  340. #FIXME: This should not be an exception, but rather an expectation
  341. #Error handling could be improved
  342. raise ValueError('working_channels must have at least 2 channels; {}'.format(workingChannels))
  343. if turnbyturn:
  344. if to != -1:
  345. to = to//184*184
  346. selector = [0,1,2,3]
  347. if self.adcNumber > 4:
  348. #selector = [0,1,2,3,6,7] #currently not all chanels are working
  349. selector = workingChannels
  350. #FIXME: WHY are we using .train() to re-arrange our data to be in
  351. # ADC-Bunches order, just to transpose it back into Bunch-ADC order, which
  352. # is EXACTLY what the raw data was to begin with...?
  353. array = self.train(adc=selector, frm=frm,to=to, calibrate=calibrate).T
  354. array = array[:, np.array(selector)]
  355. #At this point, 'array' is basically just a truncated version of the
  356. #raw-data in 'self.array' without the bunch-numbers
  357. if isinstance(adc, list):
  358. adc = adc[0]
  359. finedelays = np.array(self.fineDelays())
  360. if finedelays is None:
  361. finedelays = np.array([0,0,0,0])
  362. if self.adcNumber >4:
  363. #FIXME: Highly dangerous! What happens if we will have more than
  364. #8 ADCs in the future?
  365. finedelays = np.repeat(finedelays, 2)
  366. if calibrate:
  367. for i in range(self.adcNumber):
  368. finedelays[i] = (theCalibration.calibrateX(i, self.c330, self.c25, finedelays[i], self.c25b, self.calibId) - theCalibration.calibrateX(0, self.c330, self.c25, 0, self.c25b, self.calibId) )*1e12
  369. else:
  370. #FIXME:
  371. #WHAT DOES THIS RANDOM x3 DO!?
  372. #finedelays = finedelays*3
  373. pass
  374. #FIXME: Why? What purpose does this serve?
  375. #Why do we divide by 100!?
  376. #Disabled for now, until we understand why this was even implemented
  377. # if self.datax is not None:
  378. # finedelays = self.datax - np.min(self.datax)
  379. #finedelays = finedelays/100.0
  380. if not turnbyturn:
  381. #'array' should have numBunches many entries. So 'a' should now be
  382. # [0,1,2,3,4 ... NumBunches]
  383. a = np.arange(0, len(array))
  384. #Now 'a' should be numADC*numBunches long and internally repeat the
  385. #same each element of the previous array len(selector)-times in a row
  386. #Think [0,0,0,0, 1,1,1,1, 2,2,2,2 ... len(array)], if len(selector)
  387. #would be 4
  388. a = np.repeat(a, len(selector))
  389. #But ... a already IS 1-dimensional? Whay reshape it with -1 AGAIN?
  390. #a = np.reshape(a, -1)
  391. #And finally, we turn a into a np.float type array...
  392. a = np.array(a, dtype=np.float)
  393. else:
  394. a = np.array(np.reshape(np.tile(np.repeat(np.arange(0, 184), len(selector)), len(array)//184),-1),dtype=np.float)
  395. b = np.reshape(np.repeat([finedelays[np.array(selector)]], len(array),0), -1)
  396. orig_xs = a+b
  397. # 'a' is a list of all the bunch-bumbers (basically X-Coordinates) which
  398. # the individual datapoints from the ADCs belong to.
  399. # 'b' is a list of all fine-delays for all the ADCs, repeated for numBunches times.
  400. # This means 'a+b' is X-Coordinates with respect to the individual
  401. # delays of the ADCs. The "calibrated" X-Coordinates, so to say
  402. # Flatten array
  403. array = array.flatten()
  404. # Okay, so if I understand this correctly, 'array' should now be
  405. # basically just one long vector of values with all the the ADCs in
  406. # sequence.
  407. # Basically:
  408. # [ADC 1 Bunch1, ADC 1 Bunch2, ADC 1 Bunch3 ... ADC 1 Bunch n,
  409. # ADC 2 Bunch1, ADC 2 Bunch2, ADC 2 Bunch3 ... ADC 2 Bunch n,
  410. # ...
  411. # ADC M Bunch1, ADC M Bunch2, ADC M Bunch3 ... ADC M Bunch n]
  412. if turnbyturn and mean:
  413. array = np.mean(array.reshape((-1, 184, len(selector))),0)
  414. orig_xs = np.mean(orig_xs.reshape((-1, 184, len(selector))),0)
  415. array = array.reshape(-1)
  416. orig_xs = orig_xs.reshape(-1)
  417. #print(adc)
  418. ret = [np.array([orig_xs, array])]
  419. if adc not in selector:
  420. ret.append(np.array([0,0]))
  421. return ret
  422. #FIXME: I assume the check for ==6 is because of the 'Working channels'
  423. # thing that used to be in a previous version?
  424. if len(selector) == 6:
  425. if adc > 4:
  426. adc -= 2
  427. while adc not in selector:
  428. adc -= 1
  429. if adc < 0:
  430. adc = self.adcNumber
  431. ret.append(np.array([orig_xs[adc::len(selector)], array[adc::len(selector)]]))
  432. # Okay, so what comes out of this is an array with 2x2xnumBunches size.
  433. # [0][0] contains 'orig_xs', telling which element belongs to which X level
  434. # [0][1] contains the flattened ADC array, giving the Y levels for each element
  435. # [1][0] and [1][1] are the same, but for only 1 specific ADC, so the
  436. # plotting can color that one ADC differently. Which is completely
  437. # nonesensical, because we could have just pulled that data out [0] either
  438. # way...
  439. return ret
  440. def loadFromRawData(self, rawData):
  441. print('loadfromrawdata')
  442. #self.printData(rawData[:32+32])
  443. #print('-------------------')
  444. headerInfo = self.dataHasHeader(rawData)
  445. if True in headerInfo:
  446. #logging.vinfo("Header detected.")
  447. # We read words of 4 bytes each
  448. self.header = self.parseHeader(rawData, headerInfo)
  449. spliceWords = HEADER_SIZE_BYTES // 4
  450. rawData = rawData[spliceWords:]
  451. else:
  452. #logging.vinfo("No Header detected.")
  453. self.header = None
  454. self.array = self.decodeData(rawData)
  455. self.dataRead=True
  456. def printData(self, data):
  457. try:
  458. for line in range(len(data)//4):
  459. print('0x{:08x}: 0x{:08x} 0x{:08x} 0x{:08x} 0x{:08x}'.format(line*16, data[line*4+0], data[line*4+1], data[line*4+2], data[line*4+3]))
  460. except:
  461. traceback.print_exc()
  462. def loadFromFile(self, frm, to):
  463. if self.noFile:
  464. return False
  465. if self.dataRead == False:
  466. if '.npy' in self.fileName:
  467. self.type = RAW_FILE_NPY
  468. self.array = np.load(self.fileName)
  469. elif '.out' in self.fileName:
  470. self.type = RAW_FILE
  471. self.fp = np.memmap(self.fileName, dtype='uint32')
  472. #print('loadfromfile')
  473. #self.printdata(self.fp[:32+32])
  474. #print('-------------------')
  475. headerInfo = self.dataHasHeader(self.fp)
  476. if True in headerInfo:
  477. #logging.vinfo("Header detected.")
  478. # We read words of 4 bytes each
  479. self.header = self.parseHeader(self.fp, headerInfo)
  480. #if self.header == dict():
  481. # self.getFromLog()
  482. spliceWords = HEADER_SIZE_BYTES // 4
  483. self.fp = self.fp[spliceWords:]
  484. else:
  485. #logging.vinfo("No Header detected.")
  486. self.header = None
  487. self.getFromLog()
  488. self.adcNumber = self.dataAdcCount(self.fp)
  489. self.dataRead=True
  490. if to == -1:
  491. if self.to != to:
  492. if self.type == RAW_FILE:
  493. self.array = self.decodeData(self.fp[frm*self.adcNumber//2:])
  494. self.to = to
  495. return True
  496. return False
  497. tto = to+184*3
  498. if self.frm > frm or self.to < tto:
  499. if self.type == RAW_FILE:
  500. self.array = self.decodeData(self.fp[frm*self.adcNumber//2:tto*self.adcNumber//2])
  501. self.frm = frm
  502. self.to = tto
  503. return True
  504. return False
  505. def decodeData(self, data):
  506. #This method assumes any potential header data has already been removed
  507. self.adcNumber = self.dataAdcCount(data)
  508. try:
  509. end = np.where(data==0xDEADDEAD)[0][0]
  510. data = data[:end]
  511. except Exception as e:
  512. #FIXME: No error handling!?
  513. #Is not finding a filling pattern actually an exception?
  514. #Shouldn't this be EXPECTED?
  515. #print('decode_data', e)
  516. pass
  517. #data = data[np.where(data != 0xDEADDEAD)] # This is the new filling
  518. #print('len data', len(data))
  519. # Make sure we read multiple of adcNumber
  520. data = data[:self.adcNumber * int((math.floor(data.size // self.adcNumber)))]
  521. #print('len data', len(data))
  522. bunch_low = data & 0xfff
  523. bunch_high = np.right_shift(data, 12) & 0xfff
  524. #FIXME: Same as with the adcNumber thing. Why does the mask use 12 bits,
  525. # if we shift all but 8 bits to the right?
  526. bunch_number = np.right_shift(data, 24) & 0xfff
  527. bunch_low = bunch_low.reshape(-1, self.adcNumber)
  528. bunch_high = bunch_high.reshape(-1, self.adcNumber)
  529. if self.invert:
  530. #FIXME: What's the logic behind this math...?
  531. #Isn't this the same as 2x2048 - bunch ?
  532. bunch_high = 2048 - (bunch_high-2048)
  533. bunch_low = 2048 - (bunch_low-2048)
  534. result = np.empty((bunch_low.shape[0] + bunch_high.shape[0], self.adcNumber+1), dtype=np.uint16)
  535. result[0::2,:self.adcNumber] = bunch_low
  536. result[1::2,:self.adcNumber] = bunch_high
  537. result[0::2, self.adcNumber] = bunch_number[::self.adcNumber]
  538. result[1::2, self.adcNumber] = bunch_number[::self.adcNumber] + 1
  539. #result = result[:int(self.bunchesPerTurn * (math.floor(result.shape[0] // self.bunchesPerTurn))), :]
  540. if self.shiftFMC2:
  541. if self.v: print('shift FMC2 by ', self.shiftFMC2)
  542. #print('decode_data ', result.shape)
  543. tmp = []
  544. for i in range(self.adcNumber+1):
  545. if self.shiftFMC2 > 0:
  546. if i < 4:
  547. tmp.append(result[self.shiftFMC2:,i])
  548. else:
  549. tmp.append(result[:-self.shiftFMC2,i])
  550. else:
  551. shift = self.shiftFMC2*(-1)
  552. if i < 4:
  553. tmp.append(result[:-shift:,i])
  554. else:
  555. tmp.append(result[shift:,i])
  556. result = np.array(tmp, dtype=np.uint16).T
  557. result = result[:int(self.bunchesPerTurn * (math.floor(result.shape[0] // self.bunchesPerTurn))), :]
  558. if self.v: print('decode_data ', result.shape)
  559. #print(result)
  560. return result
  561. def dataAdcCount(self, data):
  562. #FIXME: Shift-Right would also clone any sign-bits on the very far left
  563. # end of the data repeatedly, when shifting to the right. This might
  564. # produce incorrect results, since we shift all but 8 bits off the data,
  565. # but then check for 12 bits (0xFFF), meaning we might catch up to 4
  566. # 'cloned' sign bits in the mask
  567. #Additionally, why do we even shift in the first place...?
  568. #We are using a mask anyway, why not just make the mask use the correct
  569. #bits...? And why do we apply the shift and the mask to the ENTIRE
  570. #DATASET, when all we end up checking is just the first 10 entries...?
  571. bunch_number = np.right_shift(data, 24) & 0xfff
  572. ctr = 0
  573. b0 = bunch_number[0]
  574. for i in range(10):
  575. if b0 == bunch_number[i]:
  576. ctr += 1
  577. else:
  578. break
  579. if ctr < 4:
  580. ctr = 4
  581. if self.v: print('ADC number ', ctr)
  582. #FIXME: Where is the case with 8 ADCs?
  583. #What about any other number of ADCs?
  584. if ctr < 4:
  585. ctr = 4
  586. #return 8
  587. return ctr
  588. def dataHasHeader(self, data):
  589. possible_header = data[0:HEADER_SIZE_BYTES//4]
  590. kapture2 = (possible_header[0] & 0xFF000000) != 0
  591. back = possible_header[-1] & 0xF8888888 == 0xF8888888
  592. front = possible_header[0] & 0xF8888888 == 0xF8888888
  593. if self.v: print("has head", (front, back, kapture2))
  594. return (front, back, kapture2)
  595. def parseHeader(self, data, header_info, verbose=False):
  596. """
  597. Not supported for KAPTURE-2
  598. """
  599. #FIXME: Good to know, but what prevents me from wrongly using it anyway?
  600. global HEADER_SIZE_BYTES
  601. if header_info[2] == True:
  602. HEADER_SIZE_BYTES = 64
  603. header = data[0:HEADER_SIZE_BYTES//4]
  604. parsed = dict()
  605. if verbose: print('parsing header')
  606. # print([format(b, '#08X') for b in header])
  607. try:
  608. assert header[0] == 0xf8888888 or header[7] == 0xf8888888, 'Field 8 is saved for data'
  609. assert header[1] == 0xf7777777 or header[6] == 0xf7777777, 'Field 7 is saved for data'
  610. assert header[2] == 0xf6666666 or header[5] == 0xf6666666, 'Field 6 is saved for data'
  611. if header[0] == 0xf8888888 and header[1] == 0xf7777777 and header[2] == 0xf6666666:
  612. header = header[::-1]
  613. # TODO: what kind of timestamp is this? counts with appr. 25MHz up?!
  614. parsed['timestamp'] = header[4]
  615. assert header[3] >> 8 == 0, 'Highest 6 x 4 bits of field 3 is supposed to be zero'
  616. parsed['delay_adc4'] = header[3] & 0xf
  617. parsed['delay_adc3'] = header[3] >> 4 & 0xf
  618. assert header[2] >> 16 == 0, 'Highest 4 x 4 bits of field 2 is supposed to be zero'
  619. parsed['delay_adc2'] = header[2] & 0xf
  620. parsed['delay_adc1'] = header[2] >> 4 & 0xf
  621. parsed['delay_th'] = header[2] >> 8 & 0xf
  622. parsed['delay_fpga'] = header[2] >> 12 & 0xf
  623. parsed['fine_delay_adc'] = np.array([header[1] & 0xff,
  624. header[1] >> 8 & 0xff,
  625. header[1] >> 16 & 0xff,
  626. header[1] >> 24 & 0xff])
  627. assert header[0] >> 28 == 0xF, 'Highest 4 bits of field 0 is supposed to be 0xF'
  628. parsed['skip_turns'] = header[0] & 0xfffffff
  629. if verbose: print(parsed)
  630. except Exception as e:
  631. #FIXME: So... if the decoding from header breaks, we just return a
  632. #broken 'parsed' object!?
  633. pass
  634. #traceback.print_exc()
  635. return parsed
  636. def readLogfile(self):
  637. """
  638. return: dict with all information present in the Logfile
  639. """
  640. if self.noFile:
  641. return None
  642. #FIXME: Why is the name of the Log File hardcoded!?
  643. logFile = os.path.join(os.path.dirname(self.fileName), 'Measurement_board_6028.log')
  644. if not os.path.isfile(logFile):
  645. return None
  646. log = self._readLogfile(logFile)
  647. file = os.path.basename(self.fileName)
  648. for entry in log:
  649. try:
  650. if entry['Filename'] == file:
  651. return entry
  652. except:
  653. pass
  654. return None
  655. #FIXME: Why is this split into one private and one public method?
  656. def _readLogfile(self, file):
  657. defaultKeys = ['Number of Turns', 'Number of Skipped Turns', 'T/H Delay', '25ps Delay', 'ADC 1 Delay', 'ADC 2 Delay', 'ADC 3 Delay', 'ADC 4 Delay', 'ADC Delays', "25ps Delay 2", "T/H Delay 2", 'Stage Step']
  658. out = []
  659. with open(file,'r') as f:
  660. log = f.read().split('---')[1:]
  661. for entry in log:
  662. tmp={}
  663. ptr = 0
  664. for item in defaultKeys:
  665. tmp[item] = '?'
  666. if item == '25ps Delay 2':
  667. tmp[item] = '4'
  668. for item in entry.split('\n')[1:]:
  669. t = item.split(':')
  670. if len(t) == 1:
  671. tmp[str(ptr)] = t[0].strip('\t')
  672. ptr+=1
  673. else:
  674. tmp[str(t[0]).strip('\t')] = t[1].strip(' ')
  675. out.append(tmp)
  676. return out
  677. def getFromLog(self):
  678. """
  679. used to get information from the Logfile.
  680. """
  681. print('getFromLog')
  682. self.log = self.readLogfile()
  683. if self.log == None:
  684. print('no Log found')
  685. return False
  686. try:
  687. self.isAcquisition=False
  688. try:
  689. if "Acquisition" in self.log['0']:
  690. self.isAcquisition = True
  691. elif "Acquisition" in self.log['1']:
  692. self.isAcquisition = True
  693. except:
  694. pass
  695. try:
  696. self.skippedTurns = int(self.log['Number of Skipped Turns'])
  697. except:
  698. pass
  699. self.c330 = int(self.log['T/H Delay'])
  700. self.c25 = int(self.log['25ps Delay'])
  701. self.f = np.array([float(v) for v in self.log['ADC Delays'][1:-1].split(',')])
  702. try:
  703. self.datax = np.array([float(v) for v in self.log['datax'][1:-1].split(',')])
  704. except:
  705. self.datax = None
  706. pass
  707. self.adcNumber = len(self.f)
  708. file = self.log['Filename'].split('_')[-1]
  709. self.swap_adc = float(file[:-4]) < 1543859487
  710. if self.swap_adc and self.adcNumber > 4:
  711. if self.v: print('swapping ADC', self.log['Filename'])
  712. t = np.array(self.f[4:])
  713. self.f[4:] = self.f[:4]
  714. self.f[:4] = t
  715. self.c25b = int(self.log['25ps Delay 2'])
  716. try:
  717. self.stepper = int(self.log['Stage Step'])
  718. except:
  719. self.stepper = None
  720. try:
  721. self.workingChannels = np.array([float(v) for v in self.log['Working Channels'][1:-1].split(',')])
  722. except:
  723. self.workingChannels = None
  724. try:
  725. self.shiftFMC2 = int(self.log['shiftFMC2'])
  726. except:
  727. pass
  728. #print('get good')
  729. return True
  730. except:
  731. #print('getFromLog ', self.fileName)
  732. #traceback.print_exc()
  733. pass
  734. return False
  735. def _pad_array(self, array):
  736. #return array
  737. height, width = array.shape
  738. pwidth = int(2**np.floor(np.log2(width)))
  739. padded = np.zeros((height, pwidth))
  740. padded[:, :width] = array[:, :pwidth]
  741. return padded
  742. def _fft(self, array, is1D=False):
  743. start = time.time()
  744. print('fft', array.shape)
  745. if is1D:
  746. freqs = np.fft.rfft(array)
  747. else:
  748. freqs = np.fft.rfft(self._pad_array(array))
  749. #logging.debug("np fft: {} s".format(time.time() - start))
  750. print('FFT done {:.2f} s'.format(time.time() - start))
  751. return freqs