diff --git a/xgm.py b/xgm.py index ecb78efd9b5048bdd5def72a936db7442dca4e12..f60f8b4cb117011ce7823ed0c9ca4fda5b82ab8c 100644 --- a/xgm.py +++ b/xgm.py @@ -216,69 +216,89 @@ def saseContribution(data, sase='sase1', xgm='XTD10_XGM'): else: return 1 - contrib -def calibrateXGMs(data, rollingWindow=200, plot=False): +def calibrateXGMs(data, allPulses=False, plot=False, display=False): ''' Calibrate the fast (pulse-resolved) signals of the XTD10 and SCS XGM (read in intensityTD property) to the respective slow ion signal - (photocurrent read by Keithley, channel 'pulseEnergy.photonFlux.value'). - If the sase-resolved signal (introduced in May 2019) are recorded, the - calibration is defined as the mean ratio between the photocurrent and - the low-pass slowTrain signal. Otherwise, calibrateXGMsFromAllPulses() - is called. + (photocurrent read by Keithley, key 'pulseEnergy.photonFlux.value'). + If the sase-resolved averaged signals ("slowTrain", introduced in May + 2019) are recorded, the calibration is defined as the mean ratio + between the photonFlux and the slowTrain signal. Otherwise, the + averaged fast signals are computed using a rolling average. Inputs: data: xarray Dataset - rollingWindow: length of running average to calculate E_fast_avg - plot: boolean, plot the calibration output + allPulses: if True, uses "XTD10_XGM" and "SCS_XGM" arrays and + computes the relative contributions of SASE1 and SASE3 to + photonFlux. This should be more accurate in cases where the + number of SASE1 pulses is large and/or the SASE1 pulse + intensity cannot be neglected. + plot: bool, plot the calibration output + display: bool, displays info if True Output: - factors: numpy ndarray of shape 1 x 2 containing - [XTD10 calibration factor, SCS calibration factor] + ndarray with [XTD10 calibration factor, SCS calibration factor] ''' - noSlowTrain=False + if allPulses: + return calibrateXGMsFromAllPulses(data, plot) + hasSlowTrain=[True,True] results = np.array([np.nan, np.nan], dtype=float) - slowTrainTraces = [] + slowTrainData = [] for i,whichXgm in enumerate(['XTD10', 'SCS']): - if f'{whichXgm}_slowTrain' not in data: + #1. Try to load fast data averages (in DAQ since May 2019) + if f'{whichXgm}_slowTrain' in data: + if display: + print(f'Using fast data averages (slowTrain) for {whichXgm}') + slowTrainData.append(data[f'{whichXgm}_slowTrain']) + else: mnemo = tb.mnemonics[f'{whichXgm}_slowTrain'] if mnemo['key'] in data.attrs['run'].keys_for_source(mnemo['source']): - slowTrain = data.attrs['run'].get_array(mnemo['source'], mnemo['key']) + if display: + print(f'Using fast data averages (slowTrain) for {whichXgm}') + slowTrainData.append(data.attrs['run'].get_array(mnemo['source'], mnemo['key'])) + #2. Calculate fast data average from fast data else: - noSlowTrain=True - print('No averages of fast data (slowTrain) available.'+ + if display: + print(f'No averages of fast data (slowTrain) available for {whichXgm}.'+ ' Attempting calibration from fast data.') - break - else: - slowTrain = data[f'{whichXgm}_slowTrain'] - results[i] = np.mean(data[f'{whichXgm}_photonFlux']/slowTrain) - slowTrainTraces.append(slowTrain) - if noSlowTrain: - return calibrateXGMsFromAllPulses(data, rollingWindow, plot) - else: - if plot: - plt.figure(figsize=(8,4)) - plt.subplot(211) - plt.plot(data['XTD10_photonFlux'], label='XTD10 photon flux') - plt.plot(slowTrainTraces[0]*results[0], label='calibrated XTD10 fast signal') - plt.ylabel(r'Energy [$\mu$J]') - plt.xlabel('train Id') - plt.legend(fontsize=8, loc='upper left') - plt.twinx() - plt.plot(slowTrainTraces[0], label='uncalibrated XTD10 fast signal', color='C4') - plt.ylabel(r'Uncalibrated energy') - plt.legend(fontsize=8, loc='upper right') - plt.subplot(212) - plt.plot(data['SCS_photonFlux'], label='SCS photon flux') - plt.plot(slowTrainTraces[1]*results[1], label='calibrated SCS fast signal') - plt.ylabel(r'Energy [$\mu$J]') - plt.xlabel('train Id') - plt.legend(fontsize=8, loc='upper left') - plt.twinx() - plt.plot(slowTrainTraces[1], label='uncalibrated SCS fast signal', color='C4') - plt.ylabel(r'Uncalibrated energy') - plt.legend(fontsize=8, loc='upper right') - return results + if f'{whichXgm}_SA3' in data: + if display: + print(f'Calculating slowTrain from SA3 for {whichXgm}') + slowTrainData.append(data[f'{whichXgm}_SA3'].rolling(trainId=200 + ).mean().mean(axis=1)) + elif f'{whichXgm}_XGM' in data: + sa3 = selectSASEinXGM(data, xgm=f'{whichXgm}_XGM') + slowTrainData.append(sa3.rolling(trainId=200).mean().mean(axis=1)) + else: + hasSlowTrain[i]=False + if hasSlowTrain[i]: + results[i] = np.mean(data[f'{whichXgm}_photonFlux']/slowTrainData[i]) + if display: + print(f'Calibration factor {whichXgm} XGM: {results[i]}') + if plot: + plt.figure(figsize=(8,4)) + plt.subplot(211) + plt.plot(data['XTD10_photonFlux'], label='XTD10 photon flux') + plt.plot(slowTrainData[0]*results[0], label='calibrated XTD10 fast signal') + plt.ylabel(r'Energy [$\mu$J]') + plt.legend(fontsize=8, loc='upper left') + plt.twinx() + plt.plot(slowTrainData[0], label='uncalibrated XTD10 fast signal', color='C4') + plt.ylabel(r'Uncalibrated energy') + plt.legend(fontsize=8, loc='upper right') + plt.subplot(212) + plt.plot(data['SCS_photonFlux'], label='SCS photon flux') + plt.plot(slowTrainData[1]*results[1], label='calibrated SCS fast signal') + plt.ylabel(r'Energy [$\mu$J]') + plt.xlabel('train Id') + plt.legend(fontsize=8, loc='upper left') + plt.twinx() + plt.plot(slowTrainData[1], label='uncalibrated SCS fast signal', color='C4') + plt.ylabel(r'Uncalibrated energy') + plt.legend(fontsize=8, loc='upper right') + plt.tight_layout() + return results -def calibrateXGMsFromAllPulses(data, rollingWindow=200, plot=False): +def calibrateXGMsFromAllPulses(data, plot=False): ''' Calibrate the fast (pulse-resolved) signals of the XTD10 and SCS XGM (read in intensityTD property) to the respective slow ion signal (photocurrent read by Keithley, channel 'pulseEnergy.photonFlux.value'). @@ -322,6 +342,7 @@ def calibrateXGMsFromAllPulses(data, rollingWindow=200, plot=False): stop = None npulses = data['npulses_sase3'] ntrains = npulses.shape[0] + rollingWindow=200 # First, in case of change in number of pulses, locate a region where # the number of pulses is maximum. if not np.all(npulses == npulses[0]):