def _non_linear_legendre(wcs_dict): """Returns a legendre model Constructs a Legendre1D mathematical model Parameters ---------- wcs_dict : dict Dictionary containing all the wcs information decoded from the header and necessary for constructing the Legendre1D model. Returns ------- `~astropy.modeling.Model` """ model = models.Legendre1D(degree=wcs_dict['order'] - 1, domain=[wcs_dict['pmin'], wcs_dict['pmax']], ) new_params = [wcs_dict['fpar'][i] for i in range(wcs_dict['order'])] model.parameters = new_params return model
def _non_linear_legendre(self): """Set model to Legendre1D """ """Returns a legendre model""" self.model = models.Legendre1D(degree=self.wcs_dict['order'] - 1, domain=[self.wcs_dict['pmin'], self.wcs_dict['pmax']], ) # self.model.parameters[0] = self.wcs_dict['pmin'] for param_index in range(self.wcs_dict['order']): self.model.parameters[param_index] = self.wcs_dict['fpar'][ param_index]
def test_iraf_non_linear_legendre(remote_data_path): legendre_model = models.Legendre1D(degree=3, domain=[21, 4048]) legendre_model.c0.value = 5468.67555891 legendre_model.c1.value = 835.332144466 legendre_model.c2.value = -6.02202094803 legendre_model.c3.value = -1.13142953897 wavelength_axis = legendre_model(range(1, 4143)) * u.angstrom spectrum_1d = Spectrum1D.read(remote_data_path, format='iraf') assert isinstance(spectrum_1d, Spectrum1D) assert_allclose(wavelength_axis, spectrum_1d.wavelength)
def test_linear_fitter_1dlegend(self): """ 1 pset, 1 set 1D x, 1 set 1D y, Legendre 1D polynomial """ expected = np.array([[ 1925.5000000000011, 3444.7500000000005, 1883.2500000000014, 364.4999999999996 ]]).T leg1 = models.Legendre1D(3) leg1.parameters = [1, 2, 3, 4] y1 = leg1(self.x1) pfit = fitting.LinearLSQFitter() model = pfit(leg1, self.x1, y1) assert_allclose(model.param_sets, expected, atol=10**(-12))
p2 = astmodels.Polynomial1D(1) p3 = astmodels.Polynomial1D(1) p4 = astmodels.Polynomial1D(1) m1 = p1 & p2 m2 = p3 & p4 m1.inverse = m2 return m1 test_models = [ astmodels.Identity(2), astmodels.Polynomial1D(2, c0=1, c1=2, c2=3), astmodels.Polynomial2D(1, c0_0=1, c0_1=2, c1_0=3), astmodels.Shift(2.), astmodels.Hermite1D(2, c0=2, c1=3, c2=0.5), astmodels.Legendre1D(2, c0=2, c1=3, c2=0.5), astmodels.Chebyshev1D(2, c0=2, c1=3, c2=0.5), astmodels.Chebyshev2D(1, 1, c0_0=1, c0_1=2, c1_0=3), astmodels.Legendre2D(1, 1, c0_0=1, c0_1=2, c1_0=3), astmodels.Hermite2D(1, 1, c0_0=1, c0_1=2, c1_0=3), astmodels.Scale(3.4), astmodels.RotateNative2Celestial(5.63, -72.5, 180), astmodels.Multiply(3), astmodels.Multiply(10 * u.m), astmodels.RotateCelestial2Native(5.63, -72.5, 180), astmodels.EulerAngleRotation(23, 14, 2.3, axes_order='xzx'), astmodels.Mapping((0, 1), n_inputs=3), astmodels.Shift(2. * u.deg), astmodels.Scale(3.4 * u.deg), astmodels.RotateNative2Celestial(5.63 * u.deg, -72.5 * u.deg, 180 * u.deg), astmodels.RotateCelestial2Native(5.63 * u.deg, -72.5 * u.deg, 180 * u.deg),
'Linear1D': models.Linear1D(1.0, 0.0), 'Const1D': models.Const1D(0.0), 'Redshift': models.Redshift(0.0), 'Scale': models.Scale(1.0), 'Shift': models.Shift(0.0), 'Sine1D': models.Sine1D(1.0, 1.0), 'Chebyshev1D': models.Chebyshev1D(1), 'Legendre1D': models.Legendre1D(1), 'Polynomial1D': models.Polynomial1D(1), } # this nightmarish way of getting the function name results from the way # astropy functional models store them. Both their '_name' and 'name' # attributes are set to None, and a plain, easy to use name is nowhere # to be seen. And worse, the name coding changed dramatically from # astropy 0.4 to 1.0. def getComponentName(function): if issubclass(function.__class__, Fittable1DModel): name = function.__class__() return _getName(name) elif issubclass(function.__class__, PolynomialModel):
def continuum(x, y, output='ratio', degree=6, n_iterate=5, lower_threshold=2, upper_threshold=3, verbose=False, weights=None) -> Union[Iterable, tuple, Callable]: """ Builds a polynomial continuum from segments of a spectrum, given in the form of wl and flux arrays. Parameters ---------- x : array-like Independent variable y : array-like y = f(x) output: string Specifies what will be returned by the function 'ratio' = ratio between fitted continuum and the spectrum 'difference' = difference between fitted continuum and the spectrum 'function' = continuum function evaluated at x degree : integer Degree of polynomial for the fit n_iterate : integer Number of rejection iterations lower_threshold : float Lower threshold for point rejection in units of standard deviation of the residuals upper_threshold : float Upper threshold for point rejection in units of standard deviation of the residuals verbose : boolean Prints information about the fitting weights : array-like Weights for continuum fitting. Must be the shape of x and y. Returns ------- c : tuple c[0]: numpy.ndarray Input x coordinates c[1]: numpy.ndarray See parameter "output". """ assert not np.isnan(x).all(), 'All x values are NaN.' assert not np.isnan(y).all(), 'All y values are NaN.' x_full = copy.deepcopy(x) # NOTE: For now, interp1d skips interpolation of NaNs. s = interp1d(x, y) if weights is None: weights = np.ones_like(x) if np.isnan(y).any(): nan_mask = np.isnan(s(x)) x = x[~nan_mask] weights = copy.deepcopy(weights)[~nan_mask] warnings.warn( 'NaN values found in data! Removed {:d} out of {:d} data points.'. format(np.count_nonzero(nan_mask), len(x_full)), category=RuntimeWarning, ) model = models.Legendre1D(degree=degree) fitter = fitting.LinearLSQFitter() for i in range(n_iterate): f = fitter(model, x, s(x), weights=weights) res = s(x) - f(x) sig = np.std(res) rej_cond = ((res < upper_threshold * sig) & (res > -lower_threshold * sig)) if np.sum(rej_cond) <= degree: if verbose: warnings.warn( 'Not enough fitting points. Stopped at iteration {:d}. sig={:.2e}' .format(i, sig)) break if np.sum(weights == 0.0) >= degree: if verbose: warnings.warn( 'Number of non-zero values in weights vector is lower than the polynomial degree. ' 'Stopped at iteration {:d}. sig={:.2e}'.format(i, sig)) break x = x[rej_cond] weights = weights[rej_cond] if verbose: print('Final number of points used in the fit: {:d}'.format(len(x))) print('Rejection ratio: {:.2f}'.format(1. - float(len(x)) / float(len(x_full)))) p = fitter(model, x, s(x), weights=weights) out = dict( ratio=(x_full, s(x_full) / p(x_full)), difference=(x_full, s(x_full) - p(x_full)), function=(x_full, p(x_full)), polynomial=p, ) return out[output]
def rb_iter_contfit(wave, flux, error, **kwargs): ''' Iterative continuum fitter using Legendre polynomials Input: - wavelength array flux array error array optional input: maxiter :- maximum iteration [25 default] order :- polynomial order of fit [4 default] output: - fit_final : Final fitted continuum array resid_final : residual error array fit_error : error on the fit [standard deviation of the residual] Written by: Rongmon Bordoloi Tested on Python 3.7 Sep 4 2019 -------------------------- example : from IGM import rb_iter_contfit as r out= r.rb_iter_contfit(wave,flux,error,order=5) out[0] = fitted continuum ''' if 'maxiter' in kwargs: maxiter = kwargs['maxiter'] else: maxiter = 25 if 'order' in kwargs: order = kwargs['order'] else: order = 4 #Initialize a mask mask = np.ones((np.size(wave), )) # Looking for chip gaps chip_gap = np.where(((error == 0) & (flux == 0)) | (error - flux == 0)) if (np.size(chip_gap) > 0): #error[chip_gap]=1; #flux[chip_gap]=1; mask[chip_gap] = 0 # Now get rid of negative error values qq = np.where(error <= 0) if (np.size(qq) > 0): error[qq] = np.median(error) #Do a sanity check to avoid bad flux values q = np.where(flux <= 0) if (np.size(q) > 0): flux[q] = error[q] w = 1 / error**2. # Weight index = 0 #Initialize the counter outside_chip_gap = np.where(((error != 0) & (flux != 0)) | (error - flux != 0)) med_err = np.median(error[outside_chip_gap]) med_flux = np.median(flux[outside_chip_gap]) # Now take out any possible emission or absorption features #%Flux values less than the median error are 1sigma within zero, so should be masked #%Try to exclude emission. anything above 2sigma over the median flux should be masked bd = np.where((flux < med_err)) #| (flux > (med_flux+3*med_err))) nbd = len(bd[0]) if (nbd > 0): mask[bd] = 0 mask = np.array(mask) qq = np.where(mask == 1) #Here I'm cutting out any chip gap or any other absorption feature. These are permanently excluded from the fit flux_new = flux[qq] wave_new = wave[qq] weights = np.array(w[qq]) # Fit the data using Legedre Polynomials g_init = models.Legendre1D(order) # initialize fitters fit = fitting.LevMarLSQFitter() with warnings.catch_warnings(): # Ignore model linearity warning from the fitter warnings.simplefilter('ignore') new_fit = fitting.FittingWithOutlierRemoval(fit, sigma_clip, niter=maxiter, sigma=3.0) filtered_fit, filtered_data = new_fit(g_init, wave_new, flux_new) #,weights=weights) '''' while (index < maxiter): print("Index="+np.str(index)) index=index+1 oldmask=mask # Fit the data using Legedre Polynomials g_init = models.Legendre1D(order) # initialize fitters fit = fitting.LevMarLSQFitter() new_fit = fitting.FittingWithOutlierRemoval(fit, sigma_clip,niter=maxiter, sigma=3.0) filtered_data, new_fit = new_fit(g_init, wave_new,flux_new,weights=weights) #fit_g = fitting.LevMarLSQFitter() #new_fit = fit_g(g_init, wave_new,flux_new) # Learn how to use the weights.... cont=new_fit(wave_new) resid=flux_new/np.double(cont) #mederr=np.sqrt(scipy.stats.moment(resid,2)) #;;Use the median error to set "sigma" for the clipping. If you median is skewed, i think you're pretty much hosed mederr=np.std(resid) # Do some sigma clipping here median_val=np.median(resid) kappa=3.; # 3 sigma clipping bd = np.where( (resid > (median_val+kappa*mederr)) | (resid < (median_val - mederr*kappa))) gd= np.where( (resid <= (median_val+kappa*mederr)) & (resid >= (median_val - mederr*kappa))) print np.size(gd) print np.size(bd) if (np.size(gd) > 0): mask[gd] = 1 # Allowing previously rejected points to reenter if (np.size(bd) > 0): mask[bd] = 0 if (index >0): diff=oldmask-mask qq = np.where(diff != 0.) if (np.size(qq)==0): print index print "No more Clipping" index=maxiter # ;;mask and oldmask are identical if all elements match # Updating everything qq=np.where(mask==1) flux_new=flux[qq] wave_new=wave[qq] weights=w[qq] ''' #pdb.set_trace() fit_final = filtered_fit(wave) resid_final = flux / fit_final fit_error = np.std( resid_final) #np.sqrt(scipy.stats.moment(resid_final,2)) return fit_final, resid_final, fit_error
# Get aperture lines linecat = None for ap in gdap: linecat1 = getaplines(idlines,apinfo,ap,diag=diag) print(ap,len(linecat1)) if linecat is None: linecat = linecat1 else: linecat = np.hstack((linecat,linecat1)) nlinecat = len(linecat) # Fit 4th or 5th order polynomial plus a separate offset for each aperture # 1) Initial polynomial fit to all lines in all fibers gline, = np.where(linecat['match']==1) func_init = models.Legendre1D(degree=order,domain=[0,npix]) fitter = fitting.LinearLSQFitter() func1 = fitter(func_init,linecat['center'][gline],linecat['wave'][gline]) dwave1 = func1(linecat['center'][gline]) - linecat['wave'][gline] #coef1 = dln.poly_fit(linecat['center'][gline],linecat['wave'][gline],4) #dwave1 = dln.poly(linecat['center'][gline],coef1) - linecat['wave'][gline] sigwave1 = dln.mad(dwave1) print('Initial fit coefficients ',func1.parameters) #print('Initial fit coefficients ',coef1) print('Initial sigma = %f6.2' % sigwave1) #xx = linecat['center'][gline] #yy = linecat['wave'][gline] #si = np.argsort(xx) #spl = UnivariateSpline(xx[si],yy[si],k=3,s=10) #dwave1 = spl(xx) - linecat['wave'][gline]