def _line_spectrum(data, min, line, dE, width, width_error): # Draw histogram n, bins = Analysis.histogram(data, binsize=binsize) if method in ("cs"): gn, gbins = Analysis.group_bin(n, bins, min=min) else: # No grouping in mle and ls gn, gbins = n, bins ngn = gn/(np.diff(gbins)) ngn_sigma = np.sqrt(gn)/(np.diff(gbins)) cbins = (gbins[1:]+gbins[:-1])/2 if plotting: figure() if width_error is not None: label = 'FWHM$=%.2f\pm %.2f$ eV' % (width, width_error) else: label = 'FWHM$=%.2f$ eV (Fixed)' % width if method == "cs": errorbar(cbins, ngn, yerr=ngn_sigma, xerr=np.diff(gbins)/2, capsize=0, ecolor='k', fmt=None, label=label) else: hist(data, bins=gbins, weights=np.ones(len(data))/binsize, histtype='step', ec='k', label=label) E = np.linspace(bins.min(), bins.max(), 1000) model = Analysis.normalization(ngn, gbins, dE, width, line=line, shift=shift) \ * Analysis.line_model(E, dE, width, line=line, shift=shift, full=True) # Plot theoretical model plot(E, model[0], 'r-') # Plot fine structures for m in model[1:]: plot(E, m, 'b--') xlabel('Energy$\quad$(eV)') ylabel('Normalized Count$\quad$(count/eV)') legend(frameon=False) ymin, ymax = ylim() ylim(ymin, ymax*1.1) tight_layout() savefig("%s-%s.pdf" % (session, line)) if savedat: np.savetxt('%s-%s.dat' % (session, line), np.vstack((cbins, gn)).T, header='Energy (keV), Count', delimiter='\t')
def tesana(t, p, n, lpfc=None, hpfc=None, binsize=1, max_shift=10, thre=0.4, filt=None, nulldc=False, offset=False, center=False, sigma=3, gain=None, dsr=None, shift=False, ocmethod="ols", flip=False, atom="Mn", kbfit=False, ignorekb=False, method="mle", rshunt=None, tbias=None, ites=None, ka_min=80, kb_min=40, tex=False, plotting=True, savedat=False, session="Unnamed"): """ Perform TES Analysis Parameters (and their default values): t: time data (array-like) p: pulse data (array-like) n: noise data (array-like) lpfc: low-pass filter cut-off frequency in bins (Default: None) hpfc: high-pass filter cut-off frequency in bins (Default: None) binsize: energy bin size for histograms and fittings (only for ls ans cs) in eV (Default: 1) max_shift: maximum allowed shifts to calculate maximum cross correlation (Default: 10) thre: correlation threshold for offset correction (Default: 0.4) filt: window function (hanning/hamming/blackman/tukey) (Default: None) nulldc: nullify the DC bin when template generation (Default: False) offset: subtract DC offset (Default: False) center: centering pulse rise (Default: False) sigma: sigmas for median filter (Default: 3) gain: feedback gain for current-space conversion (Default: None) dsr: down-sampling rate (Default: None) shift: treat dE as energy shift instead of scaling (Default: False) ocmethod: offset correction fitting method (ols/odr) (Default: ols) flip: flip x and y when offset correction fitting (Default: False) atom: atom to fit (Default: Mn) kbfit: fit Kb line (Default: False) ignorekb: ignore Kb line when linearity correction (Default: False) method: fitting method (mle/ls/cs) (Default: mle) rshunt: shunt resistance value for r-space conversion (Default: None) tbias: TES bias current for r-space conversion (Default: None) ites: TES current for r-space conversion (Default: None) ka_min: minimum counts to group bins for Ka line (valid only for ls/cs fittings) (Default: 80) ka_min: minimum counts to group bins for Kb line (valid only for ls/cs fittings) (Default: 20) tex: use TeX for plots (Default: False) plotting: generate and save plots (Default: True) savedat: save data to files (Default: False) session: session name for plots and data files (Default: Unnamed) Note: - Use offset option when using filt option - Consider using center option when using filt option """ if plotting: # Import matplotlib import matplotlib matplotlib.use('Agg') matplotlib.rcParams['text.usetex'] = str(tex) from pylab import figure, plot, errorbar, hist, axvline, xlim, ylim, loglog, xlabel, ylabel, legend, tight_layout, savefig print "Session: %s" % session # Preparation p = np.asarray(p) n = np.asarray(n) t = np.asarray(t) dt = np.diff(t)[0] df = (dt * t.shape[-1])**-1 # Subtract offset if offset: ofs = np.median(n) p -= ofs n -= ofs # Convert to current-space if needed if gain: print "Converting to current-space" p /= gain n /= gain # Convert to resistance-space Rspace = False if gain and rshunt and tbias and ites: print "Converting to resistance-space" ofs = np.median(n) p += (ites - ofs) n += (ites - ofs) # Convert to resistance p = (tbias - p) * rshunt / p n = (tbias - n) * rshunt / n Rspace = True # Down-sample if dsr > 1: p = p[:,:p.shape[-1]/dsr*dsr].reshape(p.shape[0], -1, dsr).mean(axis=-1) n = n[:,:n.shape[-1]/dsr*dsr].reshape(n.shape[0], -1, dsr).mean(axis=-1) dt *= dsr t = t[::dsr] # Pulse centering (for filtering) if center: # Roll pulse to the center r = p.shape[-1] / 2 - np.median(abs(p - Filter.offset(p)[:, np.newaxis]).argmax(axis=-1)) p = np.hstack((p[...,-r:], p[...,:-r])) # Calculate offset (needs to be done before applying filter) if p.size > 0: offset = Filter.offset(p) # Generate Filter if filt is None: pass else: if filt.lower() == "hanning": f = np.hanning(p.shape[-1]) elif filt.lower() == "hamming": f = np.hamming(p.shape[-1]) elif filt.lower() == "blackman": f = np.blackman(p.shape[-1]) elif filt.lower() == "tukey": f = tukey(p.shape[-1]) else: raise ValueError('Unsupported filter: %s' % filt.lower()) print "Window filter function: %s" % filt.lower() # Amplitude correction cf = f.sum() / len(f) p *= (f / cf) n *= (f / cf) # Equivalent noise bandwidth correction enb = len(f)*(f**2).sum()/f.sum()**2 df *= enb if p.size > 0: # Calculate averaged pulse avgp = Filter.average_pulse(p, max_shift=max_shift) if savedat: np.savetxt('%s-averagepulse.dat' % session, np.vstack((t, avgp)).T, header='Time (s), Averaged Pulse (%s)' % ('R' if Rspace else ('A' if gain else 'V')), delimiter='\t') if plotting: figure() plot(t, avgp) xlabel('Time$\quad$(s)') ylabel('Averaged Pulse$\quad$(%s)' % ('R' if Rspace else ('A' if gain else 'V'))) tight_layout() savefig('%s-averagepulse.pdf' % session) # Calculate averaged pulse spectrum avgps = np.sqrt(Filter.power(avgp)) / df if savedat: np.savetxt('%s-avgpulse-power.dat' % session, np.vstack((np.arange(len(avgps))*df, avgps)).T, header='Frequency (Hz), Average Pulse Power (%s/srHz)' % ('R' if Rspace else ('A' if gain else 'V')), delimiter='\t') if plotting: avgps[0] = 0 # for better plot figure() plot(np.arange(len(avgps))*df, avgps) loglog() xlabel('Frequency$\quad$(Hz)') ylabel('Average Pulse Power$\quad$(%s/Hz)' % ('R' if Rspace else ('A' if gain else 'V'))) tight_layout() savefig('%s-avgpulse-power.pdf' % session) if n.size > 0: # Plot noise spectrum avgns = np.sqrt(Filter.average_noise(n) / df) if savedat: np.savetxt('%s-noise.dat' % session, np.vstack((np.arange(len(avgns))*df, avgns)).T, header='Frequency (Hz), Noise (%s/srHz)' % ('R' if Rspace else ('A' if gain else 'V')), delimiter='\t') if plotting: avgns[0] = 0 # for better plot figure() plot(np.arange(len(avgns))*df, avgns) loglog() xlabel('Frequency$\quad$(Hz)') ylabel('Noise$\quad$(%s/$\sqrt{\mathrm{Hz}}$)' % ('R' if Rspace else ('A' if gain else 'V'))) tight_layout() savefig('%s-noise.pdf' % session) if p.size > 0 and n.size > 0: # Generate template tmpl, sn = Filter.generate_template(p, n, lpfc=lpfc, hpfc=hpfc, nulldc=nulldc, max_shift=max_shift) if savedat: np.savetxt('%s-template.dat' % session, np.vstack((t, tmpl)).T, header='Time (s), Template (A.U.)', delimiter='\t') np.savetxt('%s-sn.dat' % session, np.vstack((np.arange(len(sn))*df, sn/np.sqrt(df))).T, header='Frequency (Hz), S/N (/srHz)', delimiter='\t') if plotting: # Plot template figure() plot(t, tmpl) xlabel('Time$\quad$(s)') ylabel('Template$\quad$(A.U.)') tight_layout() savefig('%s-template.pdf' % session) # Plot SNR figure() plot(np.arange(len(sn))*df, sn/np.sqrt(df)) loglog() xlabel('Frequency$\quad$(Hz)') ylabel('S/N$\quad$(/$\sqrt{\mathrm{Hz}}$)') tight_layout() savefig('%s-sn.pdf' % session) # Calculate baseline resolution print "Resolving power: %.2f (%.2f eV @ 5.9 keV)" % (np.sqrt((sn**2).sum()*2), Analysis.baseline(sn)) # Perform optimal filtering pha_p = Filter.optimal_filter(p, tmpl, max_shift=max_shift) pha_n = Filter.optimal_filter(n, tmpl, max_shift=0) # Offset correction (a, b), coef = Analysis.fit_offset(pha_p, offset, sigma=sigma, method=ocmethod, flip=flip) if coef > thre: oc_pha_p = Analysis.offset_correction(pha_p, offset, b) oc_pha_n = Analysis.offset_correction(pha_n, offset, b) print "Offset correction with: PHA = %f * (1 + %f * Offset)" % (a, b) if plotting: figure() ka = Analysis.ka(np.vstack((pha_p, offset)).T, sigma=sigma) plot(ka.T[1], ka.T[0], '.', c='k') x_min, x_max = xlim() ofs = np.linspace(x_min, x_max) label = '$\mathrm{PHA}=%.2f\\times(1+%.2f\\times\mathrm{Offset})$' % (a, b) plot(ofs, a*(1+b*ofs), 'r-', label=label) xlabel('Offset$\quad$(V)') ylabel('PHA$\quad$(V)') legend(frameon=False) tight_layout() savefig('%s-offset.pdf' % session) else: oc_pha_p = pha_p oc_pha_n = pha_n print "Skipped offset correction: correlation coefficient (%f) is too small" % coef # Check line database if "%sKa" % atom not in Constants.LE.keys() or "%sKb" % atom not in Constants.LE.keys(): raise ValueError('Unsupported atom: %s' % atom) # Linearity correction pha_line_center = np.asarray([ np.median(Analysis.ka(oc_pha_p, sigma=sigma)), np.median(Analysis.kb(oc_pha_p, sigma=sigma)) ]) line_energy = np.asarray([ Constants.LE['%sKa' % atom], Constants.LE['%sKb' % atom] ]) if ignorekb: a, b = Analysis.fit_linearity([pha_line_center[0]], [line_energy[0]], deg=1) print "Linearity correction with: PHA = %e * E" % (b) else: a, b = Analysis.fit_linearity(pha_line_center, line_energy, deg=2) print "Linearity correction with: PHA = %e * E^2 + %e * E" % (a, b) print "MnKb saturation ratio: %.2f %%" % ((pha_line_center[1]/pha_line_center[0])/(line_energy[1]/line_energy[0])*100) lc_pha_p = Analysis.linearity_correction(oc_pha_p, a, b) lc_pha_n = Analysis.linearity_correction(oc_pha_n, a, b) if savedat: np.savetxt('%s-linearity.dat' % session, array([pha_line_center[0]]) if ignorekb else pha_line_center[np.newaxis,:], header='%sKa PHA' % atom if ignorekb else '%sKa PHA, %sKb PHA' % (atom, atom), delimiter='\t') if plotting: figure() x = np.linspace(0, 7e3) if ignorekb: plot(line_energy[0]/1e3, pha_line_center[0], '+', color='b') plot(x/1e3, x*b, 'r--') else: plot(line_energy/1e3, pha_line_center, '+', color='b') plot(x/1e3, x**2*a+x*b, 'r--') xlim((0, 7)) xlabel('Energy$\quad$(keV)') ylabel('PHA$\quad$(a.u.)') tight_layout() savefig('%s-linearity.pdf' % session) # Energy Spectrum if plotting: figure() hcount, hbin, hpatch = hist(lc_pha_p[lc_pha_p==lc_pha_p]/1e3, bins=7000/binsize, histtype='stepfilled', color='y') xlim(0, 7) xlabel('Energy$\quad$(keV)') ylabel('Count') tight_layout() savefig('%s-spec.pdf' % session) if savedat: hcount, hbin = np.histogram(lc_pha_p[lc_pha_p==lc_pha_p]/1e3, bins=7000/binsize) np.savetxt('%s-spec.dat' % session, np.vstack(((hbin[1:]+hbin[:-1])/2, hcount)).T, header='Energy (keV), Count', delimiter='\t') # Line fitting def _line_fit(data, min, line): # Fit (dE, width), (dE_error, width_error), e = Analysis.fit(data, binsize=binsize, min=min, line=line, shift=shift, method=method) if method == "cs": chi_squared, dof = e if method in ("mle", "ls"): print "%s: %.2f +/- %.2f eV @ Ec%+.2f eV" \ % (line, width, width_error, dE) elif method == "cs": print "%s: %.2f +/- %.2f eV @ Ec%+.2f eV (Red. chi^2 = %.1f/%d = %.2f)" \ % (line, width, width_error, dE, chi_squared, dof, chi_squared/dof) return dE, width, width_error def _line_spectrum(data, min, line, dE, width, width_error): # Draw histogram n, bins = Analysis.histogram(data, binsize=binsize) if method in ("cs"): gn, gbins = Analysis.group_bin(n, bins, min=min) else: # No grouping in mle and ls gn, gbins = n, bins ngn = gn/(np.diff(gbins)) ngn_sigma = np.sqrt(gn)/(np.diff(gbins)) cbins = (gbins[1:]+gbins[:-1])/2 if plotting: figure() if width_error is not None: label = 'FWHM$=%.2f\pm %.2f$ eV' % (width, width_error) else: label = 'FWHM$=%.2f$ eV (Fixed)' % width if method == "cs": errorbar(cbins, ngn, yerr=ngn_sigma, xerr=np.diff(gbins)/2, capsize=0, ecolor='k', fmt=None, label=label) else: hist(data, bins=gbins, weights=np.ones(len(data))/binsize, histtype='step', ec='k', label=label) E = np.linspace(bins.min(), bins.max(), 1000) model = Analysis.normalization(ngn, gbins, dE, width, line=line, shift=shift) \ * Analysis.line_model(E, dE, width, line=line, shift=shift, full=True) # Plot theoretical model plot(E, model[0], 'r-') # Plot fine structures for m in model[1:]: plot(E, m, 'b--') xlabel('Energy$\quad$(eV)') ylabel('Normalized Count$\quad$(count/eV)') legend(frameon=False) ymin, ymax = ylim() ylim(ymin, ymax*1.1) tight_layout() savefig("%s-%s.pdf" % (session, line)) if savedat: np.savetxt('%s-%s.dat' % (session, line), np.vstack((cbins, gn)).T, header='Energy (keV), Count', delimiter='\t') ## Ka ka = Analysis.ka(lc_pha_p, sigma=sigma) dE, width, width_error = _line_fit(ka, ka_min, "%sKa" % atom) _line_spectrum(ka, ka_min, "%sKa" % atom, dE, width, width_error) ## Kb kb = Analysis.kb(lc_pha_p, sigma=sigma) if kbfit: dE, width, width_error = _line_fit(kb, kb_min, "%sKb" % atom) else: width_error = None _line_spectrum(kb, kb_min, "%sKb" % atom, dE, width, width_error) ## Baseline f_pha_n = lc_pha_n[Filter.median_filter(lc_pha_n, sigma=sigma)] baseline = Analysis.sigma2fwhm(np.std(f_pha_n)) print "Baseline resolution: %.2f eV" % baseline n, bins = Analysis.histogram(f_pha_n, binsize=binsize) if savedat: np.savetxt('%s-baseline.dat' % session, np.vstack(((bins[1:]+bins[:-1])/2, n)).T, header='Energy (keV), Count', delimiter='\t') if plotting: figure() label = 'FWHM$=%.2f$ eV' % baseline hist(f_pha_n, bins=bins, weights=np.ones(len(f_pha_n))/binsize, histtype='step', ec='k', label=label) mu, sigma = norm.fit(f_pha_n) E = np.linspace(bins.min(), bins.max(), 1000) plot(E, norm.pdf(E, loc=mu, scale=sigma)*len(f_pha_n), 'r-') xlabel('Energy$\quad$(eV)') ylabel('Normalized Count$\quad$(count/eV)') legend(frameon=False) tight_layout() savefig('%s-baseline.pdf' % session)