def sourceAlignment(dx, dy, dz): """ Set up a trace of rays from the fiber source to the OAP. Determine wavefront error of collimated beam. """ #Source rays = sources.circularbeam(125. / 4, 10000) tran.pointTo(rays, dx, dy, -775. / 2 + dz, reverse=1) rays[0] = np.sqrt((rays[1] + dx)**2 + (rays[2] + dy)**2 + (775. / 2 + dz)**2) pdb.set_trace() #Go to focus tran.transform(rays, 0, 0, 0, np.pi / 2, 0, 0) tran.transform(rays, 0, -775. / 2, -775. / 2, 0, 0, 0) #Trace to parabola surf.conic(rays, 775., -1., nr=1.) tran.reflect(rays) #Restrict to 5" diameter ## ind = np.logical_and(rays[3]>387.5-62.5,rays[3]<387.5+62.5) ## tran.vignette(rays,ind=ind) #Reflect ## for i in range(7,10): ## rays[i] = -rays[i] tran.transform(rays, 0, 0, 775. / 2, 0, 0, 0) surf.flat(rays, nr=1.) pdb.set_trace() #Get OPD opd, dx0, dy0 = anal.interpolateVec(rays, 0, 200, 200) opd = man.remove2DLeg(opd, xo=1, yo=0) opd = man.remove2DLeg(opd, xo=0, yo=1) pv = ana.ptov(opd) plt.figure('OPD') plt.imshow(opd) plt.colorbar() wavesl = np.gradient(opd, dx0) resy = fit.legendre2d(wavesl[0], xo=2, yo=2) resx = fit.legendre2d(wavesl[1], xo=2, yo=2) resy[0][np.isnan(opd)] = np.nan resx[0][np.isnan(opd)] = np.nan plt.figure('Y') plt.imshow(resy[0] * 180 / np.pi * 60**2) plt.colorbar() plt.figure('X') plt.imshow(resx[0] * 180 / np.pi * 60**2) plt.colorbar() return pv * 1e6
def fitTip(d): """ Fit the tip term (roll) using the 1st and 2,1 coefficients """ res, c = fit.legendre2d(d, xo=1, yo=2, xl=[1, 0, 1, 1], yl=[0, 0, 0, 2]) return ptov(d), ptov(d - res), c[0, 1], c[2, 1], res
def fitTilt(d): """ Fit the tilt term (pitch) using the 1st and 3rd order coefficients """ res, c = fit.legendre2d(d, xo=0, yo=3, xl=[1, 0, 0, 0], yl=[0, 0, 1, 3]) return ptov(d), ptov(d - res), c[1, 0], c[3, 0], res
def remove2DLeg(din, xo=2, yo=0): """ Remove a 2D Legendre fit to din up to xo and yo. """ f = legendre2d(din, xo=xo, yo=yo)[0] return din - f
def OPDtoLegendre(x, y, opd, xo, yo, xwidth=1., ywidth=1.): """ Fit Legendre coefficients over ray XY plane to OPD. """ #Perform Legendre fitting res = fit.legendre2d(opd, x=x / xwidth, y=y / ywidth, xo=xo, yo=yo) #Construct order arrays coeff = res[1] [xorder, yorder] = np.meshgrid(range(xo + 1), range(yo + 1)) return coeff, xorder, yorder
def fitTwist(d): """ Fit the twist term (yaw) using the 1,1 and 3,1 terms """ res, c = fit.legendre2d(d, xo=1, yo=2, xl=[1, 0, 1, 1, 1], yl=[0, 0, 0, 1, 3]) return ptov(d), ptov(d - res), c[1, 1], c[3, 1], res
def fitFocus(d): """ Fit up to 4th order for focus and return the 2nd and 4th order coefficients """ res, c = fit.legendre2d(d, xo=0, yo=4, xl=[1, 0, 0, 0, 0], yl=[1, 0, 1, 2, 4]) return ptov(d), ptov(d - res), c[2, 0], c[4, 0], res
def fom_sl(tdata, x=None, y=None): """Fit slope of surface after plane level""" """This was taking a second argument dy, changed 2019/03/28 to data,x,y, do a workaround to keep old compatibility.""" #tdata=tdata[ci0:ci1,ci0:ci1] if y is None and x is not None: dy = x else: dy = np.diff(diff.y)[0] tdata = tdata - fit.legendre2d(tdata, 1, 1)[0] grad = np.gradient(tdata) slopeax = grad[0][:-1, :] / 1000. * 206265 / dy #in arcseconds from um return np.nanstd(slopeax)
def OPDtoLegendreP(x, y, opd, xo, yo, xwidth=1., ywidth=1.): """ Fit Legendre coefficients over ray XY plane to OPD. Return the derivatives in both axes """ #Perform Legendre fitting res = fit.legendre2d(opd, x=x / xwidth, y=y / ywidth, xo=xo, yo=yo) #Construct order arrays coeff = res[1] [xorder, yorder] = np.meshgrid(range(xo + 1), range(yo + 1)) #Construct the derivatives using the special function module cx = np.polynomial.legendre.legder(res[1], axis=0) cy = np.polynomial.legendre.legder(res[1], axis=1) #gy = np.polynomial.legendre.legval2d(x/xwidth,y/ywidth,cx)/xwidth #gx = np.polynomial.legendre.legval2d(x/xwidth,y/ywidth,cy)/ywidth gy = np.polynomial.legendre.legval2d(-y / ywidth, x / xwidth, cx) / ywidth gx = np.polynomial.legendre.legval2d(-y / ywidth, x / xwidth, cy) / xwidth return coeff, xorder, yorder, gx, gy
def findDistortionCoefficients(filename, Nx, Ny, method='cubic'): """ Fit L-L coefficients to distortion data produced by Vanessa. """ #Load in data d = np.transpose(np.genfromtxt(filename, skip_header=1, delimiter=',')) #Compute angle and radial perturbations x0, y0, z0 = d[2:5] t0 = np.arctan2(x0, -z0) r0 = 1 - np.sqrt(x0**2 + z0**2) #Define regular grid tg,zg = np.meshgrid(np.linspace(t0.min(),t0.max(),Nx+2),\ np.linspace(y0.min(),y0.max(),Ny+2)) #Regrid nominal node positions d0 = griddata((t0, y0), r0, (tg, zg), method=method) #Find perturbed node positions x1, y1, z1 = x0 + d[5], y0 + d[6], z0 + d[7] t1 = np.arctan2(x1, -z1) r1 = 1 - np.sqrt(x1**2 + z1**2) #Regrid distorted nodes onto original grid d1 = griddata((t1, y1), r1, (tg, zg), method=method) #Get distortion data # d = d1 - d0 #Apply fit res = fit.legendre2d(d, xo=10, yo=10) xo, yo = np.meshgrid(np.arange(11), np.arange(11)) pdb.set_trace() #Return coefficients and order arrays return res[1].flatten(), xo.flatten(), yo.flatten()
def calculatePSD2(wdata,xg,yg,outname="",wfun=None,vrange=[None,None],rmsrange=None,prange=None,fignum=1,misal_deg=(1,1),leg_deg=(10,10)): """Updated version with subtracted 4 terms legendre.""" """given points w, calculate and plot surface maps with different leveling (piston, tilt, sag, 10 legendre) use psd2d to calculate and save x and y 2d PSDs, plots only y. fignum window where to plot, if fignnum is 0 current figure is cleared, if None new figure is created. Default to figure 1 .""" ##STILL AWFUL CODE # misal_deg is useless, since data are expected to be already leveled. # if not, still can be applied, but is quite useless. ###AWFUL CODE, partially fixed -> fix plotting window ## FOUR PANEL PLOT save as _lev.png, _piston.dat, _tilt.dat, _sag.dat ## on fignum fig=fignumber(fignum) plt.clf() figManager = plt.get_current_fig_manager() figManager.window.showMaximized() # wdata=-fit.legendre2d(wdata,1,1)[0] lwdata=wdata-fit.legendre2d(wdata,*misal_deg)[0] lwdata2=wdata-fit.legendre2d(wdata,*leg_deg)[0] # #make plots ax=compare_images([wdata,lwdata,lwdata2],xg,yg,titles=['original','cone corrected','10 legendre 2D corrected'],fignum=0,commonscale=True,vmin=vrange[0],vmax=vrange[1]) #generator of axis, not sure how it can work below, missing something? for im in ax: plt.xlabel('X (mm)') plt.ylabel('Y (mm)') # ##return wdata,lwdata,lwdata2 wdata,lwdata,lwdata2=leveldata(wdata,xg,yg) #calculate and make a plot with 3 panels if outname: np.savetxt(fn_add_subfix(outname,'_piston','.dat'),wdata) #doesn't write x and y np.savetxt(fn_add_subfix(outname,'_%2i_%2i_misal'%misal_deg,'.dat'),lwdata) np.savetxt(fn_add_subfix(outname,'_%2i_%2i_leg'%leg_deg,'.dat'),lwdata2) leg10=wdata-fit.legendre2d(wdata,*leg_deg)[0] #forth panel contains legendre plt.subplot(224) plt.imshow(leg10,extent=(xg[0],xg[-1],yg[0],yg[-1]), interpolation='None',aspect='auto') plt.title('(%i,%i) 2D legendre removed'%leg_deg) plt.colorbar() #display(plt.gcf()) if outname: plt.savefig(fn_add_subfix(outname,'_lev','.png')) ## DATA OUTPUT save as _psd.dat, psd2d creates #create packed data for txt output #containing x and y psds, with 3 different levelings #resulting in 6 couples freq/psd. # matches the order specified in header #header for final matrix output, define labels of psd sequence head='yfreq ypsd xfreq xpsd ylevfreq ylevpsd xlevfreq xlevpsd ysagfreq ysagpsd xsagfreq xsagpsd' #this assumes x and y are same xg and yg on all datalist #change this part, we are no more interested in doing psd on all possible leveling, only 4 terms removed by line # along the direction of interest, however to keep constant number of columns, replace piston, tilt, sag # with tilt, sag, 4 legendre datalist=[wdata,lwdata,lwdata2] labels=['_tilt','_mis','_leg'] #postfix for output file flist=[] psdlist=[] for d,l in zip(datalist,labels): plt.figure(3) #x and y psds fx,px=psd2d(d.T,yg,xg,wfun=wfun) #psds along x, no plot fy,py=plot_psd2d(d,xg,yg,outname=fn_add_subfix(outname,l),wfun=wfun, rmsrange=rmsrange,prange=prange,vrange=vrange) #psds along y, plot #display(plt.gcf()) #note y goes first flist.extend([fy,fx]) psdlist.extend([py,px]) avgpsdlist=[avgpsd2d(p) for p in psdlist] #generate output array and save it with header outarr=np.empty((np.max([len(f) for f in flist]),len(psdlist)*2))*np.nan for i,(f,p) in enumerate(zip(flist,avgpsdlist)): outarr[:len(f),i*2]=f outarr[:len(p),i*2+1]=p if outname: np.savetxt(fn_add_subfix(outname,'_psd','.dat'),outarr,header=head,fmt='%f') ## PLOT AVG PSDs save as _psds.png fw1,fw1m,fw2,fw2m,fw3,fw3m=flist #used only in plot pw1avg,pw1mavg,pw2avg,pw2mavg,pw3avg,pw3mavg=avgpsdlist #average psds plt.figure(2) plt.clf() plt.plot(fw1,pw1avg,label='Y') plt.plot(fw2,pw2avg,label='Y lev.') plt.plot(fw3,pw3avg,label='Y sag.') plt.plot(fw1m,pw1mavg,'--',label='X') plt.plot(fw2m,pw2mavg,'--',label='X lev.') plt.plot(fw3m,pw3mavg,'--',label='X sag.') plt.loglog() plt.grid() plt.legend(loc=0) if outname: plt.savefig(fn_add_subfix(outname,'_psds','.png')) return outarr
def calculatePSD(wdata,xg,yg,outname="",wfun=None,vrange=None,rmsrange=None,prange=None,fignum=1): """given points w, calculate and plot surface maps with different leveling (piston, tilt, sag, 10 legendre) use psd2d to calculate and save x and y 2d PSDs, plots only y. fignum window where to plot, if fignnum is 0 current figure is cleared, if None new figure is created. Default to figure 1 .""" ###AWFUL CODE, partially fixed -> fix plotting window ## FOUR PANEL PLOT save as _lev.png, _piston.dat, _tilt.dat, _sag.dat ## on fignum if fignum==0: plt.clf() else: plt.figure(fignum) plt.clf() figManager = plt.get_current_fig_manager() figManager.window.showMaximized() #xg,yg=points_find_grid(w,'grid')[1] wdata,lwdata,lwdata2=leveldata(wdata,xg,yg) #calculate and make a plot with 3 panels if outname: np.savetxt(fn_add_subfix(outname,'_piston','.dat'),wdata) #doesn't write x and y np.savetxt(fn_add_subfix(outname,'_tilt','.dat'),lwdata) np.savetxt(fn_add_subfix(outname,'_sag','.dat'),lwdata2) leg10=wdata-fit.legendre2d(wdata,10,10)[0] #forth panel contains legendre plt.subplot(224) plt.imshow(leg10,extent=(xg[0],xg[-1],yg[0],yg[-1]), interpolation='None',aspect='auto') plt.title('10 legendre') plt.colorbar() #display(plt.gcf()) if outname: plt.savefig(fn_add_subfix(outname,'_lev','.png')) ## DATA OUTPUT save as _psd.dat, psd2d creates #create packed data for txt output #containing x and y psds, with 3 different levelings #resulting in 6 couples freq/psd. # matches the order specified in header #header for final matrix output, define labels of psd sequence head='yfreq ypsd xfreq xpsd ylevfreq ylevpsd xlevfreq xlevpsd ysagfreq ysagpsd xsagfreq xsagpsd' #this assumes x and y are same xg and yg on all datalist datalist=[wdata,lwdata,lwdata2] labels=['_piston','_tilt','_sag'] #postfix for output file flist=[] psdlist=[] for d,l in zip(datalist,labels): plt.figure(3) #x and y psds fx,px=psd2d(d.T,yg,xg,wfun=wfun) #psds along x, no plot fy,py=plot_psd2d(d,xg,yg,outname=fn_add_subfix(outname,l),wfun=wfun, rmsrange=rmsrange,prange=prange,vrange=vrange) #psds along y, plot #display(plt.gcf()) #note y goes first flist.extend([fy,fx]) psdlist.extend([py,px]) avgpsdlist=[avgpsd2d(p) for p in psdlist] #generate output array and save it with header outarr=np.empty((np.max([len(f) for f in flist]),len(psdlist)*2))*np.nan for i,(f,p) in enumerate(zip(flist,avgpsdlist)): outarr[:len(f),i*2]=f outarr[:len(p),i*2+1]=p if outname: np.savetxt(fn_add_subfix(outname,'_psd','.dat'),outarr,header=head,fmt='%f') ## PLOT AVG PSDs save as _psds.png fw1,fw1m,fw2,fw2m,fw3,fw3m=flist #used only in plot pw1avg,pw1mavg,pw2avg,pw2mavg,pw3avg,pw3mavg=avgpsdlist #average psds plt.figure(2) plt.clf() plt.plot(fw1,pw1avg,label='Y') plt.plot(fw2,pw2avg,label='Y lev.') plt.plot(fw3,pw3avg,label='Y sag.') plt.plot(fw1m,pw1mavg,'--',label='X') plt.plot(fw2m,pw2mavg,'--',label='X lev.') plt.plot(fw3m,pw3mavg,'--',label='X sag.') plt.loglog() plt.grid() plt.legend(loc=0) if outname: plt.savefig(fn_add_subfix(outname,'_psds','.png')) return outarr
def plot_surface_analysis(wdata,x,y,label="",outfolder=None,nsigma_crange=1,fignum=None, figsize=(9.6,6.7),psdrange=None,levelingfunc=None,frange=None): """Create two panel preview plots for data. This is two panels of raw and filtered data (second order removed) + histogram of height distribution + PSD. Label is typically the filename and is used to generate title and output filenames. fignum and figsize are for PSD2D figure (fignum=0 clear and reuse current figure, None create new). nsigma_crange and levelingfunc are used to evaluate color range for plot by iteratively leveling and removing outliers at nsigma until a convergence is reached. psdrange is used frange is used for plot of PSD2D """ from pySurf.psd2d import psd2d_analysis units=['mm','mm','$\mu$m'] order_remove=2 #remove sag, this is the order that is removed in leveled panel if np.size(units)==1: #unneded, put here just in case I decide to take units units=np.repeat(units,3) #as argument. However ideally should be put in plot psd function. if levelingfunc is None: levelingfunc=lambda x: x-legendre2d(x,1,1)[0] if outfolder is not None: os.makedirs(os.path.join(outfolder,'PSD2D'),exist_ok=True) else: print('OUTFOLDER is none') wdata=levelingfunc(wdata) #added 20181101 rms=np.nanstd(wdata) #crange=remove_outliers(wdata,nsigma=nsigma_crange,itmax=1,range=True) crange=remove_outliers(wdata,nsigma=nsigma_crange, flattening_func=levelingfunc,itmax=2,span=1) plt.clf() #FULL DATA plt.subplot(221) #plt.subplot2grid((2,2),(0,0),1,1) plot_data(wdata,x,y,units=units,title='leveled data',stats=True) plt.clim(*crange) #SUBTRACT LEGENDRE ldata=levellegendre(y,wdata,order_remove) plt.subplot(223) lrange=remove_outliers(ldata,nsigma=nsigma_crange,itmax=1,span=True) #plt.subplot2grid((2,2),(1,0),1,1) plot_data(ldata,x,y,units=units,title='%i orders legendre removed'%order_remove,stats=True) plt.clim(*lrange) #HISTOGRAM #plt.subplot2grid((2,2),(0,1),2,1) plt.subplot(222) plt.hist(wdata.flatten()[np.isfinite(wdata.flatten())],bins=100,label='full data') plt.hist(ldata.flatten()[np.isfinite(ldata.flatten())],bins=100,color='r',alpha=0.2,label='%i orders filtered'%order_remove) #plt.vlines(crange,*plt.ylim()) plt.vlines(crange,*plt.ylim(),label='plot range raw',linestyle='-.') plt.vlines(lrange,*plt.ylim(),label='plot range lev',linestyle=':') plt.legend(loc=0) #FREQUENCY AND PSD ANALYSIS if fignum==0: #this is the figure created by psd2d_analysis plt.clf() fig = plt.gcf() #pdb.set_trace() stax=plt.gca() #preserve current axis f,p=psd2d_analysis(ldata,x,y,title=label, wfun=np.hanning,fignum=fignum,vrange=lrange, prange=psdrange,units=units, rmsrange=frange)#,ax2f=[0,1,0,0]) plt.ylabel('slice rms ($\mu$m)') ps=projection(p,axis=1,span=True) #avgpsd2d(p,span=1) if outfolder: maximize() plt.savefig(os.path.join(outfolder,'PSD2D', fn_add_subfix(label,"_psd2d",'.png'))) save_data(os.path.join(outfolder,'PSD2D',fn_add_subfix(label,"_psd2d",'.txt')), p,x,f) np.savetxt(os.path.join(outfolder,fn_add_subfix(label,"_psd",'.txt')),np.vstack([f,ps[0],ps[1],ps[2]]).T, header='f(%s^-1)\tPSD(%s^2%s)AVG\tmin\tmax'%(units[1],units[2],units[1]),fmt='%f') #pdb.set_trace() plt.sca(stax) plt.subplot(224) plt.ylabel('axial PSD ('+units[2]+'$^2$ '+units[1]+')') plt.xlabel('Freq. ('+units[1]+'$^{-1}$)') plt.plot(f,ps[0],label='AVG') plt.plot(f,ps[1],label='point-wise min') plt.plot(f,ps[2],label='point-wise max') plt.plot(f,p[:,p.shape[1]//2],label='central profile') plt.ylim(psdrange) plt.loglog() plt.grid(1) plt.legend(loc=0) #ideally to be replaced by: """ plt.subplot(224) plot_psd(((f,ps[0]),(f,ps[1]),(f,ps[2),(f,p[:,p.shape[1]//2])), ['AVG','point-wise min','point-wise max','central profile']) plt.ylabel('axial '+plt.ylabel) plt.ylim(psdrange) """ plt.suptitle('%s: rms=%5.3f 10$^{-3}$'%(label,rms*1000)+units[2]) plt.tight_layout(rect=[0, 0.03, 1, 0.95]) if outfolder: plt.savefig(os.path.join(outfolder,fn_add_subfix(label,"",'.png'))) return (ldata,x,y),(f,p)