diff --git a/xgm.py b/xgm.py index 78bd1368666a99a70a27fab56413bdc8fc9e42dd..5b292f64a1a03c9d5f861cf6c53fd5f12729b74c 100644 --- a/xgm.py +++ b/xgm.py @@ -114,7 +114,7 @@ def repRate(data, sase='sase3'): return f -def selectSASEinXGM(data, sase='sase3', xgm='SCS_XGM'): +def selectSASEinXGM(data, sase='sase3', xgm='SCS_XGM', sase3First=True, npulses=None): ''' Extract SASE1- or SASE3-only XGM data. There are various cases depending on i) the mode of operation (10 Hz with fresh bunch, dedicated trains to one SASE, pulse on demand), @@ -125,6 +125,8 @@ def selectSASEinXGM(data, sase='sase3', xgm='SCS_XGM'): data: xarray Dataset containing xgm data sase: key of sase to select: {'sase1', 'sase3'} xgm: key of xgm to select: {'SA3_XGM', 'SCS_XGM'} + sase3First: bool, optional. Used in case no bunch pattern was recorded + npulses: int, optional. Required in case no bunch pattern was recorded. Output: DataArray that has all trainIds that contain a lasing @@ -132,6 +134,25 @@ def selectSASEinXGM(data, sase='sase3', xgm='SCS_XGM'): that sase in the run. The missing values, in case of change of number of pulses, are filled with NaNs. ''' + if sase not in data: + print('Missing bunch pattern info!') + if npulses is None: + raise TypeError('npulses argument is required when bunch pattern ' + + 'info is missing.') + print('Retrieving {} SASE {} pulses assuming that '.format(npulses, sase[4]) + +'SASE {} pulses come first.'.format('3' if sase3First else '1')) + #in older version of DAQ, non-data numbers were filled with 0.0. + xgmData = data[xgm].where(data[xgm]!=0.0, drop=True) + xgmData = xgmData.fillna(0.0).where(xgmData!=1.0, drop=True) + if (sase3First and sase=='sase3') or (not sase3First and sase=='sase1'): + return xgmData[:,:npulses] + else: + if xr.ufuncs.isnan(xgmData).any(): + raise Exception('The number of pulses changed during the run. ' + 'This is not supported yet.') + else: + start=xgmData.shape[1]-npulses + return xgmData[:,start:start+npulses] result = None npulses_sa3 = data['npulses_sase3'] npulses_sa1 = data['npulses_sase1'] @@ -374,8 +395,9 @@ def mcpPeaks(data, intstart, intstop, bkgstart, bkgstop, mcp=1, t_offset=None, n bkgstart: trace index of background start bkgstop: trace index of background stop mcp: MCP channel number - t_offset: index separation between two pulses. If None, checks the - pulse pattern and determine the t_offset assuming mininum pulse + t_offset: index separation between two pulses. Needed if bunch + pattern info is not available. If None, checks the pulse + pattern and determine the t_offset assuming mininum pulse separation of 220 ns and digitizer resolution of 2 GHz. npulses: number of pulses. If None, takes the maximum number of pulses according to the bunch patter (field 'npulses_sase3') @@ -389,8 +411,8 @@ def mcpPeaks(data, intstart, intstop, bkgstart, bkgstop, mcp=1, t_offset=None, n raise ValueError("Source not found: {}!".format(keyraw)) if npulses is None: npulses = int(data['npulses_sase3'].max().values) - sa3 = data['sase3'].where(data['sase3']>1) if t_offset is None: + sa3 = data['sase3'].where(data['sase3']>1) if npulses > 1: #Calculate the number of pulses between two lasing pulses (step) step = sa3.where(data['npulses_sase3']>1, drop=True)[0,:2].values @@ -399,7 +421,7 @@ def mcpPeaks(data, intstart, intstop, bkgstart, bkgstop, mcp=1, t_offset=None, n t_offset = 440 * step else: t_offset = 1 - results = xr.DataArray(np.empty((sa3.shape[0], npulses)), coords=sa3.coords, + results = xr.DataArray(np.empty((data.trainId.shape[0], npulses)), coords=data[keyraw].coords, dims=['trainId', 'MCP{}fromRaw'.format(mcp)]) for i in range(npulses): a = intstart + t_offset*i @@ -412,11 +434,15 @@ def mcpPeaks(data, intstart, intstop, bkgstart, bkgstop, mcp=1, t_offset=None, n def getTIMapd(data, mcp=1, use_apd=True, intstart=None, intstop=None, - bkgstart=None, bkgstop=None, t_offset=None, npulses=None): + bkgstart=None, bkgstop=None, t_offset=None, npulses=None, + stride=1): ''' Extract peak-integrated data from TIM where pulses are from SASE3 only. If use_apd is False it calculates integration from raw traces. The missing values, in case of change of number of pulses, are filled with NaNs. + If no bunch pattern info is available, the function assumes that + SASE 3 comes first and that the number of pulses is fixed in both + SASE 1 and 3. Inputs: data: xarray Dataset containing MCP raw traces (e.g. 'MCP1raw') @@ -426,12 +452,28 @@ def getTIMapd(data, mcp=1, use_apd=True, intstart=None, intstop=None, bkgstop: trace index of background stop t_offset: index separation between two pulses mcp: MCP channel number - npulses: number of pulses to compute - + npulses: int, optional. Number of pulses to compute. Required if + no bunch pattern info is available. + stride: int, optional. Used to select pulses in the APD array if + no bunch pattern info is available. Output: tim: DataArray of shape trainId only for SASE3 pulses x N with N=max(number of pulses per train) ''' + if 'sase3' not in data: + print('Missing bunch pattern info!\n') + if npulses is None: + raise TypeError('npulses argument is required when bunch pattern ' + + 'info is missing.') + print('Retrieving {} SASE 3 pulses assuming that '.format(npulses) + + 'SASE 3 pulses come first.') + if use_apd: + tim = data['MCP{}apd'.format(mcp)][:,:npulses:stride] + else: + tim = mcpPeaks(data, intstart, intstop, bkgstart, bkgstop, mcp=mcp, + t_offset=t_offset, npulses=npulses) + return tim + sa3 = data['sase3'].where(data['sase3']>1, drop=True) npulses_sa3 = data['npulses_sase3'] maxpulses = int(npulses_sa3.max().values) @@ -752,7 +794,7 @@ def checkTimApdWindow(data, mcp=1, use_apd=True, intstart=None, intstop=None): def matchXgmTimPulseId(data, use_apd=True, intstart=None, intstop=None, bkgstart=None, bkgstop=None, t_offset=None, - npulses=None): + npulses=None, sase3First=True, stride=1): ''' Function to match XGM pulse Id with TIM pulse Id. Inputs: data: xarray Dataset containing XGM and TIM data @@ -764,18 +806,24 @@ def matchXgmTimPulseId(data, use_apd=True, intstart=None, intstop=None, bkgstart: trace index of background start bkgstop: trace index of background stop t_offset: index separation between two pulses - npulses: number of pulses to compute + npulses: number of pulses to compute. Required if no bunch + pattern info is available + sase3First: bool, needed if bunch pattern is missing. + stride: int, used to select pulses in the TIM APD array if + no bunch pattern info is available. Output: xr DataSet containing XGM and TIM signals with the share dimension 'pId'. Raw traces, raw XGM and raw APD are dropped. ''' - res = selectSASEinXGM(data, xgm='SCS_XGM').rename({'XGMbunchId':'pId'}).rename('SCS_XGM') + res = selectSASEinXGM(data, xgm='SCS_XGM', npulses=npulses, + sase3First=sase3First).rename({'XGMbunchId':'pId'}).rename('SCS_XGM') dropList = ['SCS_XGM'] mergeList = [res] if 'SA3_XGM' in data: - res2 = selectSASEinXGM(data, xgm='SA3_XGM').rename({'XGMbunchId':'pId'}).rename('SA3_XGM') + res2 = selectSASEinXGM(data, xgm='SA3_XGM', npulses=npulses, + sase3First=sase3First).rename({'XGMbunchId':'pId'}).rename('SA3_XGM') dropList.append('SA3_XGM') mergeList.append(res2) @@ -783,8 +831,8 @@ def matchXgmTimPulseId(data, use_apd=True, intstart=None, intstop=None, if 'MCP{}apd'.format(mcp) in data or 'MCP{}raw'.format(mcp) in data: MCPapd = getTIMapd(data, mcp=mcp, use_apd=use_apd, intstart=intstart, intstop=intstop,bkgstart=bkgstart, bkgstop=bkgstop, - t_offset=t_offset, - npulses=npulses).rename('MCP{}apd'.format(mcp)) + t_offset=t_offset, npulses=npulses, + stride=stride).rename('MCP{}apd'.format(mcp)) if use_apd: MCPapd = MCPapd.rename({'apdId':'pId'}) else: