diff --git a/xgm.py b/xgm.py
index 91f6f2fd55a0dabb206f7a182d620b230e0b24e7..5f7c3c092e51fa3e31c6df7e9613d865d188450d 100644
--- a/xgm.py
+++ b/xgm.py
@@ -195,7 +195,7 @@ def selectSASEinXGM(data, sase='sase3', xgm='SCS_XGM'):
     return result
 
 
-def calcContribSASE(data, sase='sase1', xgm='SA3_XGM'):
+def saseContribution(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
         and pulse on demand modes.
@@ -211,13 +211,11 @@ def calcContribSASE(data, sase='sase1', xgm='SA3_XGM'):
     '''
     xgm_sa1 = selectSASEinXGM(data, 'sase1', xgm=xgm)
     xgm_sa3 = selectSASEinXGM(data, 'sase3', xgm=xgm)
-    if np.all(xgm_sa1.trainId.isin(xgm_sa3.trainId).values) == False:
-        print('Dedicated mode')
-        r = xr.align(*[xgm_sa1, xgm_sa3], join='outer', exclude=['SA3_XGM_dim', 'SA1_XGM_dim'])
-        xgm_sa1 = r[0]
-        xgm_sa1.fillna(0)
-        xgm_sa3 = r[1]
-        xgm_sa3.fillna(0)
+    #Fill missing train ids with 0
+    r = xr.align(*[xgm_sa1, xgm_sa3], join='outer', exclude=['XGMbunchId'])
+    xgm_sa1 = r[0].fillna(0)
+    xgm_sa3 = r[1].fillna(0)
+
     contrib = xgm_sa1.sum(axis=1)/(xgm_sa1.sum(axis=1) + xgm_sa3.sum(axis=1))
     if sase=='sase1':
         return contrib
@@ -300,7 +298,7 @@ def calibrateXGMs(data, rollingWindow=200, plot=False):
         print('calibration factor SA3 XGM: %f'%sa3_calib_factor)
 
     # Calibrate SCS XGM with SASE3-only contribution
-    sa3contrib = calcContribSASE(data, 'sase3', 'SA3_XGM')
+    sa3contrib = saseContribution(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()
@@ -343,7 +341,7 @@ def calibrateXGMs(data, rollingWindow=200, plot=False):
     return np.array([sa3_calib_factor, scs_calib_factor])
 
 
-def mcpPeaks(data, intstart, intstop, bkgstart, bkgstop, t_offset=1760, mcp=1, npulses=None):
+def mcpPeaks(data, intstart, intstop, bkgstart, bkgstop, mcp=1, t_offset=None, npulses=None):
     ''' Computes peak integration from raw MCP traces.
     
         Inputs:
@@ -352,21 +350,32 @@ def mcpPeaks(data, intstart, intstop, bkgstart, bkgstop, t_offset=1760, mcp=1, n
             intstop: trace index of integration stop
             bkgstart: trace index of background start
             bkgstop: trace index of background stop
-            t_offset: index separation between two pulses
             mcp: MCP channel number
-            npulses: number of pulses
+            t_offset: index separation between two pulses. 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')
             
         Output:
-            results: DataArray with dims trainId x max(sase3 pulses)*1MHz/intra-train rep.rate 
+            results: DataArray with dims trainId x max(sase3 pulses) 
             
     '''
     keyraw = 'MCP{}raw'.format(mcp)
     if keyraw not in data:
         raise ValueError("Source not found: {}!".format(keyraw))
     if npulses is None:
-        npulses = int((data['sase3'].max().values + 1)/4)
-    sa3 = data['sase3'].where(data['sase3']>1)/4
-    sa3 -= sa3[:,0]
+        npulses = int(data['npulses_sase3'].max().values)
+    sa3 = data['sase3'].where(data['sase3']>1)
+    if t_offset is None:
+        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
+            step = int(step[1] - step[0])
+            #multiply by elementary samples length (220 ns @ 2 GHz = 440)
+            t_offset = 440 * step
+        else:
+            t_offset = 1
     results = xr.DataArray(np.empty((sa3.shape[0], npulses)), coords=sa3.coords,
                            dims=['trainId', 'MCP{}fromRaw'.format(mcp)])
     for i in range(npulses):
@@ -380,7 +389,7 @@ def mcpPeaks(data, intstart, intstop, bkgstart, bkgstop, t_offset=1760, mcp=1, n
 
 
 def getTIMapd(data, mcp=1, use_apd=True, intstart=None, intstop=None,
-              bkgstart=None, bkgstop=None, t_offset=1760, npulses=None):
+              bkgstart=None, bkgstop=None, t_offset=None, npulses=None):
     ''' 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
@@ -399,19 +408,36 @@ def getTIMapd(data, mcp=1, use_apd=True, intstart=None, intstop=None,
             tim: DataArray of shape trainId only for SASE3 pulses x N 
                  with N=max(number of pulses per train)
     '''
-    key = 'MCP{}apd'.format(mcp)
+    sa3 = data['sase3'].where(data['sase3']>1, drop=True)
+    npulses_sa3 = data['npulses_sase3']
+    maxpulses = int(npulses_sa3.max().values)
+    step = 1
+    if maxpulses > 1:
+        #Calculate the number of non-lasing pulses between two lasing pulses (step)
+        step = sa3.where(data['npulses_sase3']>1, drop=True)[0,:2].values
+        step = int(step[1] - step[0])
     if use_apd:
-        apd = data[key]
+        apd = data['MCP{}apd'.format(mcp)]
+        initialDelay = data.attrs['run'].get_array(
+                        'SCS_UTC1_ADQ/ADC/1', 'board1.apd.channel_0.initialDelay.value')[0].values
+        upperLimit = data.attrs['run'].get_array(
+                        'SCS_UTC1_ADQ/ADC/1', 'board1.apd.channel_0.upperLimit.value')[0].values
+        nsamples = upperLimit - initialDelay
+        npulses_per_apd = int(nsamples/440)
+        sa3 /= npulses_per_apd
     else:
-        apd = mcpPeaks(data, intstart, intstop, bkgstart, bkgstop, t_offset, mcp, npulses)
-    npulses_sa3 = data['npulses_sase3']
-    sa3 = data['sase3'].where(data['sase3']>1, drop=True)/4
+        apd = mcpPeaks(data, intstart, intstop, bkgstart, bkgstop, mcp=mcp, 
+                       t_offset=t_offset, npulses=npulses)
+        sa3 /= step
     sa3 -= sa3[:,0]
     sa3 = sa3.astype(int)
     if np.all(npulses_sa3 == npulses_sa3[0]):
         tim = apd[:, sa3[0].values]
         return tim
-    maxpulses = int(npulses_sa3.max().values)
+    
+    stride = 1
+    if use_apd:
+        stride = np.max([stride,int(step/npulses_per_apd)])
     diff = npulses_sa3.diff(dim='trainId')
     #only keep trainIds where a change occured:
     diff = diff.where(diff != 0, drop=True)
@@ -429,7 +455,7 @@ def getTIMapd(data, mcp=1, use_apd=True, intstart=None, intstop=None,
         else:
             l = idx_change[i+1]
         b = npulses_sa3[idx].values
-        temp = apd[idx:l,:maxpulses].copy()
+        temp = apd[idx:l,:maxpulses*stride:stride].copy()
         temp[:,b:] = np.NaN
         if tim is None:
             tim = temp
@@ -438,8 +464,8 @@ def getTIMapd(data, mcp=1, use_apd=True, intstart=None, intstop=None,
     return tim
 
 
-def calibrateTIM(data, rollingWindow=200, mcp=1, plot=False, use_apd=True, intstart=None, intstop=None,
-              bkgstart=None, bkgstop=None, t_offset=1760, npulses_apd=None):
+def calibrateTIM(data, rollingWindow=200, mcp=1, plot=False, use_apd=True, intstart=None,
+                 intstop=None, bkgstart=None, bkgstop=None, t_offset=None, npulses_apd=None):
     ''' Calibrate TIM signal (Peak-integrated signal) to the slow ion signal of SCS_XGM
         (photocurrent read by Keithley, channel 'pulseEnergy.photonFlux.value').
         The aim is to find F so that E_tim_peak[uJ] = F x TIM_peak. For this, we want to
@@ -468,7 +494,6 @@ def calibrateTIM(data, rollingWindow=200, mcp=1, plot=False, use_apd=True, intst
             bkgstart: trace index of background start
             bkgstop: trace index of background stop
             t_offset: index separation between two pulses
-            mcp: MCP channel number
             npulses_apd: number of pulses
             
         Output:
@@ -486,9 +511,8 @@ def calibrateTIM(data, rollingWindow=200, mcp=1, plot=False, use_apd=True, intst
             print('not enough consecutive data points with the largest number of pulses per train')
         start += rollingWindow
         stop = np.min((ntrains, stop+rollingWindow))
-        #print(start, stop)
     filteredTIM = getTIMapd(data, mcp, use_apd, intstart, intstop, bkgstart, bkgstop, t_offset, npulses_apd)
-    sa3contrib = calcContribSASE(data, 'sase3', 'SA3_XGM')
+    sa3contrib = saseContribution(data, 'sase3', 'SA3_XGM')
     avgFast = filteredTIM.mean(axis=1).rolling(trainId=rollingWindow).mean()
     ratio = ((data['npulses_sase3']+data['npulses_sase1']) *
              data['SCS_XGM_SLOW'] * sa3contrib) / (avgFast*data['npulses_sase3'])
@@ -505,9 +529,7 @@ def calibrateTIM(data, rollingWindow=200, mcp=1, plot=False, use_apd=True, intst
         ax.plot(avgFast*F, label='Calibrated TIM rolling avg', color='C2')
         ax.legend(loc='upper left', fontsize=8)
         ax.set_ylabel('Energy [$\mu$J]', size=10)
-        #ax2=ax#.twinx()
         ax.plot(filteredTIM.mean(axis=1)*F, label='Calibrated TIM train avg', alpha=0.2, color='C9')
-        #ax2.set_ylabel('Calibrated TIM (MCP{}) [uJ]'.format(mcp))
         ax.legend(loc='best', fontsize=8, ncol=2)
         plt.xlabel('train in run')
         
@@ -530,10 +552,6 @@ def calibrateTIM(data, rollingWindow=200, mcp=1, plot=False, use_apd=True, intst
         ax.hist(filteredTIM.values.flatten()*F, bins=50, rwidth=0.8)
         ax.set_ylabel('number of pulses', size=10)
         ax.set_xlabel('Pulse energy MCP{} [uJ]'.format(mcp), size=10)
-        #ax2 = ax.twiny()
-        #ax2.set_xlabel('MCP 1 APD')
-        #toRaw = lambda x: x/F
-        #ax2.set_xlim((toRaw(ax.get_xlim()[0]),toRaw(ax.get_xlim()[1])))
         ax.set_yscale('log')
         
         ax = plt.subplot(236)
@@ -565,3 +583,64 @@ def calibrateTIM(data, rollingWindow=200, mcp=1, plot=False, use_apd=True, intst
         plt.tight_layout()
 
     return F
+
+
+''' TIM calibration table
+    Dict with key= photon energy and value= array of polynomial coefficients for each MCP (1,2,3).
+    The polynomials correspond to a fit of the logarithm of the calibration factor as a function
+    of MCP voltage. If P is a polynomial and V the MCP voltage, the calibration factor (in microjoule
+    per APD signal) is given by -exp(P(V)).
+    This table was generated from the calibration of March 2019, proposal 900074, semester 201930, 
+    runs 69 - 111 (Ni edge):  https://in.xfel.eu/elog/SCS+Beamline/2323
+    runs 113 - 153 (Co edge): https://in.xfel.eu/elog/SCS+Beamline/2334
+    runs 163 - 208 (Fe edge): https://in.xfel.eu/elog/SCS+Beamline/2349
+'''
+tim_calibration_table = {
+    705.5: np.array([
+        [-6.85344690e-12,  5.00931986e-08, -1.27206912e-04, 1.15596821e-01, -3.15215367e+01],
+        [ 1.25613942e-11, -5.41566381e-08,  8.28161004e-05, -7.27230153e-02,  3.10984925e+01],
+        [ 1.14094964e-12,  7.72658935e-09, -4.27504907e-05, 4.07253378e-02, -7.00773062e+00]]),
+    779: np.array([
+        [ 4.57610777e-12, -2.33282497e-08,  4.65978738e-05, -6.43305156e-02,  3.73958623e+01],
+        [ 2.96325102e-11, -1.61393276e-07,  3.32600044e-04, -3.28468195e-01,  1.28328844e+02],
+        [ 1.14521506e-11, -5.81980336e-08,  1.12518434e-04, -1.19072484e-01,  5.37601559e+01]]),
+    851: np.array([
+        [ 3.15774215e-11, -1.71452934e-07,  3.50316512e-04, -3.40098861e-01,  1.31064501e+02],
+        [5.36341958e-11, -2.92533156e-07,  6.00574534e-04, -5.71083140e-01,  2.10547161e+02],
+        [ 3.69445588e-11, -1.97731342e-07,  3.98203522e-04, -3.78338599e-01,  1.41894119e+02]])
+}
+
+def timFactorFromTable(voltage, photonEnergy, mcp=1):
+    ''' Returns an energy calibration factor for TIM integrated peak signal (APD)
+        according to calibration from March 2019, proposal 900074, semester 201930, 
+        runs 69 - 111 (Ni edge):  https://in.xfel.eu/elog/SCS+Beamline/2323
+        runs 113 - 153 (Co edge): https://in.xfel.eu/elog/SCS+Beamline/2334
+        runs 163 - 208 (Fe edge): https://in.xfel.eu/elog/SCS+Beamline/2349
+        Uses the tim_calibration_table declared above.
+        
+        Inputs:
+            voltage: MCP voltage in volts.
+            photonEnergy: FEL photon energy in eV. Calibration factor is linearly
+                interpolated between the known values from the calibration table. 
+            mcp: MCP channel (1, 2, or 3).
+            
+        Output:
+            f: calibration factor in microjoule per APD signal
+    '''
+    energies = np.sort([key for key in tim_calibration_table])
+    if photonEnergy not in energies:
+        if photonEnergy > energies.max():
+            photonEnergy = energies.max()
+        elif photonEnergy < energies.min():
+            photonEnergy = energies.min()
+        else:
+            idx = np.searchsorted(energies, photonEnergy) - 1
+            polyA = np.poly1d(tim_calibration_table[energies[idx]][mcp-1])
+            polyB = np.poly1d(tim_calibration_table[energies[idx+1]][mcp-1])
+            fA = -np.exp(polyA(voltage))
+            fB = -np.exp(polyB(voltage))
+            f = fA + (fB-fA)/(energies[idx+1]-energies[idx])*(photonEnergy - energies[idx])
+            return f
+    poly = np.poly1d(tim_calibration_table[photonEnergy][mcp-1])
+    f = -np.exp(poly(voltage))
+    return f