From 57925d4ab6c0f6e018018c460e9982be168a68eb Mon Sep 17 00:00:00 2001
From: Mercadier <mercadil@xfel.eu>
Date: Mon, 24 Jun 2019 22:11:37 +0200
Subject: [PATCH] Adds function to sort XGM data, updates calibrateXGMs(),
 matchXgmTimPulseId(), adds 'sigma' fields of XGM pulse-resolved data

---
 Load.py |  27 +++++++-
 XAS.py  |   4 +-
 xgm.py  | 203 ++++++++++++++++++++++++++++++++++++++++----------------
 3 files changed, 175 insertions(+), 59 deletions(-)

diff --git a/Load.py b/Load.py
index 43e773c..c91bd91 100644
--- a/Load.py
+++ b/Load.py
@@ -64,16 +64,28 @@ mnemonics = {
     "XTD10_photonFlux": {'source':'SA3_XTD10_XGM/XGM/DOOCS',
                      'key':'pulseEnergy.photonFlux.value',
                      'dim':None},
+    "XTD10_photonFlux_sigma": {'source':'SA3_XTD10_XGM/XGM/DOOCS',
+                     'key':'pulseEnergy.photonFluxSigma.value',
+                     'dim':None},
     ## ADC
     "XTD10_XGM": {'source':'SA3_XTD10_XGM/XGM/DOOCS:output',
                 'key':'data.intensityTD',
                 'dim':['XGMbunchId']},
+    "XTD10_XGM_sigma": {'source':'SA3_XTD10_XGM/XGM/DOOCS:output',
+                'key':'data.intensitySigmaTD',
+                'dim':['XGMbunchId']},
     "XTD10_SA3": {'source':'SA3_XTD10_XGM/XGM/DOOCS:output',
                 'key':'data.intensitySa3TD',
                 'dim':['XGMbunchId']},
+    "XTD10_SA3_sigma": {'source':'SA3_XTD10_XGM/XGM/DOOCS:output',
+                'key':'data.intensitySa3SigmaTD',
+                'dim':['XGMbunchId']},
     "XTD10_SA1": {'source':'SA3_XTD10_XGM/XGM/DOOCS:output',
                 'key':'data.intensitySa1TD',
                 'dim':['XGMbunchId']},
+    "XTD10_SA1_sigma": {'source':'SA3_XTD10_XGM/XGM/DOOCS:output',
+                'key':'data.intensitySa1SigmaTD',
+                'dim':['XGMbunchId']},
     ## low pass averaged ADC
     "XTD10_slowTrain": {'source':'SA3_XTD10_XGM/XGM/DOOCS',
                     'key':'controlData.slowTrain.value',
@@ -90,16 +102,28 @@ mnemonics = {
     "SCS_photonFlux": {'source':'SCS_BLU_XGM/XGM/DOOCS',
                      'key':'pulseEnergy.photonFlux.value',
                      'dim':None},
+    "SCS_photonFlux_sigma": {'source':'SCS_BLU_XGM/XGM/DOOCS',
+                     'key':'pulseEnergy.photonFluxSigma.value',
+                     'dim':None},
     ## ADC
     "SCS_XGM": {'source':'SCS_BLU_XGM/XGM/DOOCS:output',
                 'key':'data.intensityTD',
                 'dim':['XGMbunchId']},
+    "SCS_XGM_sigma": {'source':'SCS_BLU_XGM/XGM/DOOCS:output',
+                'key':'data.intensitySigmaTD',
+                'dim':['XGMbunchId']},
     "SCS_SA1": {'source':'SCS_BLU_XGM/XGM/DOOCS:output',
                 'key':'data.intensitySa1TD',
                 'dim':['XGMbunchId']},
+    "SCS_SA1_sigma": {'source':'SCS_BLU_XGM/XGM/DOOCS:output',
+                'key':'data.intensitySa1SigmaTD',
+                'dim':['XGMbunchId']},
     "SCS_SA3": {'source':'SCS_BLU_XGM/XGM/DOOCS:output',
                 'key':'data.intensitySa3TD',
                 'dim':['XGMbunchId']},
+    "SCS_SA3_sigma": {'source':'SCS_BLU_XGM/XGM/DOOCS:output',
+                'key':'data.intensitySa3SigmaTD',
+                'dim':['XGMbunchId']},
     ## low pass averaged ADC
     "SCS_slowTrain": {'source':'SCS_BLU_XGM/XGM/DOOCS',
                     'key':'controlData.slowTrain.value',
@@ -351,7 +375,8 @@ def load(fields, runNB, proposalNB, semesterNB, topic='SCS', display=False,
                 k = f
             else:
                 print('Unknow mnemonic "{}". Skipping!'.format(f))
-
+                continue
+                
         if k in keys:
             continue # already loaded, skip
 
diff --git a/XAS.py b/XAS.py
index a7bab50..5fff047 100644
--- a/XAS.py
+++ b/XAS.py
@@ -97,7 +97,7 @@ def binning(x, data, func, bins=100, bin_length=None):
 
     return bins, res
 
-def xas(nrun, bins=None, Iokey='SCS_XGM', Itkey='MCP3apd', nrjkey='nrj', Iooffset=0):
+def xas(nrun, bins=None, Iokey='SCS_SA3', Itkey='MCP3apd', nrjkey='nrj', Iooffset=0):
     """ Compute the XAS spectra from a xarray nrun.
 
         Inputs:
@@ -119,7 +119,7 @@ def xas(nrun, bins=None, Iokey='SCS_XGM', Itkey='MCP3apd', nrjkey='nrj', Iooffse
                 counts: the number of events in each bin
     """
     
-    stacked = nrun.stack(x=['trainId','pId'])
+    stacked = nrun.stack(x=['trainId','sa3_pId'])
 
     Io = stacked[Iokey].values + Iooffset
     It = stacked[Itkey].values
diff --git a/xgm.py b/xgm.py
index 769fbb2..4602cc1 100644
--- a/xgm.py
+++ b/xgm.py
@@ -97,7 +97,7 @@ def pulsePatternInfo(data, plot=False):
 def repRate(data, sase='sase3'):
     ''' Calculates the pulse repetition rate in sase according
         to the bunch pattern and assuming a minimum pulse 
-        separation of 222e-9 seconds.
+        separation of 221.54e-9 seconds.
         Inputs:
             data: xarray Dataset containing pulse pattern
             sase: sase in which the repetition rate is
@@ -111,11 +111,76 @@ def repRate(data, sase='sase3'):
     if len(sase)==0:
         print('Not enough pulses to extract repetition rate')
         return 0
-    f = 1/((sase[0,1] - sase[0,0])*222e-6)
+    f = 1/((sase[0,1] - sase[0,0])*221.54e-6)
     return f
     
             
 # XGM
+def cleanXGMdata(data, npulses=None, sase3First=True):
+    ''' Cleans the XGM data arrays obtained from load() function.
+        The XGM "TD" data arrays have arbitrary size of 1000 and default value 1.0
+        when there is no pulse. This function sorts the SASE 1 and SASE 3 pulses.
+        For recent DAQ runs, sase-resolved arrays can be used. For older runs,
+        the function selectSASEinXGM can be used to extract sase-resolved pulses.
+        Inputs:
+            data: xarray Dataset containing XGM TD arrays.
+            npulses: number of pulses, needed if pulse pattern not available.
+            sase3First: bool, needed if pulse pattern not available.
+        
+        Output:
+            xarray Dataset containing sase- and pulse-resolved XGM data, with
+                dimension names 'sa1_pId' and 'sa3_pId'                
+    '''
+    dropList = []
+    mergeList = []
+    if ("XTD10_SA3" not in data and "XTD10_XGM" in data) or (
+        "SCS_SA3" not in data and "SCS_XGM" in data):
+        #no SASE-resolved arrays available
+        if 'SCS_XGM' in data:
+            sa3 = selectSASEinXGM(data, xgm='SCS_XGM', sase='sase3', npulses=npulses,
+                   sase3First=sase3First).rename({'XGMbunchId':'sa3_pId'}).rename('SCS_SA3')
+            mergeList.append(sa3)
+            sa1 = selectSASEinXGM(data, xgm='SCS_XGM', sase='sase1', npulses=npulses,
+                   sase3First=sase3First).rename({'XGMbunchId':'sa1_pId'}).rename('SCS_SA1')
+            mergeList.append(sa1)
+            dropList.append('SCS_XGM')
+
+        if 'XTD10_XGM' in data:
+            sa3 = selectSASEinXGM(data, xgm='XTD10_XGM', sase='sase3', npulses=npulses,
+                       sase3First=sase3First).rename({'XGMbunchId':'sa3_pId'}).rename('XTD10_SA3')
+            mergeList.append(sa3)
+            sa1 = selectSASEinXGM(data, xgm='XTD10_XGM', sase='sase1', npulses=npulses,
+                       sase3First=sase3First).rename({'XGMbunchId':'sa1_pId'}).rename('XTD10_SA1')
+            mergeList.append(sa1)
+            dropList.append('XTD10_XGM')
+        keys = []
+        
+    else:
+        keys = ["XTD10_XGM", "XTD10_SA3", "XTD10_SA1",
+                "XTD10_XGM_sigma", "XTD10_SA3_sigma", "XTD10_SA1_sigma"]
+        keys += ["SCS_XGM", "SCS_SA3", "SCS_SA1",
+                 "SCS_XGM_sigma", "SCS_SA3_sigma", "SCS_SA1_sigma"]
+        
+    for key in keys:
+        if key not in data:
+            continue
+        if "sa3" in key.lower():
+            sase = 'sa3_'
+        elif "sa1" in key.lower():
+            sase = 'sa1_'
+        else:
+            dropList.append(key)
+            continue
+        res = data[key].where(data[key] != 1.0, drop=True).rename(
+                {'XGMbunchId':'{}pId'.format(sase)}).rename(key)
+        dropList.append(key)
+        mergeList.append(res)
+    mergeList.append(data.drop(dropList))
+    subset = xr.merge(mergeList, join='inner')
+    subset.attrs['run'] = data.attrs['run']
+    return subset
+
+
 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
@@ -268,23 +333,49 @@ def saseContribution(data, sase='sase1', xgm='XTD10_XGM'):
     else:
         return 1 - contrib
 
+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').
+        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.
 
-def filterOnTrains(data, key='sase3'):
-    ''' Removes train ids for which there was no pulse in sase='sase1' or 'sase3' branch
-        
         Inputs:
             data: xarray Dataset
-            sase: SASE onwhich to filter: {'sase1', 'sase3'}
-            
+            rollingWindow: length of running average to calculate E_fast_avg
+            plot: boolean, plot the calibration output
+
         Output:
-            filtered xarray Dataset
+            factors: numpy ndarray of shape 1 x 2 containing 
+                     [XTD10 calibration factor, SCS calibration factor]
     '''
-    key = 'npulses_' + key
-    res = data.where(data[key]>0, drop=True)
-    return res
-
-
-def calibrateXGMs(data, rollingWindow=200, plot=False):
+    XTD10_factor = np.nan
+    SCS_factor = np.nan
+    if "XTD10_slowTrain" in data or "SCS_slowTrain" in data:
+        if "XTD10_slowTrain" in data:
+            XTD10_factor = np.mean(data.XTD10_photonFlux/data.XTD10_slowTrain)
+            
+        else:
+            print('no XTD10 XGM data. Skipping calibration for XTD10 XGM')
+        if "SCS_slowTrain" in data:
+            #XTD10_SA3_contrib = data.XTD10_slowTrain_SA3 * data.npulses_sase3 / (
+            #                data.XTD10_slowTrain * (data.npulses_sase3+data.npulses_sase1))
+            #SCS_SA3_SLOW = data.SCS_photonFlux*(data.npulses_sase3+
+            #                                    data.npulses_sase1)*XTD10_SA3_contrib/data.npulses_sase3
+            #SCS_factor = np.mean(SCS_SA3_SLOW/data.SCS_slowTrain_SA3)
+            SCS_factor = np.mean(data.SCS_photonFlux/data.SCS_slowTrain)
+        else:
+            print('no SCS XGM data. Skipping calibration for SCS XGM')
+            
+        #TODO: plot the results of calibration
+        return np.array([XTD10_factor, SCS_factor])
+    else:
+        return calibrateXGMsFromAllPulses(data, rollingWindow, plot)
+        
+        
+def calibrateXGMsFromAllPulses(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').
@@ -300,27 +391,30 @@ def calibrateXGMs(data, rollingWindow=200, plot=False):
             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
+    XTD10_factor = np.nan
+    SCS_factor = np.nan
+    noSCS = noXTD10 = False
     if 'SCS_XGM' not in data:
         print('no SCS XGM data. Skipping calibration for SCS XGM')
         noSCS = True
     if 'XTD10_XGM' not in data:
         print('no XTD10 XGM data. Skipping calibration for XTD10 XGM')
-        noSA3 = True
-    if noSCS and noSA3:
-        return np.array([None, None])
+        noXTD10 = True
+    if noSCS and noXTD10:
+        return np.array([XTD10_factor, SCS_factor])
+    if not noSCS and noXTD10:
+        print('XTD10 data is needed to calibrate SCS XGM.')
+        return np.array([XTD10_factor, SCS_factor])
     start = 0
     stop = None
     npulses = data['npulses_sase3']
@@ -340,32 +434,35 @@ def calibrateXGMs(data, rollingWindow=200, plot=False):
     sa3contrib = saseContribution(data, 'sase3', 'XTD10_XGM')
     SA3_SLOW = data['XTD10_photonFlux']*(data['npulses_sase3']+data['npulses_sase1'])*sa3contrib/data['npulses_sase3']
     SA1_SLOW = data['XTD10_photonFlux']*(data['npulses_sase3']+data['npulses_sase1'])*(1-sa3contrib)/data['npulses_sase1']
-    SCS_SLOW = data['SCS_photonFlux']*(data['npulses_sase3']+data['npulses_sase1'])*sa3contrib/data['npulses_sase3'] 
 
-    # Calibrate SASE3 XGM with all signal from SASE1 and SASE3
-    if not noSA3:
+    # Calibrate XTD10 XGM with all signal from SASE1 and SASE3
+    if not noXTD10:
         xgm_avg = selectSASEinXGM(data, 'sase3', 'XTD10_XGM').mean(axis=1)
         rolling_sa3_xgm = xgm_avg.rolling(trainId=rollingWindow).mean()
         ratio = SA3_SLOW/rolling_sa3_xgm
-        sa3_calib_factor = ratio[start:stop].mean().values
-        print('calibration factor SA3 XGM: %f'%sa3_calib_factor)
+        XTD10_factor = ratio[start:stop].mean().values
+        print('calibration factor XTD10 XGM: %f'%XTD10_factor)
 
     # Calibrate SCS XGM with SASE3-only contribution
     if not noSCS:
+        SCS_SLOW = data['SCS_photonFlux']*(data['npulses_sase3']+data['npulses_sase1'])*sa3contrib/data['npulses_sase3'] 
         scs_sase3_fast = selectSASEinXGM(data, 'sase3', 'SCS_XGM').mean(axis=1)
         meanFast = scs_sase3_fast.rolling(trainId=rollingWindow).mean()
         ratio = SCS_SLOW/meanFast
-        scs_calib_factor = ratio[start:stop].median().values
-        print('calibration factor SCS XGM: %f'%scs_calib_factor)
+        SCS_factor = ratio[start:stop].median().values
+        print('calibration factor SCS XGM: %f'%SCS_factor)
         
     if plot:
-        plt.figure(figsize=(8,8))
+        if noSCS ^ noXTD10:
+            plt.figure(figsize=(8,4))
+        else:
+            plt.figure(figsize=(8,8))
         plt.subplot(211)
-        plt.title('E[uJ] = %.2f x IntensityTD' %(sa3_calib_factor))
+        plt.title('E[uJ] = %.2f x IntensityTD' %(XTD10_factor))
         plt.plot(SA3_SLOW, label='SA3 slow', color='C1')
-        plt.plot(rolling_sa3_xgm*sa3_calib_factor,
+        plt.plot(rolling_sa3_xgm*XTD10_factor,
                  label='SA3 fast signal rolling avg', color='C4')
-        plt.plot(xgm_avg*sa3_calib_factor, label='SA3 fast signal train avg', alpha=0.2, color='C4')
+        plt.plot(xgm_avg*XTD10_factor, label='SA3 fast signal train avg', alpha=0.2, color='C4')
         plt.ylabel('Energy [uJ]')
         plt.xlabel('train in run')
         plt.legend(loc='upper left', fontsize=10)
@@ -375,16 +472,16 @@ def calibrateXGMs(data, rollingWindow=200, plot=False):
         plt.legend(loc='lower right', fontsize=10)
 
         plt.subplot(212)
-        plt.title('E[uJ] = %.2f x HAMP' %scs_calib_factor)
+        plt.title('E[uJ] = %.2g x HAMP' %SCS_factor)
         plt.plot(SCS_SLOW, label='SCS slow', color='C1')
-        plt.plot(meanFast*scs_calib_factor, label='SCS HAMP rolling avg', color='C2')
+        plt.plot(meanFast*SCS_factor, label='SCS HAMP rolling avg', color='C2')
         plt.ylabel('Energy [uJ]')
         plt.xlabel('train in run')
-        plt.plot(scs_sase3_fast*scs_calib_factor, label='SCS HAMP train avg', alpha=0.2, color='C2')
+        plt.plot(scs_sase3_fast*SCS_factor, label='SCS HAMP train avg', alpha=0.2, color='C2')
         plt.legend(loc='upper left', fontsize=10)
         plt.tight_layout()
 
-    return np.array([sa3_calib_factor, scs_calib_factor])
+    return np.array([XTD10_factor, SCS_factor])
 
 
 # TIM
@@ -816,40 +913,34 @@ def matchXgmTimPulseId(data, use_apd=True, intstart=None, intstop=None,
                 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.
+            xr DataSet containing XGM and TIM signals with the share d
+            dimension 'sa3_pId'. Raw traces, raw XGM and raw APD are dropped.
     '''
-    res = selectSASEinXGM(data, xgm='SCS_XGM', npulses=npulses,
-                   sase3First=sase3First).rename({'XGMbunchId':'pId'}).rename('SCS_XGM')
-    dropList = ['SCS_XGM']
-    mergeList = [res]
-
-    if 'XTD10_XGM' in data: 
-        res2 = selectSASEinXGM(data, xgm='XTD10_XGM', npulses=npulses,
-                   sase3First=sase3First).rename({'XGMbunchId':'pId'}).rename('XTD10_XGM')
-        dropList.append('XTD10_XGM')
-        mergeList.append(res2)
-
+    
+    dropList = []
+    mergeList = []
+    ndata = cleanXGMdata(data, npulses, sase3First)
     for mcp in range(1,5):
         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,
+            MCPapd = getTIMapd(ndata, mcp=mcp, use_apd=use_apd, intstart=intstart,
                                intstop=intstop,bkgstart=bkgstart, bkgstop=bkgstop,
                                t_offset=t_offset, npulses=npulses,
                                stride=stride).rename('MCP{}apd'.format(mcp))
             if use_apd:
-                MCPapd = MCPapd.rename({'apdId':'pId'})
+                MCPapd = MCPapd.rename({'apdId':'sa3_pId'})
             else:
-                MCPapd = MCPapd.rename({'MCP{}fromRaw'.format(mcp):'pId'})
+                MCPapd = MCPapd.rename({'MCP{}fromRaw'.format(mcp):'sa3_pId'})
             mergeList.append(MCPapd)
-            if 'MCP{}raw'.format(mcp) in data:
+            if 'MCP{}raw'.format(mcp) in ndata:
                 dropList.append('MCP{}raw'.format(mcp))
             if 'MCP{}apd'.format(mcp) in data:
                 dropList.append('MCP{}apd'.format(mcp))
-    mergeList.append(data.drop(dropList))
+    mergeList.append(ndata.drop(dropList))
     subset = xr.merge(mergeList, join='inner')
-    subset.attrs['run'] = data.attrs['run']
+    subset.attrs['run'] = ndata.attrs['run']
     return subset
 
+
 # Fast ADC
 def fastAdcPeaks(data, channel, intstart, intstop, bkgstart, bkgstop, period=None, npulses=None):
     ''' Computes peak integration from raw FastADC traces.
-- 
GitLab