diff --git a/xgm.py b/xgm.py
index b17fbf4ff6ad7bc16ed7c61b66986f6f9629a3a1..b638e270c6c78caee9ad3a26d4a481ae73dc60fa 100644
--- a/xgm.py
+++ b/xgm.py
@@ -187,6 +187,7 @@ def selectSASEinXGM(data, sase='sase3', xgm='SCS_XGM'):
             result = xr.concat([result, temp], dim='trainId')
     return result
 
+
 def calcContribSASE(data, sase='sase1', xgm='SA3_XGM'):
     ''' Calculate the relative contribution of SASE 1 or SASE 3 pulses 
         for each train in the run. Supports fresh bunch, dedicated trains
@@ -216,6 +217,7 @@ def calcContribSASE(data, sase='sase1', xgm='SA3_XGM'):
     else:
         return 1 - contrib
 
+
 def filterOnTrains(data, key='sase3'):
     ''' Removes train ids for which there was no pulse in sase='sase1' or 'sase3' branch
         
@@ -230,6 +232,110 @@ def filterOnTrains(data, key='sase3'):
     res = data.where(data[key]>0, drop=True)
     return res
 
+
+def calibrateXGMs(data, rollingWindow=200, 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').
+        One has to take into account the possible signal created by SASE1 pulses. In the
+        tunnel, this signal is usually large enough to be read by the XGM and the relative
+        contribution C of SASE3 pulses to the overall signal is computed.
+        In the tunnel, the calibration F is defined as:
+            F = E_slow / E_fast_avg, where
+        E_fast_avg is the rolling average (with window rollingWindow) of the fast signal.
+        In SCS XGM, the signal from SASE1 is usually in the noise, so we calculate the 
+        average over the pulse-resolved signal of SASE3 pulses only and calibrate it to the 
+        slow signal modulated by the SASE3 contribution:
+            F = (N1+N3) * E_avg * C/(N3 * E_fast_avg_sase3), where N1 and N3 are the number 
+        of pulses in SASE1 and SASE3, E_fast_avg_sase3 is the rolling average (with window
+        rollingWindow) of the SASE3-only fast signal.
+        
+        Inputs:
+            data: xarray Dataset
+            rollingWindow: length of running average to calculate E_fast_avg
+            plot: boolean, plot the calibration output
+            
+        Output:
+            factors: numpy ndarray of shape 1 x 2 containing 
+                     [XTD10 calibration factor, SCS calibration factor]
+    '''
+    noSCS = noSA3 = False
+    sa3_calib_factor = None
+    scs_calib_factor = None
+    if 'SCS_XGM' not in data:
+        print('no SCS XGM data. Skipping calibration for SCS XGM')
+        noSCS = True
+    if 'SA3_XGM' not in data:
+        print('no SASE3 XGM data. Skipping calibration for SASE3 XGM')
+        noSA3 = True
+    if noSCS and noSA3:
+        return np.array([None, None])
+    start = 0
+    stop = None
+    npulses = data['npulses_sase3']
+    ntrains = npulses.shape[0]
+    # 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]):
+        print('Warning: Number of pulses per train changed during the run!')
+        start = np.argmax(npulses.values)
+        stop = ntrains + np.argmax(npulses.values[::-1]) - 1
+        if stop - start < rollingWindow:
+            print('not enough consecutive data points with the largest number of pulses per train')
+        start += rollingWindow
+        stop = np.min((ntrains, stop+rollingWindow))
+    # Calibrate SASE3 XGM with all signal from SASE1 and SASE3
+    if not noSA3:
+        xgm_avg = data['SA3_XGM'].where(data['SA3_XGM'] != 1.0).mean(axis=1)
+        rolling_sa3_xgm = xgm_avg.rolling(trainId=rollingWindow).mean()
+        ratio = data['SA3_XGM_SLOW']/rolling_sa3_xgm
+        sa3_calib_factor = ratio[start:stop].mean().values
+        print('calibration factor SA3 XGM: %f'%sa3_calib_factor)
+
+    # Calibrate SCS XGM with SASE3-only contribution
+    sa3contrib = calcContribSASE(data, 'sase3', 'SA3_XGM')
+    if not noSCS:
+        scs_sase3_fast = selectSASEinXGM(data, 'sase3', 'SCS_XGM').mean(axis=1)
+        meanFast = scs_sase3_fast.rolling(trainId=rollingWindow).mean()
+        ratio = ((data['npulses_sase3']+data['npulses_sase1']) *
+                 data['SCS_XGM_SLOW'] * sa3contrib) / (meanFast * data['npulses_sase3'])
+        scs_calib_factor = ratio[start:stop].median().values
+        print('calibration factor SCS XGM: %f'%scs_calib_factor)
+        
+    if plot:
+        plt.figure(figsize=(8,8))
+        plt.subplot(211)
+        plt.title('E[uJ] = %.2f x IntensityTD' %(sa3_calib_factor))
+        plt.plot(data['SA3_XGM_SLOW'], label='SA3 slow', color='C1')
+        plt.plot(rolling_sa3_xgm*sa3_calib_factor,
+                 label='SA3 fast signal rolling avg', color='C4')
+        plt.ylabel('Energy [uJ]')
+        plt.xlabel('train in run')
+        plt.legend(loc='upper left', fontsize=10)
+        plt.twinx()
+        plt.plot(xgm_avg*sa3_calib_factor, label='SA3 fast signal train avg', alpha=0.2, color='C4')
+        plt.ylabel('Calibrated SA3 fast signal [uJ]')
+        plt.legend(loc='lower right', fontsize=10)
+
+        plt.subplot(212)
+        plt.title('E[uJ] = %.2f x HAMP' %scs_calib_factor)
+        plt.plot(data['SCS_XGM_SLOW'], label='SCS slow (all SASE)', color='C0')
+        slow_avg_sase3 = data['SCS_XGM_SLOW']*(data['npulses_sase1']
+                                                    +data['npulses_sase3'])*sa3contrib/data['npulses_sase3']
+        plt.plot(slow_avg_sase3, label='SCS slow (SASE3 only)', color='C1')
+        plt.plot(meanFast*scs_calib_factor, label='SCS HAMP rolling avg', color='C2')
+        plt.ylabel('Energy [uJ]')
+        plt.xlabel('train in run')
+        plt.legend(loc='upper left', fontsize=10)
+        plt.twinx()
+        plt.plot(scs_sase3_fast*scs_calib_factor, label='SCS HAMP train avg', alpha=0.2, color='C2')
+        plt.ylabel('Calibrated HAMP signal [uJ]')
+        plt.legend(loc='lower right', fontsize=10)
+        plt.tight_layout()
+
+    return np.array([sa3_calib_factor, scs_calib_factor])
+
+
 def mcpPeaks(data, intstart, intstop, bkgstart, bkgstop, t_offset=1760, mcp=1, npulses=None):
     ''' Computes peak integration from raw MCP traces.
     
@@ -265,6 +371,7 @@ def mcpPeaks(data, intstart, intstop, bkgstart, bkgstop, t_offset=1760, mcp=1, n
         results[:,i] = np.trapz(data[keyraw][:,a:b] - bg, axis=1)
     return results
 
+
 def getTIMapd(data, mcp=1, use_apd=True, intstart=None, intstop=None,
               bkgstart=None, bkgstop=None, t_offset=1760, npulses=None):
     ''' Extract peak-integrated data from TIM where pulses are from SASE3 only.
@@ -323,6 +430,7 @@ def getTIMapd(data, mcp=1, use_apd=True, intstart=None, intstop=None,
             tim = xr.concat([tim, temp], dim='trainId')
     return tim
     
+
 def calibrateTIM(data, rollingWindow=200, mcp=1, use_apd=True, intstart=None, intstop=None,
               bkgstart=None, bkgstop=None, t_offset=1760, npulses_apd=None):
     ''' Calibrate TIM signal (Peak-integrated signal) to the slow ion signal of SCS_XGM
@@ -343,8 +451,7 @@ def calibrateTIM(data, rollingWindow=200, mcp=1, use_apd=True, intstart=None, in
         
         Inputs:
             data: xarray Dataset
-            rolling window: number of trains to perform a running average on to match
-                            TIM-avg and E_slow
+            rollingWindow: length of running average to calculate TIM_avg
             mcp: MCP channel
             use_apd: boolean. If False, the TIM pulse peaks are extract from raw traces using
                      getTIMapd
@@ -377,5 +484,6 @@ def calibrateTIM(data, rollingWindow=200, mcp=1, use_apd=True, intstart=None, in
     ratio = ((data['npulses_sase3']+data['npulses_sase1']) *
              data['SCS_XGM_SLOW'] * sa3contrib) / (avgFast*data['npulses_sase3'])
     F = float(ratio[start:stop].median().values)
+    #print('calibration factor TIM: %f'%F)
     return F