def local_energy_dispersion(spectra, n=1, order=2, correctE=True, \ refname=None, ret_data=False, verbosity=0, **kwargs): """ calculate the position-dependent energy dispersion from the shift of the ZLP with a change in beam energy spectra ... file containing the spectrum image (Nspectra, Npx) n ... (opt) use ZLP shift between spectrum i and i+n order ... (opt) order of the fitting polynomial for the distances correctE ... (opt) if True, beam energy is corrected using BeamEnergy refname ... (opt) filename of reference spectrum for second peak, which will be used instead of the ZLP ret_data ... (opt) return (pos, diff)-points used for the fit verbosity... (opt) verbosity (0=silent, 2=plotting fit, 3=debug) RETURNS - function object returning the energy dispersion for any position on the detector - if ret_data is True, data used to create this function object """ # set up beam energy correction Beam = BeamEnergy() data = [] pos = [] diff = [] dE = [] for filename in spectra: # determine zlp position for each beam energy if refname is not None: Eref, d, zl = get_peak_pos(filename, refname=refname, verbosity=verbosity - 1, **kwargs) else: Eref, zl = get_peak_pos(filename, verbosity=verbosity - 1, **kwargs) # correct BeamEnergy E = Beam.En(Eref) if correctE else Eref # remove NaN's in zl (fit failed) and E (no correction available) ind = ~np.isnan(zl[:, 0] + E) E = E[ind] zl = zl[ind] assert np.allclose(Eref[ind][1:] - Eref[ind][:-1], Eref[1] - Eref[0]) # only even spacing in ZLP energy shifts allowed so far # calculate energy dispersion from difference in adjacent spectra data.append([Eref[ind], zl]) # zl[:,0]=mean pos pos.append((zl[n:, 0] + zl[:-n, 0]) / 2) # average position on detector diff.append(-(zl[n:, 0] - zl[:-n, 0])) # distance between ZLP i and i+n dE.append(E[n:] - E[:-n]) # corresponding energy distance # TODO: allow for many spectra with uneven energy spacing dEref = Eref[n] - Eref[0] # nominal energy difference # plotting if verbosity > 1: fig = plt.figure() ax = plt.gca() plt.title("Position-dependent peak distance"+\ " (n=%d, $\Delta E_{ref}$=%4.1f eV)"%(n,dEref)) for i in range(len(pos)): ax.plot(pos[i], diff[i], 'ko', alpha=0.1, label="uncorrected" if i == 0 else "") ax.plot(pos[i], diff[i] * dEref / dE[i], 'o', markersize=6, label="%s" % spectra[i].split('/')[-1].split('.dm3')[0]) # flatten lists (one level) pos = np.asarray(sum(map(list, pos), [])) diff = np.asarray(sum(map(list, diff), [])) dE = np.asarray(sum(map(list, dE), [])) # fitting polynomial to the Energy-corrected appearent size of the scale ind = ~np.isnan(pos) # index for outliers diff_corr = diff * dEref / dE disp = np.poly1d(np.polyfit(pos[ind], diff_corr[ind], order)) if verbosity > 1: plt.gca().plot(pos[~ind], diff[~ind], 'rx', label="outliers") plt.gca().plot(disp(np.arange(4096)), 'k-', label="fit") ax.set_xlabel("position on detector [px]") if correctE: ax.set_ylabel( "corrected ZLP distance $ \Delta x * (\Delta E_{ref} / \Delta E)$ [px]" ) else: ax.set_ylabel("ZLP distance dx [px]") #ax.set_ylim(460,475); ax.legend(loc=0) # calculate energy dispersion dE/dy e2x = trafo.NonlinearDispersion(disp, scale=dEref, xrange=(0, 4096)) # # DEBUG: test energy calibration for positions of ZLP: if verbosity > 2: fig = plt.figure() ax = fig.add_subplot(111) # reference curve for beam energy Beam = BeamEnergy() ax.plot(Beam.E, Beam.E_corr, 'k-', label='reference') # beam energy from ZLP position and energy dispersion for i, (Eref, zl) in enumerate(data): E, _ = e2x.inverse(zl[:, 0], zl[:, 0]) dE = -(E - E[0]) # shift of ZLP from calibrated E-axis E_corr = dE + Beam.En(Eref[0]) # corrected Beam energy for each ZLP ax.plot(Eref, E_corr - Eref, 'x', label=spectra[i].split('/')[-1]) ax.set_xlabel("nominal beam energy offset $E_0$ [eV]") ax.set_ylabel("estimated deviation $E - E_0$ [eV]") ax.set_ylim(-0.3, 0.3) ax.legend(loc=0) # output transformation if verbosity > 1: print "Transformation to linear energy axis e2x: \n", e2x.info(3) return e2x if not ret_data else (e2x, np.vstack( (pos[ind], diff_corr[ind])))
def get_dispersion(spectra, refname, DE=None, order=2, \ ret_data=False, verbosity=0, **kwargs): """ calculate the position-dependent energy dispersion from the distance between two peaks (ZLP and plasmon reference) spectra ... file containing the spectrum image (Nspectra, Npx) refname ... filename of reference spectrum for second peak DE ... (opt) use energy width of shifted scale to calibrate the absolute energy axis, otherwise we use the nominal distance of two ZLPs order ... (opt) order of the fitting polynomial for the distances ret_data ... (opt) return (pos, diff)-points used for the fit verbosity... (opt) verbosity (0=silent, 2=plotting fit, 3=debug) RETURNS function object returning the energy dispersion for any position on the detector """ pos = [] diff = [] data = [] for filename in spectra: Eref, zl, pl = get_peak_pos(filename, refname, verbosity=verbosity, **kwargs) # order zl and pl (zl should be left peak) if np.nansum(pl[:, 0] - zl[:, 0]) < 0: zl, pl = pl, zl # remove NaN's in zl (fit failed) and BeamEnergy (no correction available) ind = ~np.isnan(zl[:, 0] + BeamEnergy().En(Eref)) Eref = Eref[ind] zl = zl[ind] pl = pl[ind] # determine position-dependent peak distance data.append([Eref, zl, pl]) # zl[:,0]=mean pos pos.append((zl[:, 0] + pl[:, 0]) / 2) # average position on detector diff.append(pl[:, 0] - zl[:, 0]) # distance between peaks dy # plotting if verbosity > 1: fig = plt.figure() ax = plt.gca() plt.title("Position-dependent peak distance") for i in range(len(pos)): ax.plot(pos[i], diff[i], 'x', label="%s" % spectra[i].split('/')[-1].split('.dm3')[0]) # flatten lists (one level) pos = np.asarray(sum(map(list, pos), [])) diff = np.asarray(sum(map(list, diff), [])) # fitting polynomial to the appearent size of the scale ind = ~np.isnan(pos) # index for outliers disp = np.poly1d(np.polyfit(pos[ind], diff[ind], order)) if verbosity > 1: plt.gca().plot(pos[~ind], diff[~ind], 'rx', label="outliers") plt.gca().plot(disp(np.arange(4096)), 'k-', label="fit") ax.set_xlabel("position on detector [px]") ax.set_ylabel("distance between ZLP and plasmon [px]") #ax.set_ylim(460,475); ax.legend(loc=0) # Calculate energy width of scale from the linear shift of the ZLP # with (corrected) beam energy if DE is None: # for each data series with N shifted spectra, we calculate the # missing scaling factor DE from the expected shift of the ZLPs DE = [] for Eref, zl, pl in data: # data[series][quantity][Nspectra] # expected energy shift for n-th ZLP compared to initial pos. Ecorr = -BeamEnergy().En(Eref) # energy scale inversed wrt. E-q map Y = Ecorr[0] - Ecorr[1:] # measured energy shift for scale DE=1 e2x = NonlinearDispersion(disp, scale=1) en = e2x.inverse(zl[:, 0], zl[:, 0])[0] X = en[0] - en[1:] #print Ecorr, Eref, Y, zl[:,0], en; # calculate scaling factor DE which minimizes residuals X*DE-Y # min(|X*DE-Y|^2) <=> 2(X*DE-Y)*X = 0 <=> DE=X*Y/X*X DE.append(np.dot(X, Y) / np.dot(X, X)) # ouput if verbosity > 0: print "-- estimating scale DE ---------------------------" if verbosity > 1: for i in range(len(spectra)): print " optimal Eplasmon for '%s': %8.5f eV " \ % (spectra[i].split('/')[-1], DE[i]) print "\n Average Eplasmon: (%5.3f +- %5.3f) eV" \ % (np.mean(DE), np.std(DE)) # calculate energy dispersion dE/dy e2x = NonlinearDispersion(disp, scale=np.mean(DE)) # DEBUG: test energy calibration for positions of ZLP: if verbosity > 2: fig = plt.figure() ax = fig.add_subplot(111) # reference curve for beam energy Beam = BeamEnergy() ax.plot(Beam.E, Beam.E_corr, 'k-', label='reference') # beam energy from ZLP position and energy dispersion for i, (Eref, zl, pl) in enumerate(data): E, _ = e2x.inverse(zl[:, 0], zl[:, 0]) dE = -(E - E[0]) # shift of ZLP from calibrated E-axis E_corr = dE + Beam.En(Eref[0]) # corrected Beam energy for each ZLP ax.plot(Eref, E_corr - Eref, 'x', label=spectra[i].split('/')[-1]) ax.set_xlabel("nominal beam energy offset $E_0$ [eV]") ax.set_ylabel("estimated deviation $E - E_0$ [eV]") ax.set_ylim(-0.3, 0.3) ax.legend(loc=0) # output transformation if verbosity > 1: print "Transformation to linear energy axis e2x: \n", e2x.info(3) return e2x if not ret_data else (e2x, np.vstack((pos[ind], diff[ind])))
from TEMareels.ecal.energy_dispersion import get_dispersion; from TEMareels.ecal.fit_peak_pos import get_peak_pos; # calculate dispersion spectra = ["../tests/Eseries%i.tif" %i for i in range(1,4)]; refname = "../tests/Ereference.msa"; order = 2; DE = None; # energy width of shifted scale [eV] e2x = get_dispersion(spectra,refname,verbosity=0,order=order,ampl_cut=0.5); # calculate energy of ZLP for each of the series E_ref = []; E_zl = []; E_pl = []; for i,filename in enumerate(spectra): E,zl,pl=get_peak_pos(filename, refname, border=100, \ ampl_cut=0.1, verbosity=2); # positions [px] ind=~np.isnan(pl[:,0]-zl[:,0]); # identify NaN's E = E[ind]; zl = zl[ind]; pl = pl[ind]; zl = e2x.inverse( zl[:,0], zl[:,0] )[0]; # energies [eV] pl = e2x.inverse( pl[:,0], pl[:,0] )[0]; s0 = np.nanargmax(zl); # remove offset E_zl.append( E[s0]+zl[s0]-zl ); plOffset = np.mean(pl-zl) \ if DE is None else DE; E_pl.append( E[s0]+zl[s0]+plOffset-pl); E_ref.append( E ); #sE = np.argmin(np.abs(E-0)); # align at Energy E0 #print sE, E_zl[-1][sE]; #E_zl[-1] += E[sE]-E_zl[-1][sE]; #E_pl[-1] += E[sE]-E_pl[-1][sE];
def local_energy_dispersion(spectra, n=1, order=2, correctE=True, \ refname=None, ret_data=False, verbosity=0, **kwargs): """ calculate the position-dependent energy dispersion from the shift of the ZLP with a change in beam energy spectra ... file containing the spectrum image (Nspectra, Npx) n ... (opt) use ZLP shift between spectrum i and i+n order ... (opt) order of the fitting polynomial for the distances correctE ... (opt) if True, beam energy is corrected using BeamEnergy refname ... (opt) filename of reference spectrum for second peak, which will be used instead of the ZLP ret_data ... (opt) return (pos, diff)-points used for the fit verbosity... (opt) verbosity (0=silent, 2=plotting fit, 3=debug) RETURNS - function object returning the energy dispersion for any position on the detector - if ret_data is True, data used to create this function object """ # set up beam energy correction Beam = BeamEnergy(); data = []; pos = []; diff = []; dE = []; for filename in spectra: # determine zlp position for each beam energy if refname is not None: Eref,d,zl=get_peak_pos(filename,refname=refname, verbosity=verbosity-1, **kwargs); else: Eref,zl = get_peak_pos( filename, verbosity=verbosity-1, **kwargs); # correct BeamEnergy E = Beam.En(Eref) if correctE else Eref; # remove NaN's in zl (fit failed) and E (no correction available) ind =~np.isnan(zl[:,0] + E); E = E[ind]; zl=zl[ind]; assert np.allclose( Eref[ind][1:] - Eref[ind][:-1], Eref[1]-Eref[0] ) # only even spacing in ZLP energy shifts allowed so far # calculate energy dispersion from difference in adjacent spectra data.append( [Eref[ind], zl] ); # zl[:,0]=mean pos pos.append( (zl[n:,0] + zl[:-n,0])/2 ); # average position on detector diff.append(-(zl[n:,0] - zl[:-n,0]) ); # distance between ZLP i and i+n dE.append( E[n:] - E[:-n] ); # corresponding energy distance # TODO: allow for many spectra with uneven energy spacing dEref = Eref[n]-Eref[0]; # nominal energy difference # plotting if verbosity > 1: fig=plt.figure(); ax=plt.gca(); plt.title("Position-dependent peak distance"+\ " (n=%d, $\Delta E_{ref}$=%4.1f eV)"%(n,dEref)); for i in range(len(pos)): ax.plot(pos[i], diff[i], 'ko', alpha=0.1, label="uncorrected" if i==0 else ""); ax.plot(pos[i], diff[i]*dEref/dE[i], 'o', markersize=6, label="%s"%spectra[i].split('/')[-1].split('.dm3')[0]); # flatten lists (one level) pos = np.asarray(sum(map(list,pos ),[])); diff = np.asarray(sum(map(list,diff),[])); dE = np.asarray(sum(map(list,dE),[])); # fitting polynomial to the Energy-corrected appearent size of the scale ind = ~np.isnan(pos); # index for outliers diff_corr = diff*dEref/dE; disp= np.poly1d(np.polyfit(pos[ind],diff_corr[ind],order)); if verbosity > 1: plt.gca().plot(pos[~ind],diff[~ind], 'rx', label="outliers"); plt.gca().plot(disp(np.arange(4096)), 'k-', label="fit"); ax.set_xlabel("position on detector [px]"); if correctE: ax.set_ylabel("corrected ZLP distance $ \Delta x * (\Delta E_{ref} / \Delta E)$ [px]"); else: ax.set_ylabel("ZLP distance dx [px]"); #ax.set_ylim(460,475); ax.legend(loc=0); # calculate energy dispersion dE/dy e2x=trafo.NonlinearDispersion(disp,scale=dEref,xrange=(0,4096));# # DEBUG: test energy calibration for positions of ZLP: if verbosity>2: fig = plt.figure(); ax=fig.add_subplot(111); # reference curve for beam energy Beam= BeamEnergy(); ax.plot(Beam.E,Beam.E_corr,'k-',label='reference'); # beam energy from ZLP position and energy dispersion for i,(Eref,zl) in enumerate(data): E,_ = e2x.inverse(zl[:,0],zl[:,0]); dE =-(E - E[0]); # shift of ZLP from calibrated E-axis E_corr = dE + Beam.En(Eref[0]); # corrected Beam energy for each ZLP ax.plot(Eref,E_corr-Eref,'x',label=spectra[i].split('/')[-1]); ax.set_xlabel("nominal beam energy offset $E_0$ [eV]"); ax.set_ylabel("estimated deviation $E - E_0$ [eV]"); ax.set_ylim(-0.3,0.3); ax.legend(loc=0); # output transformation if verbosity > 1: print "Transformation to linear energy axis e2x: \n", e2x.info(3); return e2x if not ret_data else (e2x, np.vstack((pos[ind],diff_corr[ind])));
# calculate dispersion spectra = ["../tests/Eseries%i.tif" % i for i in range(1, 4)] refname = "../tests/Ereference.msa" order = 2 DE = None # energy width of shifted scale [eV] e2x = get_dispersion(spectra, refname, verbosity=0, order=order, ampl_cut=0.5) # calculate energy of ZLP for each of the series E_ref = [] E_zl = [] E_pl = [] for i, filename in enumerate(spectra): E,zl,pl=get_peak_pos(filename, refname, border=100, \ ampl_cut=0.1, verbosity=2) # positions [px] ind = ~np.isnan(pl[:, 0] - zl[:, 0]) # identify NaN's E = E[ind] zl = zl[ind] pl = pl[ind] zl = e2x.inverse(zl[:, 0], zl[:, 0])[0] # energies [eV] pl = e2x.inverse(pl[:, 0], pl[:, 0])[0] s0 = np.nanargmax(zl) # remove offset E_zl.append(E[s0] + zl[s0] - zl) plOffset = np.mean(pl-zl) \ if DE is None else DE E_pl.append(E[s0] + zl[s0] + plOffset - pl)
def get_dispersion(spectra, refname, DE=None, order=2, \ ret_data=False, verbosity=0, **kwargs): """ calculate the position-dependent energy dispersion from the distance between two peaks (ZLP and plasmon reference) spectra ... file containing the spectrum image (Nspectra, Npx) refname ... filename of reference spectrum for second peak DE ... (opt) use energy width of shifted scale to calibrate the absolute energy axis, otherwise we use the nominal distance of two ZLPs order ... (opt) order of the fitting polynomial for the distances ret_data ... (opt) return (pos, diff)-points used for the fit verbosity... (opt) verbosity (0=silent, 2=plotting fit, 3=debug) RETURNS function object returning the energy dispersion for any position on the detector """ pos = []; diff = []; data = []; for filename in spectra: Eref,zl,pl = get_peak_pos( filename, refname, verbosity=verbosity, **kwargs); # order zl and pl (zl should be left peak) if np.nansum(pl[:,0]-zl[:,0])<0: zl,pl=pl,zl # remove NaN's in zl (fit failed) and BeamEnergy (no correction available) ind =~np.isnan(zl[:,0] + BeamEnergy().En(Eref)); Eref=Eref[ind]; zl=zl[ind]; pl=pl[ind]; # determine position-dependent peak distance data.append( [Eref, zl, pl] ); # zl[:,0]=mean pos pos.append( (zl[:,0] + pl[:,0])/2 ); # average position on detector diff.append( pl[:,0] - zl[:,0] ); # distance between peaks dy # plotting if verbosity > 1: fig=plt.figure(); ax=plt.gca(); plt.title("Position-dependent peak distance"); for i in range(len(pos)): ax.plot(pos[i],diff[i], 'x', label="%s"%spectra[i].split('/')[-1].split('.dm3')[0]); # flatten lists (one level) pos = np.asarray(sum(map(list,pos ),[])); diff = np.asarray(sum(map(list,diff),[])); # fitting polynomial to the appearent size of the scale ind = ~np.isnan(pos); # index for outliers disp= np.poly1d(np.polyfit(pos[ind],diff[ind],order)); if verbosity > 1: plt.gca().plot(pos[~ind],diff[~ind], 'rx', label="outliers"); plt.gca().plot(disp(np.arange(4096)), 'k-', label="fit"); ax.set_xlabel("position on detector [px]"); ax.set_ylabel("distance between ZLP and plasmon [px]"); #ax.set_ylim(460,475); ax.legend(loc=0); # Calculate energy width of scale from the linear shift of the ZLP # with (corrected) beam energy if DE is None: # for each data series with N shifted spectra, we calculate the # missing scaling factor DE from the expected shift of the ZLPs DE = []; for Eref,zl,pl in data: # data[series][quantity][Nspectra] # expected energy shift for n-th ZLP compared to initial pos. Ecorr=-BeamEnergy().En(Eref); # energy scale inversed wrt. E-q map Y =Ecorr[0]-Ecorr[1:]; # measured energy shift for scale DE=1 e2x=NonlinearDispersion(disp,scale=1); en =e2x.inverse(zl[:,0],zl[:,0])[0]; X = en[0]-en[1:]; #print Ecorr, Eref, Y, zl[:,0], en; # calculate scaling factor DE which minimizes residuals X*DE-Y # min(|X*DE-Y|^2) <=> 2(X*DE-Y)*X = 0 <=> DE=X*Y/X*X DE.append( np.dot(X,Y)/np.dot(X,X) ); # ouput if verbosity>0: print "-- estimating scale DE ---------------------------" if verbosity>1: for i in range(len(spectra)): print " optimal Eplasmon for '%s': %8.5f eV " \ % (spectra[i].split('/')[-1], DE[i]); print "\n Average Eplasmon: (%5.3f +- %5.3f) eV" \ % (np.mean(DE), np.std(DE)); # calculate energy dispersion dE/dy e2x=NonlinearDispersion(disp,scale=np.mean(DE)); # DEBUG: test energy calibration for positions of ZLP: if verbosity>2: fig = plt.figure(); ax=fig.add_subplot(111); # reference curve for beam energy Beam= BeamEnergy(); ax.plot(Beam.E,Beam.E_corr,'k-',label='reference'); # beam energy from ZLP position and energy dispersion for i,(Eref,zl,pl) in enumerate(data): E,_ = e2x.inverse(zl[:,0],zl[:,0]); dE =-(E - E[0]); # shift of ZLP from calibrated E-axis E_corr = dE + Beam.En(Eref[0]); # corrected Beam energy for each ZLP ax.plot(Eref,E_corr-Eref,'x',label=spectra[i].split('/')[-1]); ax.set_xlabel("nominal beam energy offset $E_0$ [eV]"); ax.set_ylabel("estimated deviation $E - E_0$ [eV]"); ax.set_ylim(-0.3,0.3); ax.legend(loc=0); # output transformation if verbosity > 1: print "Transformation to linear energy axis e2x: \n", e2x.info(3); return e2x if not ret_data else (e2x, np.vstack((pos[ind],diff[ind])));