docs: def _get_2e_asym(self):
          """
              Get asymmetries for 2e random-frequency scan. 
              Based on bnmr_2e.cpp by rmlm (Oct 4, 2017).
          """
          
          # get needed PPG parameters for splitting 1D histos into 2D histos
          try:        
              # get frequency vector
              freq = np.arange(self.ppg['freq_start'].mean,\
                               self.ppg['freq_stop'].mean+\
                                      self.ppg['freq_incr'].mean,\
                               self.ppg['freq_incr'].mean)
                               
              # number of dwelltimes per frequency bin 
              ndwell = 2*int(self.ppg['ndwell_per_f'].mean)-1
              
              # number of RF on delays for the start bin. 
              start_bin = int(self.ppg['rf_on_delay'].mean)
              
              # get bin centers in ms
              time = self.ppg['rf_on_ms'].mean*(np.arange(ndwell)+0.5-ndwell/2.)
              
              # get the time and index of the middle time 
              mid_time_i = int(np.floor(ndwell/2.))
              mid_time = time[mid_time_i]
          
              # beam off time after pulse in ms
              beam_off = int(self.ppg['beam_off_ms'].mean)
          
          except KeyError:
              raise RuntimeError("Not all dictionary variables read out to "+\
                                 "proper locations")
              
          # setup output
          out = bdict()
          out['freq'] = freq
          out['time'] = time
              
          # get data
          data = np.array(self._get_area_data()) # [[fp], [bfm], [bp], [bm]]
          
          # discared initial bad bins, and beam-off trailing bins
          data = data[:,start_bin:len(freq)*ndwell+start_bin]
          
          # split data by frequency
          nsplit = len(data[0])/ndwell
          
          fp = np.array(np.split(data[0],nsplit))
          fm = np.array(np.split(data[1],nsplit))
          bp = np.array(np.split(data[2],nsplit))
          bm = np.array(np.split(data[3],nsplit))
          
          # get raw asymmetries 
          asym_p_2cntr = (bp-fp)/(bp+fp)      # two counter
          asym_m_2cntr = (bm-fm)/(bm+fm)      # two counter
          r = np.sqrt(bp*fm/(bm*fp))
          asym_4cntr = (r-1)/(r+1)            # four counter
          
          # get raw asymmetry errors
          asym_p_2cntr_err = 2*np.sqrt(bp*fp)/((bp+fp)**1.5)
          asym_m_2cntr_err = 2*np.sqrt(bm*fm)/((bm+fm)**1.5)
          asym_4cntr_err = r*np.sqrt(1./bp+1./bm+1./fp+1./fm)/((r+1)**2)
  
          # save to output
          out['raw_p'] = np.array([asym_p_2cntr,asym_p_2cntr_err])
          out['raw_n'] = np.array([asym_m_2cntr,asym_m_2cntr_err])
          out['raw_c'] = np.array([asym_4cntr,asym_4cntr_err])
  
          # wrap asymmetry arrays into one for calculations [p,m,4]
          # indexing is now [pm4][freq][time bin]
          asym     = np.array([asym_p_2cntr,    asym_m_2cntr,    asym_4cntr])
          asym_err = np.array([asym_p_2cntr_err,asym_m_2cntr_err,asym_4cntr_err])
  
          # compute differenced asymmetries via slopes from weighted least squares 
          # minimization.
          if ndwell >= 5:
              
              # calculate needed components element-wise
              w = asym_err**-2
              x = time
              y = asym
              
              wx = w*x
              wy = w*y
              wxy = w*x*y
              wxx = w*x*x
          
              # sum over values i < mid_time_i within each asymmetry and frequency 
              # Indexing: [pm4][freq]
              w_pre   = np.sum(w  [:,:,:mid_time_i],2)    
              wx_pre  = np.sum(wx [:,:,:mid_time_i],2)
              wy_pre  = np.sum(wy [:,:,:mid_time_i],2)
              wxy_pre = np.sum(wxy[:,:,:mid_time_i],2)
              wxx_pre = np.sum(wxx[:,:,:mid_time_i],2)
              
              # sum over values i > mid_time_i
              w_pst   = np.sum(w  [:,:,-mid_time_i:],2)
              wx_pst  = np.sum(wx [:,:,-mid_time_i:],2)
              wy_pst  = np.sum(wy [:,:,-mid_time_i:],2)
              wxy_pst = np.sum(wxy[:,:,-mid_time_i:],2)
              wxx_pst = np.sum(wxx[:,:,-mid_time_i:],2)
              
              # calculate slopes and intercepts
              delta_pre = w_pre*wxx_pre - wx_pre**2
              delta_pst = w_pst*wxx_pst - wx_pst**2
              
              sl_pre = (w_pre*wxy_pre - wx_pre*wy_pre)/delta_pre
              sl_pst = (w_pst*wxy_pst - wx_pst*wy_pst)/delta_pst
              dsl_pre = np.sqrt(w_pre/delta_pre)
              dsl_pst = np.sqrt(w_pst/delta_pst)
              
              intr_pre = (wy_pre*wxx_pre - wx_pre*wxy_pre)/delta_pre
              intr_pst = (wy_pst*wxx_pst - wx_pst*wxy_pst)/delta_pst
              dintr_pre = np.sqrt(wxx_pre/delta_pre)
              dintr_pst = np.sqrt(wxx_pst/delta_pst)
              
              # extrapolate to middle time bin
              asym_slopes = intr_pst-intr_pre+(sl_pst-sl_pre)*mid_time
              asym_slopes_err = np.sqrt(dintr_pre**2 + dintr_pst**2 + \
                                        (dsl_pre**2 + dsl_pst**2) * mid_time**2)
              
              # save to output        
              out['sl_p'] = np.array([asym_slopes[0],asym_slopes_err[0]])
              out['sl_n'] = np.array([asym_slopes[1],asym_slopes_err[1]])
              out['sl_c'] = np.array([asym_slopes[2],asym_slopes_err[2]])
          
          # calculate asymmetry using differences
          asym_diff = asym[:,:,mid_time_i+1] - asym[:,:,mid_time_i-1]
          asym_diff_err = np.sqrt(asym_err[:,:,mid_time_i+1]**2+\
                                  asym_err[:,:,mid_time_i-1]**2)
          
          # save to output
          out['dif_p'] = np.array([asym_diff[0],asym_diff_err[0]])
          out['dif_n'] = np.array([asym_diff[1],asym_diff_err[1]])
          out['dif_c'] = np.array([asym_diff[2],asym_diff_err[2]])   
              
          return out