def internal_nan_mask(a, x=None, y=None): """return a mask with True on internal nans.""" if x is None: x = np.arange(np.shape(a)[1]) if y is None: y = np.arange(np.shape(a)[0]) sx = [np.min(x), np.max(x)] sy = [np.min(y), np.max(y)] # b is 1 where a is nan, 0 othewise #used to detect when a region is made of nans aisnan = np.where(np.isnan(a), True, False) #plt.figure(); plot_data(b,x,y) #plt.title('b') # s is different integer for each contiguous region, can be s = label(aisnan)[0] #plt.figure() #plot_data(s) #m_int mask of internal nan regions of a m_int = np.zeros(a.shape).astype(bool) for i in range(np.max(s) + 1): c = s == i if aisnan[c].all(): #plt.figure() #plot_data(c,x,y) #for each one find hull and check if it touches borders p = matrix_to_points2(c, x, y) p = p[p[:, 2] != 0, :] #keep only points of current region ssx = span(p[:, 0]) #ranges of points in region ssy = span(p[:, 1]) #print(i,sx,sy,ssx,ssy) if (ssx[0] > sx[0] and ssx[1] < sx[1] and ssy[0] > sy[0] and ssy[1] < sy[1]): #print(i,'internal') import pdb #pdb.set_trace() m_int = np.logical_or(m_int, c != 0) #print(len(np.where(m_int!=0)[0])) #plt.figure() #plot_points(p,scatter=1) #p=p[~np.isnan(p[:,2]),:] #h=points_find_hull(p) #plot_poly(h,'bo') ''' if not(((h[0,:] <= min(x)).any() or (h[0,:] >= max(x)).any() or (h[1,:] <= min(y)).any() or (h[1,:] >= max(y))).any()): print(i,'internal') m_int=np.logical_or(m_int,c==1) ''' return m_int
def remove_outliers2d(data,x=None,y=None,nsigma=3,fignum=None,name='',includenan=True): """remove outliers line by line by interpolation (along vertical lines). If fignum is set, plot comparison in corresponding figure.""" ldata= data.copy() mask=np.apply_along_axis( remove_profile_outliers, axis=0, arr=ldata, nsigma=nsigma ,includenan=includenan) #this modifies ldata and return a mask #these are used only to determine min and max. if x is None: x=np.arange(data.shape[1]) if y is None: y=np.arange(data.shape[0]) if fignum: #plot data before and after removal of outliers plt.figure(fignum) plt.clf() plt.suptitle('Effects of outliers removal - %s'%name).set_size('large') ax1=plt.subplot(221) plt.title('data') plt.xlabel('X (mm)') plt.ylabel('Y (mm)') axim=plt.imshow(data,interpolation='None',aspect='auto', clim=span(ldata),extent=np.hstack([span(x),span(y)])) plt.colorbar() ax2=plt.subplot(222,sharex=ax1,sharey=ax1) plt.title('data outliers removed') plt.xlabel('X (mm)') plt.ylabel('Y (mm)') axim=plt.imshow(ldata,interpolation='None',aspect='auto', clim=span(ldata),extent=np.hstack([span(x),span(y)])) plt.colorbar() ax3=plt.subplot(223,sharex=ax1,sharey=ax1) plt.title('outliers') outliers=ldata-data plt.imshow(outliers,interpolation='None',aspect='auto', clim=span(ldata),extent=np.hstack([span(x),span(y)])) plt.colorbar() if mask.any(): ax4=plt.subplot(224) plt.title('outliers height distribution') #plt.hist(outliers[mask].flatten(),bins=20,label='$\Delta$ correction',alpha=0.5) plt.hist(data[np.isfinite(data)].flatten(),bins=20,label='before',alpha=0.3,color='b') plt.hist(ldata[np.isfinite(ldata)].flatten(),bins=20,label='after',alpha=0.3,color='r') ax4b=ax4.twinx() ax4b.hist((data-ldata)[np.isfinite(data-ldata)].flatten(),bins=20,label='difference',alpha=0.3,color='r') plt.xlabel('Height (um)') plt.ylabel('N') plt.legend(loc=0) plt.show() return ldata
def get_stats(x, y=None, units=None): """""" if units is None: u = ["", ""] elif np.size(units) == 1: u = [units, units] else: u = units assert np.size(u) == 2 stats = [ 'RMS:%3.3g %s' % (np.nanstd(y), u[1]), 'PV_X:%3.3g %s, PV_Y:%3.3g %s' % (span(x, size=1), u[0], span(y, size=1), u[1]) ] return stats
def ax_update2(ax): ax.set_autoscale_on(False) # Otherwise, infinite loop # Get the number of points from the number of pixels in the window #dims = ax.axesPatch.get_window_extent().bounds # Get the range for the new area xstart, ystart, xdelta, ydelta = ax.viewLim.bounds xend = xstart + xdelta yend = ystart + ydelta # Update the image object with our new data and extent xl=[xstart,xend] yl=[ystart,yend] # Update the image object with our new data and extent im = ax.images[-1] cdata=mandel(xstart, xend, ystart, yend) im.set_data(cdata) crange=span(cdata) im.set_extent((xstart, xend, ystart, yend)) if not (im.colorbar is None): fig=ax.get_figure() s = copy.copy( fig.canvas.toolbar._views ) #this store and restore is made to preserve the zooming history, p = copy.copy( fig.canvas.toolbar._positions ) im.colorbar.remove() plt.clim(crange) plt.colorbar() fig.canvas.toolbar._views = s fig.canvas.toolbar._positions = p
def test_with_plot(outfolder=None): """Apply scale to a line plot, it should work the same as an image.""" N = 100 #nr. of points in plot L = 20. #length of x axis x = np.arange(N) * L / (N - 1) y = np.random.random(N) plt.clf() plt.plot(x, y) #fix pixsize: 300 um is 810 units of axis (pixels). #visible is set, scale should be immediately plotted. s = Scale(L / span(plt.xlim(), True), 'units of x', True) #The entire axis span is L if outfolder is not None: plt.savefig(os.path.join(outfolder, 'test_with_plot_01.png')) #zoom 3x #plt.xlim(plt.xlim()[0]/3.,plt.xlim()[1]/3) #plt.ylim(plt.ylim()[0]/3.,plt.ylim()[1]/3) s.t.set_color('black') s.l.set_color('black') s.draw() plt.grid(1) if outfolder is not None: plt.savefig(os.path.join(outfolder, 'test_with_plot_02.png')) return s
def printstats(self, label=None, fmt='%3.2g'): if label is not None: print(label) s = ("%s PV: " + fmt + ", rms: " + fmt) % (self.name, span(self.y, size=True), np.nanstd(self.y)) print(s) return s
def xplot_sig_psd(x, y, label="", **kwargs): """plot signal, fft and psd on three panels. dft sets how the fft is plotted 'realim': real and im parts, 'euler' argument and phase 'both' uses two panels (four in total). """ N = len(y) L = span(x, True) ax1 = plt.subplot(211) plt.title('Signal') plt.plot(x, y, **kwargs) ax1.set_xlabel('mm') plt.grid(1) ax4 = plt.subplot(212) plt.title('PSD') f, PSD = psd(x, y) plt.plot(f, PSD, label=label, **kwargs) plt.loglog() ax4.set_xlabel('mm^-1') plt.grid(1) plt.show() return ax1, ax4
def psd(x, y, retall=False, wfun=None, norm=1, rmsnorm=False): """return frequencies and PSD of a profile. If retall is set, also the phase is returned. PSD is squared FFT divided by step size. Profile is assumed real, so only positive freqs are used, doubling PSD values. Return value has ceil(N/2) elements (always odd). See `np.fft.rfftfreq` for details. wfun is a function that given the number of points return a vector with values for the window. Note that normalization is quite arbitrary, usually it's rescaled later with rmsnorm. 2017/07/30 if rmsnorm is set True, PSD is normalized to (multiplied by) rms calculated from profile (must be same if no window applied). 2016/03/26 this psd i return to old normalizaiton "time-integral squared magnitude", multiplying by dx. """ if wfun is None: win = np.ones(len(y)) else: win = wfun(len(x)) N = len(y) L = span(x, True) yfft = np.fft.rfft( y * win ) #note that rfft return exact values as fft (needs to be doubled after squaring amplitude) normfactor = normPSD(N, L, form=norm) if rmsnorm: #normalize to rms of non windowed function. This is independent on 0 constant term, # but also alter its meaning normfactor = normfactor * np.nanstd(y)**2 / np.nanstd(y * win)**2 psd = 2 * normfactor * np.abs(yfft)**2 psd[0] = psd[0] / 2 #the first component is not doubled freqs = np.fft.rfftfreq(N, np.float(L) / (N - 1)) if retall: return freqs, psd, np.angle(yfft) else: return freqs, psd
def test_HEW(): #datafile=r'test\01_mandrel3_xscan_20140706.txt' print("uses Spizzichino's formula to predict PSF on sinusoidal ") x = np.linspace(0, 300., 100) y = np.cos(6 * np.pi * x / span(x, size=True)) / 60000. xout = np.linspace(-5, 5, 1000) / 206265. #x,y=np.genfromtxt(datafile,unpack=True,delimiter=',') #y=y-line(x,y) plt.figure('profile') plt.clf() plt.title('profile') plt.plot(x, y) plt.plot(x, line(x, y)) plt.xlabel('Axial Position (mm)') plt.ylabel('Profile height (mm)') plt.figure('PSF') plt.clf() alpha = 89.79 plt.title('PSF, alpha=%f6.2' % alpha) xout, yout = PSF_spizzichino(x, y, alpha=alpha, xout=xout) plt.xlabel('angular position around alpha (arcsec)') plt.ylabel('Intensity (a.u.)') plt.plot(xout * 206265., yout) plt.show() print('done!') return xout, yout
def external_roi_rect(points): """ finds the smallest (straight to axis) rectangle containing all points. Points are passed as N x 2 array, returns two couples (x0,x1) (y0,y1) """ return span(points, axis=0)
def FFTtransform(x, y): yfft = np.fft.rfft( y ) #note that rfft return exact values as fft (needs to be doubled after squaring amplitude) N = len(y) L = span(x, True) f = np.fft.rfftfreq(N, L / N) return f, yfft
def PSF_raimondiSR(x, y, alpha=0, xout=None, energy=1.): """Try to use theory from Raimondi and Spiga A&A2015 to calculate the PSF for single reflection, return a vector of same length as xout. alpha is incidence angle from normal in degrees, alpha= 90 - shell slope for tilt removed profiles. Tilt can be included in the profile, in that case alpha is left to 0 (total slope must be <0 and alpha>0, this means that profile with tilt is expected to be <0). xout can set the output intervals in theta on the focal plane (from specular angle), if not set 512 points are used. Lambda is wavelength in keV. """ """ R0 e' necessario ma solo per normalizzare la PSF. In realtaà se nella formula all'ultima riga sostituisci dr1 = L*sin(alpha) = L*R0/2f (usi la singola riflessione, giusto?) vedrai che R0 se ne va e non serve saperlo. f a questo punto sara' semplicemente la distanza alla quale si valuta il campo, non necessariamente la focale. dr1 = L*sin(alpha) = L*R0/2f """ lambda_mm = 12.398425 / energy / 10**7 if xout is None: lout = 1001 else: lout = len(xout) L = span(x, size=True) deltax = L / (len(x)) #calculate and remove slope as alpha. Profile after tilt removal is yl slope = line(x, y) yl = y - slope #adjust incidence angle to include the slope removed frorm profile leveling. # Increasing profile is positive slope angle: alpha = alpha * np.pi / 180 - np.arctan2(y[-1] - y[0], x[-1] - x[0]) if alpha <= 0: raise ValueError thmax = lambda_mm / (2 * deltax * (np.pi / 2 - alpha)) #xout is the array of theta for the output if xout is None: xout = np.linspace(alpha - thmax, alpha + thmax, lout) else: xout = xout + alpha dR1 = L * np.sin(alpha) #L1 R0 = 1 #F*np.tan(4*alpha) d20 = 1 #randdomly set things to 1 z1 = 1 PSF = 1 / 2 * F / (L * lambda_mm) * np.abs([ np.sqrt(y / d20) * np.exp(-2 * np.pi * 1.j / lambda_mm * (d20 - z1 + x**2 / (2 * (S - x)))) ].sum() * deltax)**2 #L1 """ spizzichino original: scale=np.sqrt(2.) I=[np.abs((deltax/L*(np.exp(2*np.pi*1.j/lambda_mm*(x*(np.sin(alpha)-np.sin(theta))-scale*yl*(np.cos(alpha)+np.cos(theta)))))).sum())**2 for theta in xout] """ """ The above is equivalent to (iterate on all theta in xout): I[theta]=np.abs((deltax/L*(np.exp(2*np.pi*1.j/lambda_mm*(x*(np.sin(alpha)-np.sin(theta))-scale*yl*(np.cos(alpha)+np.cos(theta)))))).sum())**2 """ return xout - alpha, I
def PSF_spizzichino(x, y, alpha=0, xout=None, energy=1., level=True, HEW=True): """Try to use spizzichino theory as in PR notes to calculate Hthe PSF, return a vector of same length as xout. alpha is incidence angle from normal in degrees, alpha= 90 - shell slope for tilt removed profiles. Tilt can be included in the profile, in that case alpha is left to 0 (total slope must be <0 and alpha>0, this means that profile with tilt is expected to be <0). xout can set the output intervals in theta on the focal plane (from specular angle), if not set 512 points are used. Lambda is wavelength in keV.""" lambda_mm = 12.398425 / energy / 10**7 if xout is None: lout = 1001 else: lout = len(xout) L = span(x, size=True) deltax = L / (len(x)) #calculate and remove slope as alpha. Profile tilt removed is yl if level: slope = line(x, y) yl = y - slope #adjust incidence angle to include the slope removed from profile leveling. # Increasing profile is positive slope angle: alpha = alpha * np.pi / 180 - np.arctan2(y[-1] - y[0], x[-1] - x[0]) else: yl = y if alpha <= 0: raise ValueError thmax = lambda_mm / (2 * deltax * (np.pi / 2 - alpha)) #xout is the array of theta for the output if xout is None: xout = np.linspace(alpha - thmax, alpha + thmax, lout) else: xout = xout + alpha scale = np.sqrt(2.) I = np.array([ np.abs((deltax / L * (np.exp(2 * np.pi * 1.j / lambda_mm * (x * (np.sin(alpha) - np.sin(theta)) - scale * yl * (np.cos(alpha) + np.cos(theta)))))).sum())**2 for theta in xout ]) """ The above is equivalent to (iterate on all theta in xout): I[theta]=np.abs((deltax/L*(np.exp(2*np.pi*1.j/lambda_mm*(x*(np.sin(alpha)-np.sin(theta))-scale*yl*(np.cos(alpha)+np.cos(theta)))))).sum())**2 """ #if HEW: # calculate_HEW(xout-alpha,I,center=None,fraction=0.5) return xout - alpha, I
def make_sag(nx, ny): """ create surface with sag along y with peak-to-valley 1. Note that this is the range of data, not of the surface (i.e. if number of points in y is even, the analytical minimum of the surface lies between the two central pixels and it is not included in data)""" ss = (np.arange(ny) - (ny - 1) / 2.)**2 #makes a parabola centered on 0 ss = ss / span(ss, size=1) - np.min(ss) #rescale height to 1 sag = np.repeat(ss[np.newaxis, :], nx, axis=0).T #np.tile(ss,nx).reshape(ny,nx) return sag, np.arange(nx), np.arange(ny)
def test_make_Signal(): # test make_signal x = np.linspace(-0.5, 0.5, 100) plt.clf() print("X span: %s N: %i" % (span(x), len(x))) #plt.plot(*make_signal(2.,x,nwaves=3)) #plt.plot(*make_signal(1.,x,nwaves=3,phase=np.pi/2),'o') plt.plot(*make_signal(1., nwaves=3, L=10, N=100), 'o') plt.plot(*make_signal(2., nwaves=3, L=[1, 11], N=100), 'x') plt.plot(*make_signal(2., nwaves=3, L=[1, 11], N=100, phase=np.pi / 2)) plt.plot(*make_signal(2., x, nwaves=3, L=100, phase=np.pi / 2))
def test_psd_normalization(x, y, wfun=None, norm=1, **kwargs): """Calculate PSD with a given normalization and compare its integral over frequency and sum to rms square and standard deviation (they differ for rms including also offset, while stddev being referred to mean.""" f, p = psd(x, y, wfun=wfun, norm=norm, **kwargs) print("== Quantities calculated from profile ==") print("Profile Height PV %6.3g (min: %6.3g, max: %6.3g)" % (np.nanmax(y) - np.nanmin(y), np.nanmin(y), np.nanmax(y))) print("Profile Height avg.: ", np.nanmean(y)) print("devstd**2=", np.std(y)**2, '(devstd=%f5.3)' % np.std(y)) print("rms**2=", (y**2 / len(x)).sum()) #, '(rms=%f5.3)'%np.std(y) print("\n== Quantities calculated from PSD ==") print("sum of PSD is ", p[1:].sum()) print("integral of PSD (as sum*deltaf) is ", p[1:].sum() / span(x, 1)) #span(x,size=1)=1/L print("integral trapz: ", np.trapz(p[1:], f[1:])) print("psd[0]*deltaf=%f (integral including:%f)" % (p[0] * f[1], p.sum() / span(x, 1))) print("#--------\n") return f, p
def radial_slice(x,y,angle,xyc,plot=False, *args,**kwargs): """Gives extreme points of a segment passing by xc, yc with slope angle in radians inside the span of points x and y. works in all four quadrants.""" if len(np.shape(angle))==0: angle = [angle] xx = x - xyc[0] yy = y - xyc[1] xs = span(xx) ys = span(yy) xxs = [] for a in angle: cx = np.cos(a) cy = np.sin(a) t = xs/cx #parameter that brings to the x limits t2 = ys/cy #parameter that brings to the y limits # they are one negative and one positive if center in rectangle. tmin = max(min(t),min(t2)) tmax = min(max(t),max(t2)) p1 = (xyc[0]+tmin*cx,xyc[1]+tmin*cy) p2 = (xyc[0]+tmax*cx,xyc[1]+tmax*cy) xxs.append((p1,p2)) if plot: l = plt.plot(*zip(p1,p2),*args,**kwargs) c = l[-1].get_color() plt.plot(*p1,'x',color = c) plt.plot(*p2,'^', color = c) #plt.arrow(*p1,*(np.array(p2)-np.array(p1)),*args,**kwargs) plt.show() return np.array(xxs)
def plot_difference(p1t, p4, trim=None, dis=False): """plots and return difference of two Data2D objects, return difference. All data are plane leveled before plots, a common color scale is set after excluding outliers. Leveled difference is returned. If trim is other than None, plots are adjusted on valid data x and y range, if Trim = True empty borders are removed also from difference data.""" if trim is not None: if trim: p1t = p1t.remove_nan_frame() p4 = p4.remove_nan_frame() xr = span(np.array([span(d.remove_nan_frame().x) for d in [p1t, p4]])) yr = span(np.array([span(d.remove_nan_frame().y) for d in [p1t, p4]])) else: xr = span(np.array([span(d.x) for d in [p1t, p4]])) yr = span(np.array([span(d.y) for d in [p1t, p4]])) plt.clf() ax1 = plt.subplot(131) p1t.level((1, 1)).plot() #plt.title('PZT + IrC') plt.clim(*remove_outliers(p1t.level().data, nsigma=2, itmax=3, span=1)) plt.grid() ax2 = plt.subplot(132, sharex=ax1, sharey=ax1) p4.level((1, 1)).plot() #plt.title('PZT') plt.clim(*remove_outliers(p4.level().data, nsigma=2, itmax=3, span=1)) plt.grid() ax3 = plt.subplot(133, sharex=ax1, sharey=ax1) diff = (p1t - p4).level((1, 1)) diff.name = 'Difference 1-2' diff.plot() plt.clim(*remove_outliers(diff.level().data, nsigma=2, itmax=3, span=1)) plt.grid() plt.xlim(*xr) #this adjust all plots to common scale plt.ylim(*yr) if dis: display(plt.gcf()) return diff
def line(x, y=None): """return line through end points of x,y. x and y are vectors, can be of different length (e.g. y can be a 2-elements vector). If only one vector is provided, it is assumend as equally spaced points. """ x = np.array(x) if y is None: y = x x = np.arange(len(y)) y = np.array(y) #account for nan sel = ~np.isnan(y) if sel.any(): y0 = [y[sel][0], y[sel][-1]] if len(x) == len(y): x0 = x[sel] else: x0 = x L = span(x0, size=1) return (x - x0[0]) * (y0[-1] - y0[0]) / L + y0[0] else: return y
def psd2d(data, x, y, wfun=None, norm=1, rmsnorm=False): """Calculate the 2d psd. return freq and psd. doesnt work with nan. use 2d function for psd np.fft.rfft2 for efficiency and mimics what done in pySurf.psd.psd norm defines the normalization, see function psd.normPSD. 2017/01/11 broken interface from (x,y,data..), added check to correct on the base of sizes.""" #2017/08/01 complete refactoring, this was internal _psd2d, # now is made analogous of 1d pySurf.psd.psd # The previous psd2d was including a lot of plotting and output, # it was renamed in plot_psd2d #attention, normalization doesn't really work with # window, the rms is the rms of the windowed function. #assert data.shape[0]==data.shape[1] if wfun is None: win=np.ones(data.shape[0])[:,None] else: win=wfun(data.shape[0])[:,None] N=data.shape[0] L=span(y,True) yfft=np.fft.rfft2(data*win,axes=[0]) normfactor=normPSD(N,L,form=norm) if rmsnorm: #normalize to rms of non windowed function normfactor=normfactor*np.nanstd(data,axis=0)**2/np.nanstd(data*win,axis=0)**2 psd=2*normfactor*(np.abs(yfft))**2 psd[0,:]=psd[0,:]/2. freqs = np.fft.rfftfreq(N,np.float(L)/(N-1)) return freqs,psd
def trioptimize(d1, d2, fom, testvals=None, rois=None, outfile=None, noplot=False, dis=True): """run optimizations using scale_fit2 over a set of cases defined by the values in matching lists of testvals (array of scaling test values) and rois ([[xmin,xmax],[ymin,ymax]]). Returns a pandas data structure with parameters and best scaling values for each case, with fields ['tbest','testval','roi','fom_roi']. Plot d1 and d2 are Data2D objects. testvals: list of vectors to use as test values for each of the ROI rois: list of rois (same len as testvals), in form [[xmin,xmax],[ymin,ymax]], set to None to use for range. outfile: noplot: dis: """ if outfile is not None: outname = os.path.basename(outfile) outfolder = os.path.dirname(outfile) else: outname = None plt.close('all') res = pd.DataFrame( index=['tbest', 'testval', 'roi', 'fom_roi', 'fom_initial' ]).transpose() if any_is_none(testvals): if fom != fom_rms: raise ValueError( 'analytical fit (without testval) supported only for fom_rms') if testvals is None: testvals = [None] * len(rois) #Minimizes on each subaperture for i, (roi, testval) in enumerate(zip(rois, testvals)): name = 'crop%02i' % ( i + 1) if outname is None else outname + '_crop%02i' % (i + 1) #if dis print optimization parameters if dis: print("\n\n\n=== CASE %i: %s ===\n" % (i + 1, name)) if roi is None: print('FULL APERTURE') else: print("CROP: [%.2f:%.2f] [%.2f:%.2f]" % tuple(roi[0] + roi[1])) print("TEST RANGE:", span(testval)) dtest = (d1 - d2).crop(*roi) if roi is not None else d1 - d2 fom0 = fom(*dtest()) print("INITIAL FOM:", fom0) #do optimization, plot bestfit on ROI, displaying plot if dis is True s = scale_fit2( d1, d2, testval, dis=dis if roi is not None else False, #it will plot later if no roi fom=fom, outfile='' if outname is None else fn_add_subfix( outfile, '_crop%02i' % (i + 1)), roi=roi, crop=dis) #if dis is set, plot roi, otherwise skip #calculate and store best fit ftest = fom(*((d1 - d2 * s).crop(*roi))()) if roi is not None else fom( *(d1 - d2 * s)()) b = pd.Series( [s, testval, roi, ftest, fom0], index=['tbest', 'testval', 'roi', 'fom_roi', 'fom_initial'], #fom_initial added 04/02 name=name) res = res.append(b) #plot best fit case full range unless noplot if not noplot: plt.close('all') comp2(d1, d2 * s, roi=roi) # plots entire area with rectangle dtest = (d1 - d2 * s) plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.suptitle('best factor:%6.3f, FOM surf:%6.3f' % (s, fom(*dtest()))) if outname: plt.savefig( fn_add_subfix(outfile, '_croproi_%02i' % (i + 1), '.png')) if dis: display(plt.gcf()) if dis: print("BEST SCALE: %.3f" % s, " FOM:", ftest) if dis: print("\n===\nSUMMARY:") #a=pd.DataFrame(index=['tbest','testval','roi','fom_roi']).transpose() for roi, r in zip(rois, res.iterrows()): if dis: if roi is None: print('FULL APERTURE--> BEST SCALE: ', r[1]['tbest']) else: #pdb.set_trace() #not sure why this is needed, but r[0] is the dataseries name, r[1] the dataseries print("CROP: [%.2f:%.2f] [%.2f:%.2f]" % tuple(roi[0] + roi[1]), "--> BEST SCALE: ", r[1]['tbest']) print("=======================") return res
def plotFEA(FEAfile, FEAreader, datafile=None, outname=None, markers=None): """ read FEA and resample/rebin both points and FEA on the grid defined by steps, subtract gravity from pts and plot and returns the corrected points. Simulation and data are also plotted if valid. If datafile is not passed only simulation data are plotted and returned. FEA reader is a function that accept a single argument FEAfile and return an array of points describing changes in shape (x,y,dz).""" rebin = False fpts = FEAreader(FEAfile) if trans is not None: fpts = trans(fpts) if datafile is not None: pts = get_points(datafile, delimiter=' ') if rebin: pts = rebin_points(pts, steps=steps) fp = rebin_points(fpts, steps=steps) else: xl, yl = span(pts, axis=0)[:2, :] xg = np.arange(xl[0], xl[1], steps[0]) yg = np.arange(yl[0], yl[1], steps[1]) pts = resample_grid(pts, xg, yg) fp = resample_grid(fpts, xg, yg) pts0 = subtract_points(pts, fp) else: pts0 = pts pts = None #diagnostic plots for gravity subtraction if pts is not None: plt.figure('pts0') #corrected plt.clf() plot_points(pts0, scatter=1, aspect='equal') plt.title('points from file') if markers is not None: plt.plot(markers[:, 0], markers[:, 1], '+w', label='support') if datafile is not None: plt.figure('pts') #data plt.clf() plot_points(pts, scatter=1, aspect='equal') plt.title('resampled and gravity corrected') plt.savefig(fn_add_subfix(outname, '_corrected', '.png')) if markers is not None: plt.plot(markers[:, 0], markers[:, 1], '+w', label='support') plt.figure('fp') #simulation plt.clf() plot_points(fp, scatter=1, aspect='equal') plt.title('gravity effect from FEA') if markers is not None: plt.plot(markers[:, 0], markers[:, 1], '+', label='support') #here x and y inverted plt.savefig(fn_add_subfix(outname, '_FEA', '.png')) if outname is not None: save_points(pts0, fn_add_subfix(outname, '', '.dat')) save_points(pts0, fn_add_subfix(outname, '_matrix', '.dat'), matrix=1) save_points(pts0, fn_add_subfix(outname, '_matrix', '.fits'), matrix=1) return pts0
def plot_transform(f, yfft, label="", euler=False, units=None, includezerofreq=False, **kwargs): """units is a 2 element vector.""" #pdb.set_trace() ax3 = plt.gca() ##PLOT TRANSFORM AS EULER REPRESENTATION unitstr = psd_units(units) if not includezerofreq: if f[0] == 0: f = f[1:] yfft = yfft[1:, ...] if euler: plt.title('DFT - Module and phase') plt.plot(f, np.abs(yfft), label=label, **kwargs) #plt.loglog() ax3.legend(loc=2) #string for x is set to '' as default: plt.xlabel('Freq (' + (unitstr[0] if unitstr[0] else '[X]') + '$^-1$)') plt.ylabel('Module' + ((' (' + unitstr[1]) if unitstr[1] else '[Y]') + '$^2$)') ax3b = ax3.twinx() ax3.twin = ax3b ax3b.plot(f, np.angle(yfft) / np.pi, '--', label=label + ' Phase', **kwargs) ax3b.set_ylabel('Phase /$\pi$') ax3b.set_ylim(-1., 1.) plt.grid(1) ax3b.legend(loc=1) ##PLOT TRANSFORM AS COMPLEX NUMBER else: #pdb.set_trace() plt.title('DFT - Real and imaginary parts') ax3.plot(f, np.real(yfft), label=label + ' Re', **kwargs) #plt.loglog() s = span(np.real(yfft)) plt.ylim([s[0], s[1]]) # needed to expland scale if very small #ax3.set_xlabel(unitstr[0]+'$^-1$') #string for x is set to '' as default: plt.xlabel('Freq. (' + (unitstr[0] if unitstr[0] else '[X]') + '$^-1$)') ax3.set_ylabel('Re') #for tl in ax2.get_yticklabels(): #tl.set_color('b') ax3.legend(loc=2) ax2b = ax3.twinx() ax2b.plot(f, np.imag(yfft), '--', label=label + ' Im', **kwargs) s = span(np.imag(yfft)) plt.ylim([s[0], s[1]]) # needed to expland scale if very small ax2b.set_ylabel('Im') #for tl in ax2b.get_yticklabels(): #tl.set_color('r') plt.grid(True) ax2b.legend(loc=1) ax3.twin = ax2b plt.tight_layout
def compare_images(datalist, x=None, y=None, fignum=None, titles=None, vmin=None, vmax=None, commonscale=False, axis=0, axmax=0, *args, **kwargs): """return a generator that plots n images in a list in subplots with shared zoom and axes. datalist is a list of data in format (data, x, y). x and y provide plot range (temporarily, ideally want to be able to plot same scale). fignum window where to plot, if fignnum is 0 current figure is cleared, if None new figure is created. axis defines the axis with larger number of plots (default ncols >= nrows) axmax maximum nr of plots along shorter dim. """ #modified again 2018/06/05 to accept list of data,x,y triplets. x and y are # accepted as range. # old docstring was: # return a generator that plots n images in a list in subplots with shared # zoom and axes. datalist is a list of 2d data on same x and y. # fignum window where to plot, if fignnum is 0 current figure is cleared, # if None new figure is created. # this was changed a couple of times in interface, for example in passing # ((data,x,y),...) rather than (data1,data2),x,y # it can be tricky in a general way if data don't have all same size, # at the moment it is assumed they have. # in any case x and y are used only to generate the extent, # that is then adapted to the size of data. gridsize = find_grid_size(len(datalist), axmax) if axis == 1: gridsize = gridsize[::-1] fig = fignumber(fignum) plt.clf() ax = None # this is to set scale if not fixed d1std = [np.nanstd(data[0]) for data in datalist] std = min(d1std) for i, d in enumerate(datalist): """adjust to possible input formats""" data, x, y = d if x is None: x = np.arange(data.shape[1]) if y is None: y = np.arange(data.shape[0]) ax = plt.subplot(gridsize[0], gridsize[1], i + 1, sharex=ax, sharey=ax) if titles is not None: print("titles is not none, it is ", titles) plt.title(titles[i]) # plt.xlabel('X (mm)') # plt.ylabel('Y (mm)') s = (std if commonscale else d1std[i]) d1mean = np.nanmean(data) aspect = kwargs.pop('aspect', None) axim = plt.imshow(data, extent=np.hstack([span(x), span(y)]), interpolation='none', aspect=aspect, vmin=kwargs.get('vmin', d1mean - s), vmax=kwargs.get('vmax', d1mean + s), *args, **kwargs) plt.colorbar() yield ax
def psd2d_analysis(wdata,x,y,title=None,wfun=None,vrange=None, rmsrange=None,prange=None,fignum=5,rmsthr=None, aspect='auto', ax2f=None, units=None,outname=None,norm=1,rmsnorm=True): """ Calculates 2D PSD as image obtained combining all profile PSDS calculated along vertical slices of data. Resulting image has size If title is provided rms slice power is also calculated and plotted on three panels with figure and PSD. Return PSD as PSD2D object. uses plot_rms_power(f,p,rmsrange=None) to calculate rms power. fignum window where to plot, if fignnum is 0 current figure is cleared, if None new figure is created. Default to figure 5. rmsrange is one or a list of frequency ranges for plotting integrated rms. Can contain None to use max or min. If axf2 is set to boolean or array of boolean, plot slice rms for the frequencies associated to rmsrange on second axis. rmsrange must be set accordingly (same number of elements). rmsthr sets a threshold for data inclusion. If rms is above the value, the line is considered to contain invalid data and is removed from output PSD. This makes it easy to average the returned PSDs. Corresponding data are still visualized in central panel, but are marked with a red cross at top of y axes. If multiple rms range intervals are provided, line is removed if any of them is over the threshold, but this might change in future, e.g. TODO: make it possible to add vector threshold with as many elements as rms range intervals. Set outname to empty string to plot without generating output. typical values: rmsrange=[[None,0.1],[0.1,0.2],[0.2,1],[1,None]] #list of frequency intervals for rms ax2f=[0,1,1,0,0] # axis where to plot rms corresponding to intervals in rmsrange: 1 right, 0 left wfun = np.hanning # type of windows for fourier transform units = ("mm","mm","$\mu$m") # units of surface data from which PSD is calculated vrange_surf=([-0.5,0.5]) #color scale of surface map vrange_leg=([-0.05,0.05]) #color scale of legendre removed map prange=np.array((1e-8,1.e-1))#np.array((5e-8,1.e-5)) #color scale of 2d psd plot fs,ps=psd2d_analysis(levellegendre(y,wdata,2),x,y,outname="",wfun=np.hanning, rmsrange=rmsrange,prange=prange,ax2f=ax2f,units=units) """ #2018/04/05 # -critical renaming of plot_psd2d(wdata,x,y..) -> psd2d_analysis # and _plot_psd2d(f,p,x) -> plot_psd2d consistently with other routines. # -added title to determine if generating plot, with the intent of replacing outname. plt.pause(1) #print('cane',fignum) if outname is not None: #handle backcompatibility print ('psd2d_analysis WARNING: `title` replaced `outname` and output figure will be no more generated.'+ 'OUTNAME will be removed in next version, use title and save the plot after returning from routine.') if title is not None: print('outname should be not used together with title, it will be ignored.') else: title=outname if vrange is not None: if prange is None: print("""WARNING: older versions of psd2d use vrange as range of psdvalue. In updated version prange is range of psd and vrange is range for surface plots. Update your code.""") #check compatibility with old interface if len(wdata.shape)==1: if len(y.shape)==2: print("WARNING: your code calling psd2d uses old call") print("psd2d(x,y,data), automaticly adjusted, but") print("update IMMEDIATELY your code to call") print("psd2d(data,x,y)") x,y,wdata=wdata,x,y f, p = psd2d(wdata, x, y, wfun=wfun, norm=norm, rmsnorm=rmsnorm) # calculate PSD 2D # GENERATE OUTPUT if title is not None: if np.size(units)==1: units=np.repeat(units,3) if prange is None: if f[0] == 0: prange=span(p[1:,:]) else: prange=span(p) if prange[0]<1e-20: #arbitrary small value print("WARNING: low limit detected in prange, can cause problems with log color scale.") if vrange is None: vrange=span(wdata) #plot surface map, who knows why on figure 5 fig=fignumber(fignum) #pdb.set_trace() # plt.clf() maximize() plt.draw() plt.suptitle(title) ################ #plot Surface ax1=plt.subplot(311) plot_data(wdata,x,y,aspect=aspect,vmin=vrange[0],vmax=vrange[1],units=units,title='Surface',stats=True) plt.xlabel(None) plt.grid(1) ################ #plot 2D PSD ax2=plt.subplot(312,sharex=ax1) plot_psd2d(f,p,x,prange=prange,units=units) if rmsrange is not None: #plot horizontal lines to mark frequency ranges for rms extraction, #rms is tranformed to 2D array if len(np.array(rmsrange).shape)==1: #if only one interval, make it 2D anyway rmsrange=[rmsrange] rmsrange=np.array(rmsrange) for fr in np.array(rmsrange): #I want fr to be array type, even if I don't care about rmsrange this does the job ax2.hlines(fr[fr != None],*((span(x)-x.mean())*1.1+x.mean())) ax2.set_xlabel("") plt.xlabel(None) #plt.subplots_adjust(top=0.85) ################ #plot rms slice ax3=plt.subplot(313,sharex=ax1) if f[0]==0: #ignore component of 0 frequency in rms calculation if present ff,pp=f[1:],p[1:] else: ff,pp=f,p rms=plot_rms_power(ff,pp,x,rmsrange=rmsrange,ax2f=ax2f,units=units) #pdb.set_trace() mask=np.isfinite(rms) #True if good. it is an array if rmsthr is not None: mask = mask & (rms < rmsthr) ax3.hlines(rmsthr,*ax2.get_xlim()) #plot markers on rms chart ax2.plot(x[~mask],np.repeat(ax2.get_ylim()[1],len(x[~mask])),'rx') #plot markers on psd2d chart #pdb.set_trace() #questa era qui, ma dava errore perche' mask e' lineare #mask = np.all(mask,axis =0) # if any of mask is False -> False p[:,~mask]=np.nan ax3.grid(1) #plt.tight_layout(rect=(0, 0.03, 1, 0.95) if title else (0, 0, 1, 1)) # replaced with more evolved interactive resize: # plt.colorbar().remove() #dirty trick to adjust size to other panels def resize(event): box1 = ax1.get_position() box2 = ax2.get_position() box3 = ax3.get_position() ax2.set_position([box1.x0, box2.y0, box1.width , box2.height]) ax2.set_adjustable("box",share=True) ax3.set_position([box1.x0, box3.y0, box1.width , box3.height]) ax3.set_adjustable("box",share=True) cid = fig.canvas.mpl_connect('draw_event', resize) cid2 = fig.canvas.mpl_connect('resize_event', resize) resize(None) #plt.tight_layout() if outname: #kept for compatibility, will be removed in next version plt.savefig(fn_add_subfix(outname,'_2dpsd','.png')) return f,p
def xdiff_images(data1, data2, x=None, y=None, fignum=None, titles=None, vmin=None, vmax=None, commonscale=False, direction=0, *args, **kwargs): """plots two data sets with common axis and their difference. Return the three axis. Colorbars are formatted to be same height as plot. """ fig = fignumber(fignum) plt.clf() ax = None aspect = kwargs.pop('aspect', 'auto') #this is to set scale if not fixed d1std = [np.nanstd(data) for data in (data1, data2)] std = min(d1std) if x is None: x = np.arange(data1.shape[1]) if y is None: y = np.arange(data1.shape[0]) ax1 = plt.subplot(131, sharex=ax, sharey=ax) s = (std if commonscale else d1std[0]) d1mean = np.nanmean(data1) axim = plt.imshow(data1, extent=np.hstack([span(x), span(y)]), interpolation='None', aspect=aspect, vmin=kwargs.get('vmin', d1mean - s), vmax=kwargs.get('vmax', d1mean + s), *args, **kwargs) plt.colorbar(fraction=0.046, pad=0.04) ax2 = plt.subplot(132, sharex=ax, sharey=ax) s = (std if commonscale else d1std[1]) d2mean = np.nanmean(data2) axim = plt.imshow(data2, extent=np.hstack([span(x), span(y)]), interpolation='None', aspect=aspect, vmin=kwargs.get('vmin', d2mean - s), vmax=kwargs.get('vmax', d2mean + s), *args, **kwargs) plt.colorbar(fraction=0.046, pad=0.04) ax3 = plt.subplot(133, sharex=ax, sharey=ax) plt.title('Difference (2-1)') diff = data2 - data1 axim = plt.imshow(diff, extent=np.hstack([span(x), span(y)]), interpolation='None', aspect=aspect, *args, **kwargs) plt.colorbar(fraction=0.046, pad=0.04) axes = [ax1, ax2, ax3] if titles is not None: for ax, tit in zip(axes, titles): if tit is not None: ax.set_title(tit) return axes
def __init__(self, x=None, y=None, file=None, reader=None, units=None, name=None, *args, **kwargs): """can be initialized with data; x,y; file; file, x if x is provided, they override x from data if matching number of elements, or used as range if two element (error is raised in case of ambiguity).""" #from pySurf.readers.instrumentReader import reader_dic from pyProfile.profile import register_profile #pdb.set_trace() if isinstance(y, str): print('first argument is string, use it as filename') file = y y = None else: y = np.array(y) #onvert to array if not #pdb.set_trace() self.file = file #initialized to None if not provided if file is not None: assert y is None # passed file AND xrange, overrides x because only range matters. #store in xrange values for x if were passed xrange = span(x) if x is not None else None #pdb.set_trace() if reader is not None: raise NotImplementedError( "readers are not implemented yet for profiles," + "\tPass data or read from two column text file in compatible format." ) self.load(file, *args, **kwargs) """ if reader is None: reader=auto_reader(file) #returns a reader """ from pyProfile.profile import load_profile x, y = load_profile( file, *args, **kwargs ) #calling without arguments skips register, however skips also reader argumnets, temporarily arranged with pop in read_data to strip all arguments for #register data and pass the rest to reader #pdb.set_trace() #TODO: Cleanup this part and compare with pySurf. Consider centering # of axis coordinates as in grid example. if np.size(xrange) == np.size(y): # x is compatible with data size if np.size(xrange) == 2: print( 'WARNING: You passed both data and x, with size 2. There may be some ambiguity in how coordinates are handled' ) self.x = xrange elif np.size(xrange) == 2: #here it must be intended as a range. Should check also interval centerings. #it was considered somewhere in pySurf. x = np.linspace(*xrange, np.size(y)) elif xrange is not None: print( "wrong number of elements for x (must be 2 or xsize_data), it is instead", np.size(xrange)) raise ValueError # set self.header to file header if implemented in reader, otherwise set to empty string"" #questo dovrebbe sempre fallire try: #kwargs['header']=True self.header = reader(file, header=True, *args, **kwargs) except TypeError: #unexpected keyword if header is not implemented self.header = "" #raise else: if y is not None: if len(y.shape) != 1: #pdb.set_trace() print( 'WARNING: data are not uniidimensional, results can be unpredictable!' ) if x is None: x = np.arange(np.size(y)) #if data is not None: x, y = register_profile( x, y, *args, **kwargs) # se load_profile calls register, this #goes indented. self.x, self.y = x, y if np.size(units) == 1: units = [units, units] self.units = units if name is not None: self.name = name elif file is not None: self.name = os.path.basename(file) else: self.name = ""
def __init__( self, data=None, x=None, y=None, file=None, reader=None, units=None, name=None, *args, **kwargs ): """ A class for 2D data with coordinates and their analysis. Can be initialized with data | data, x, y | file | file, x, y. if x and y are coordinates if match number of elements, or used as range if two element. If provided together with file, they override x and y read from file. Function methods return a copy with new values and don't alter original object. Reassign to variable to use as modifier: e.g. a=a.level( ... ) Args: data (2D array or string): 2D data or file name (suitable reader must provided). x, y (array): coordinates or ranges. file (str): alternative way to provide a data file. units (str array): 3-element array with units symbols for `x`, `y`, `data` reader (function): reader function (see `pySurf.readers.instrumentReader`). name (str): sets the name of created object. *args, **kwargs: optional arguments for `pySurf.data2D.register_data`. """ # from pySurf.readers.instrumentReader import reader_dic # pdb.set_trace() if isinstance(data, str): print("first argument is string, use it as filename") file = data data = None # pdb.set_trace() self.file = file # initialized to None if not provided if file is not None: assert data is None # store in xrange values for x if were passed xrange = span(x) if x is not None else None yrange = span(y) if y is not None else None # pdb.set_trace() if reader is None: reader = auto_reader(file) # returns a reader # calling without arguments skips register, however skips also reader argumnets, temporarily arranged with pop in read_data to strip all arguments for data, x, y = read_data(file, reader, *args, **kwargs) # register data and pass the rest to reader # pdb.set_trace() if np.size(x) == data.shape[1]: self.x = x elif np.size(x) == 2: # y and yrange are identical print("WARNING: 2 element array provided for X, uses as range.") x = np.linspace(*xrange, data.shape[1]) elif xrange is not None: print( "wrong number of elements for x (must be 2 or xsize_data [%i]), it is instead %i" % (np.size(data)[1], np.size(x)) ) raise ValueError if np.size(y) == data.shape[0]: self.y = y elif np.size(y) == 2: # y and yrange are identical print("WARNING: 2 element array provided for Y, uses as range.") x = np.linspace(*yrange, data.shape[0]) elif yrange is not None: print( "wrong number of elements for y (must be 2 or ysize_data [%i]), it is instead %i" % (np.size(data)[0], np.size(y)) ) raise ValueError # set self.header to file header if implemented in reader, otherwise set to empty string"" try: # kwargs['header']=True # self.header=reader(file,header=True,*args,**kwargs) self.header = reader(file, header=True, *args, **kwargs) except TypeError: # unexpected keyword if header is not implemented self.header = "" # raise else: if data is not None: if len(data.shape) != 2: # pdb.set_trace() print( "WARNING: data are not bidimensional, results can be unpredictable!" ) if x is None: x = np.arange(data.shape[1]) if y is None: y = np.arange(data.shape[0]) # if data is not None: # se read_data calls register, this data, x, y = register_data(data, x, y, *args, **kwargs) # goes indented. self.data, self.x, self.y = data, x, y self.units = units if name is not None: self.name = name elif file is not None: self.name = os.path.basename(file) else: self.name = ""
def on_lims_change(ax): nsigma = 1.5 ax.set_autoscale_on(False) #ax.figure.canvas.draw_idle(self) xl = ax.xaxis.get_view_interval( ) #range of plot in data coordinates, sorted lr yl = ax.yaxis.get_view_interval() #sorted bottom top #print xl,yl im = ax.images[-1] imdata = im.get_array( ) #.filled(np.nan) #this helps when a masked array is returned, for some reason it doesn't work dims = imdata.shape #number of pixels #print dims extent = im.get_extent() #this is the range for axis of full data. #ixl=np.interp(xl,extent[0:2],[0,dims[1]]) #pixel indices for plotted data #iyl=np.interp(yl,extent[2:],[0,dims[0]]) ixl = interp1d(extent[0:2], [0, dims[1]], assume_sorted=False, bounds_error=False)(xl).astype( int) #pixel indices for plotted data iyl = interp1d(extent[2:], [0, dims[0]], assume_sorted=False, bounds_error=False)(yl).astype(int) #prevent empty array if np.abs(ixl[1] - ixl[0]) < 1: ixl[1] = ixl[0] + 1 if np.abs(iyl[1] - iyl[0]) < 1: iyl[1] = iyl[0] + 1 #print ixl[0],ixl[1],iyl[0],iyl[1] cdata = imdata[ixl[0]:ixl[1], iyl[0]:iyl[1]] #crange=np.nanmean(cdata)+[-np.nanstd(cdata),np.nanstd(cdata)] #this is a more sensitive view. if nsigma: crange = np.nanmean(cdata) + np.nanstd(cdata) * np.array( [-1., 1.]) * nsigma else: crange = span(cdata) #print ax.__repr__(),cdata #make a copy of zoom history fig = ax.figure s = copy.copy(fig.canvas.toolbar._views) p = copy.copy(fig.canvas.toolbar._positions) title = "min:%4.1f max:%4.1f rms:%4.3f" % ( np.nanmin(cdata), np.nanmax(cdata), np.nanstd(cdata)) #print crange ax.set_title(title) if not (im.colorbar is None): im.colorbar.remove() #if not (title is None): # if title=="": #these commands resets colorbar plt.colorbar() plt.clim(crange) #restore zoom history fig.canvas.toolbar._views = s fig.canvas.toolbar._positions = p ax.figure.canvas.draw_idle()
def scale_fit2(d1, d2, testval=None, fom=fom_rms, outfile='', roi=None, crop=False, dis=True): """d1 and d2 are Data2D objects. Returns value for scaling tbest of d2 that minimizes fom(d1-d2*tbest). e.g. fom = rms of surface/slope. Plot rms as a function of scale factor (if testval are passed for raster scan) and 4 panel plot of best result (use comp2) either cropped (if crop is True) or full. if testval is set to None, best fit is determined by analytical formula (minimizing rms of surface difference). If tesval is array, all values are tested. if `roi` is passed, fom is calculated on the roi only. `crop` flag controls the plotting. If True, only the roi is plotted and color scale is adjusted accordingly. Otherwise, entire region is plotted and a rectangle around the roi is plotted. dis=True print information and display plots in notebook. all fom and stats are calculated on leveled data, ideally I want to have stats in roi unleveled, but it is safer this way for now. """ #dy=np.diff(d1.y)[0] d2 = d2.resample(d1) res = [] if roi is None: diff = d1 - d2 roi = [span(diff.x), span(diff.y)] #stupid way of doing it if testval is None: #use analytical formula tbest = simple_regressor(d1.crop(*roi).data, d2.crop(*roi).data) dtest = (d1 - (d2 * tbest)).crop(*roi).level() rbest = fom(*dtest()) else: #test all values for i, t in enumerate(testval): #test each testval, create a plot (on file only). If dis is True, print vals diff = d1 - d2 * t f = fom(*(diff.crop(*roi))()) res.append(f) axes = comp2(d1, d2 * t, roi=roi) plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.suptitle('factor: %s' % t) if outfile: plt.savefig(fn_add_subfix(outfile, '_%03i' % (i + 1), '.jpg')) if dis: print(i, t, f) #select best value res = np.array(res) ibest = np.argmin(res) tbest = testval[np.argmin(res)] rbest = res[ibest] #make plot with rms as a function of testval plt.figure(5) plt.clf() plt.plot(testval, res) plt.title('best factor:%6.3f, FOM surf:%6.3f' % (tbest, res[ibest])) plt.grid() if outfile: plt.savefig((fn_add_subfix(outfile, '', '.jpg', pre='FOMtrend_'))) if dis: display(plt.gcf()) #plot and display (if dis) best fit data (cropped if crop is selected, full otherwise). if crop: d1 = d1.crop(*roi).level() d2 = d2.crop(*roi).level() plt.figure() a = comp2(d1, d2 * tbest, roi=roi) plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.suptitle('best factor:%6.3f, FOM surf:%6.3f' % (tbest, rbest)) if dis: display(plt.gcf()) if outfile: plt.savefig(fn_add_subfix(outfile, '_fit', '.jpg')) return tbest