def get_oversampled_psf(self): gamma = self.gamma(size=1)[0] alpha = self.alpha print(self.x0,self.y0,gamma,alpha) m2d = models.Moffat2D(x_0=self.x0,y_0=self.y0,gamma=gamma,alpha=alpha) psf = m2d(self.x,self.y) return gamma,alpha,psf
def fit_moffat2d_outlier_removal(x, y, z, sigma=3.0, niter=50, guess=None, bounds=None): """Moffat2D parameters: amplitude, x_mean,y_mean,gamma,alpha""" gg_init = models.Moffat2D() if guess is not None: for ip, p in enumerate(gg_init.param_names): getattr(gg_init, p).value = guess[ip] if bounds is not None: for ip, p in enumerate(gg_init.param_names): getattr(gg_init, p).min = bounds[0][ip] getattr(gg_init, p).max = bounds[1][ip] with warnings.catch_warnings(): # Ignore model linearity warning from the fitter warnings.simplefilter('ignore') fit = fitting.LevMarLSQFitter() or_fit = fitting.FittingWithOutlierRemoval(fit, sigma_clip, niter=niter, sigma=sigma) # get fitted model and filtered data filtered_data, or_fitted_model = or_fit(gg_init, x, y, z) if parameters.VERBOSE: print or_fitted_model return or_fitted_model
def fwhm(self,size=1): gammas = self.gamma(size=size) fwhm = [] for g in gammas: m2d = models.Moffat2D(x_0=self.x0,y_0=self.y0,gamma=g,alpha=self.alpha) fwhm += [m2d.fwhm/self.oversampling] return np.array(fwhm)
def __init__(self, fwhm, tpsf_model, fiber_r, imagesize=20, pix_arcsec=10): self.fwhm = fwhm self.fiber_r = fiber_r self.imagesize = imagesize self.pix_arcsec = pix_arcsec # hard coded in cure self.offsets = 6.0*fiber_r*(arange(0, 100)/100.0) if tpsf_model == 'gaussian': sigma = fwhm/2.35 self.psf_model = models.Gaussian2D(x_stddev=sigma, y_stddev=sigma) elif tpsf_model == 'moffat': alpha = 2.5 # hard coded in Cure gamma = fwhm/2.0/sqrt(power(2.0, (1.0/alpha)) - 1.0) self.psf_model = models.Moffat2D(alpha=alpha, gamma=gamma) # self.psf_model = moffat_generator(0.0, 0.0, fwhm) else: # model not supported return self.initialise_modelfrac()
def test_moffat_fwhm(gamma): ans = 34.641016151377542 kwargs = {'gamma': gamma, 'alpha': 0.5} m1 = models.Moffat1D(**kwargs) m2 = models.Moffat2D(**kwargs) assert_allclose([m1.fwhm, m2.fwhm], ans) assert_array_less(0, [m1.fwhm, m2.fwhm])
def get_oversampled_psf(self, verbose=True): gamma = self.gamma(size=1)[0] alpha = self.alpha if verbose: print("PSF x0,y0,gamma,alpha = ",self.x0,self.y0,gamma,alpha) m2d = models.Moffat2D(x_0=self.x0,y_0=self.y0,gamma=gamma,alpha=alpha) psf = m2d(self.x,self.y) return gamma,alpha,psf
def moffat(xcen, ycen, amp, wid, power, fixed=False): if fixed: return models.Moffat2D(amplitude=amp, x_0=xcen, y_0=ycen, gamma=wid, alpha=power, fixed={ "gamma": True, "alpha": True }) else: return models.Moffat2D(amplitude=amp, x_0=xcen, y_0=ycen, gamma=wid, alpha=power)
def __init__(self, gamma, alpha, **kwargs): # Compute amplitude, from # https://en.wikipedia.org/wiki/Moffat_distribution amplitude = (alpha - 1.0) / (np.pi * gamma * gamma) self._model = models.Moffat2D(amplitude, 0, 0, gamma, alpha) self._default_size = _round_up_to_odd_integer(4.0 * self._model.fwhm) super().__init__(**kwargs) self.normalize()
def fit_star(psf_data, type_fit): ''' Fits Gaussian or Moffat model to star. ''' # Fit the data using astropy.modeling if (type_fit == 'Gaussian'): p_init = models.Gaussian2D(amplitude=np.nanmax(psf_data), x_mean=0.5 * psf_data.shape[0], y_mean=0.5 * psf_data.shape[1]) elif (type_fit == 'Moffat'): p_init = models.Moffat2D(amplitude=np.nanmax(psf_data), x_0=0.5 * psf_data.shape[0], y_0=0.5 * psf_data.shape[1]) fit_p = fitting.LevMarLSQFitter() x, y = np.mgrid[:psf_data.shape[0], :psf_data.shape[1]] p = fit_p(p_init, x, y, psf_data) return (p)
def dofit(self, verbose=True): # Fit the data using astropy.modeling center_x, center_y = find_center(self.z) p_init = models.Moffat2D(x_0=center_x, y_0=center_y) fit_p = fitting.LevMarLSQFitter() with warnings.catch_warnings(): # Ignore model linearity warning from the fitter warnings.simplefilter('ignore') self.model = fit_p(p_init, self.x, self.y, self.z) if verbose: print("Mean Residual:", (self.z - self.model(self.x, self.y)).mean()) self.params = Table( data=[self.model.param_names, self.model.parameters], names=["param_names", "param_vals"]) self.gamma = self.model.parameters[3] self.alpha = self.model.parameters[4] self.fwhm = self.model.fwhm
def measure_fwhm(array, displ): """Fit a Gaussian2D model to a PSF and return the FWHM Parameters ---------- array : numpy.ndarray Array containing PSF Returns ------- x_fwhm : float FWHM in x direction in units of pixels y_fwhm : float FWHM in y direction in units of pixels """ yp, xp = array.shape y, x, = np.mgrid[:yp, :xp] p_init = models.Moffat2D(amplitude=np.max(array), x_0=xp / 2, y_0=yp / 2) fit_p = fitting.LevMarLSQFitter() # print(fit_p.fit_info) fitted_psf = fit_p(p_init, x, y, array, maxiter=50) # print(fit_p.fit_info) if displ == True: plt.figure(figsize=(8, 2.5)) plt.subplot(1, 3, 1) plt.imshow(array, origin='lower', interpolation='nearest') plt.title("Data") plt.subplot(1, 3, 2) plt.imshow(fitted_psf(x, y), origin='lower', interpolation='nearest') plt.title("Model") plt.subplot(1, 3, 3) plt.imshow(array - fitted_psf(x, y), origin='lower', interpolation='nearest') plt.title("Residual") plt.show() return fitted_psf.fwhm
def _initialize_model(self): """Initialize a model with first guesses for the parameters. The user can select between several astropy models, e.g., 'Gaussian2D', 'Moffat2D'. We will use the data to get the first estimates of the parameters of each model. Finally, a Constant2D model is added to account for the background or sky level around the star. """ max_value = self.data.max() if self.model_type == self._GAUSSIAN2D: model = models.Gaussian2D(x_mean=self.x, y_mean=self.y, x_stddev=1, y_stddev=1) model.amplitude = max_value # Establish reasonable bounds for the fitted parameters model.x_stddev.bounds = (0, self._box / 4) model.y_stddev.bounds = (0, self._box / 4) model.x_mean.bounds = (self.x - 5, self.x + 5) model.y_mean.bounds = (self.y - 5, self.y + 5) elif self.model_type == self._MOFFAT2D: model = models.Moffat2D() model.x_0 = self.x model.y_0 = self.y model.gamma = 2 model.alpha = 2 model.amplitude = max_value # Establish reasonable bounds for the fitted parameters model.alpha.bounds = (1, 6) model.gamma.bounds = (0, self._box / 4) model.x_0.bounds = (self.x - 5, self.x + 5) model.y_0.bounds = (self.y - 5, self.y + 5) model += models.Const2D(self.fit_sky()) model.amplitude_1.fixed = True return model
astmodels.Const2D(amplitude=5.), astmodels.Disk2D(amplitude=10., x_0=0.5, y_0=1.5, R_0=5.), astmodels.Ellipse2D(amplitude=10., x_0=0.5, y_0=1.5, a=2., b=4., theta=0.1), astmodels.Exponential1D(amplitude=10., tau=3.5), astmodels.Gaussian1D(amplitude=10., mean=5., stddev=3.), astmodels.Gaussian2D(amplitude=10., x_mean=5., y_mean=5., x_stddev=3., y_stddev=3.), astmodels.KingProjectedAnalytic1D(amplitude=10., r_core=5., r_tide=2.), astmodels.Logarithmic1D(amplitude=10., tau=3.5), astmodels.Lorentz1D(amplitude=10., x_0=0.5, fwhm=2.5), astmodels.Moffat1D(amplitude=10., x_0=0.5, gamma=1.2, alpha=2.5), astmodels.Moffat2D(amplitude=10., x_0=0.5, y_0=1.5, gamma=1.2, alpha=2.5), astmodels.Planar2D(slope_x=0.5, slope_y=1.2, intercept=2.5), astmodels.RedshiftScaleFactor(z=2.5), astmodels.RickerWavelet1D(amplitude=10., x_0=0.5, sigma=1.2), astmodels.RickerWavelet2D(amplitude=10., x_0=0.5, y_0=1.5, sigma=1.2), astmodels.Ring2D(amplitude=10., x_0=0.5, y_0=1.5, r_in=5., width=10.), astmodels.Sersic1D(amplitude=10., r_eff=1., n=4.), astmodels.Sersic2D(amplitude=10., r_eff=1., n=4., x_0=0.5, y_0=1.5, ellip=0.0, theta=0.0), astmodels.Sine1D(amplitude=10., frequency=0.5, phase=1.), astmodels.Cosine1D(amplitude=10., frequency=0.5, phase=1.),
def img_subtract_bright_star(img, star, x_col='x_pix', y_col='y_pix', gamma=5.0, alpha=6.0, sig=None, x_buffer=4, y_buffer=4, img_maxsize=300): """Subtract a bright star from image using a Moffat model.""" # Use the SLSQP fitter fitter_use = fitting.SLSQPLSQFitter() # Image dimension img_h, img_w = img.shape # Only fit the stars on the image if ((0 + x_buffer < int(star[x_col]) < img_w - x_buffer) and (0 + y_buffer < int(star[y_col]) < img_h - y_buffer)): # Get the center of the star x_cen, y_cen = int(star[x_col]), int(star[y_col]) # If the image is too big, cut a part of it if (img_h >= img_maxsize) or (img_w >= img_maxsize): x_0 = int(x_cen - img_maxsize / 2.0) if (x_cen - img_maxsize / 2.0) > 0 else 0 x_1 = int(x_cen + img_maxsize / 2.0) if (x_cen + img_maxsize / 2.0) < img_w else (img_w - 1) y_0 = int(y_cen - img_maxsize / 2.0) if (y_cen - img_maxsize / 2.0) > 0 else 0 y_1 = int(y_cen + img_maxsize / 2.0) if (y_cen + img_maxsize / 2.0) < img_h else (img_h - 1) x_cen, y_cen = (x_cen - x_0), (y_cen - y_0) else: x_0, x_1 = 0, img_w + 1 y_0, y_1 = 0, img_h + 1 # Determine the weights for the fitting img_use = copy.deepcopy(img[y_0:y_1, x_0:x_1]) weights = (1.0 / sig[y_0:y_1, x_0:x_1]) if (sig is not None) else None # X, Y grids y_size, x_size = img_use.shape y_arr, x_arr = np.mgrid[:y_size, :x_size] # Initial the Moffat model p_init = models.Moffat2D(x_0=x_cen, y_0=y_cen, amplitude=(img_use[int(x_cen), int(y_cen)]), gamma=gamma, alpha=alpha, bounds={ 'x_0': [x_cen - x_buffer, x_cen + x_buffer], 'y_0': [y_cen - y_buffer, y_cen + y_buffer] }) try: with np.errstate(all='ignore'): best_fit = fitter_use(p_init, x_arr, y_arr, img_use, weights=weights, verblevel=0) img_new = copy.deepcopy(img) img_new[y_0:y_1, x_0:x_1] -= best_fit(x_arr, y_arr) return img_new except Exception: warnings.warn('# Star fitting failed!') return img else: return img
def fwhmrGridStatistic(catfile, size, gridNum=40): tdata = np.loadtxt(catfile) maxEllipticity = 0.1 mag = tdata[:, 38] elpct = tdata[:, 15] fwhm = tdata[:, 18] mag1 = sigma_clip(mag, sigma=2.5, iters=3) minMag = np.min(mag1) maxMag = np.max(mag1) medianFwhm = np.median(fwhm) targetFwhmMax = medianFwhm targetMag = maxMag - 6 targetMagMin = targetMag + 1 targetMagMax = targetMag + 2 tdata = tdata[(mag > targetMagMin) & (mag < targetMagMax) & (elpct < maxEllipticity) & (fwhm < targetFwhmMax)] print(tdata.shape) imgData = fits.getdata(r"E:\fwhm\data1\oi.fit") print(imgData.shape) tRadius = 5 for i in range(tdata.shape[1]): if i < 10: trow = tdata[i] print("%.2f, %.2f, %.2f, %.2f, %.2f, %.2f" % (trow[3], trow[4], trow[38], trow[18], trow[15], trow[17])) x = int(round(trow[3])) y = int(round(trow[4])) fwhm = trow[18] bkg = trow[17] minX = x - tRadius maxX = x + tRadius + 1 minY = y - tRadius maxY = y + tRadius + 1 subImg = imgData[minY:maxY, minX:maxX] subImg = subImg - bkg x = np.arange(subImg.shape[1]) y = np.arange(subImg.shape[0]) X, Y = np.meshgrid(x, y) Z = subImg fig = plt.figure() fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax.scatter(X, Y, Z) plt.show() ''' ''' #https://github.com/astropy/astropy/issues/4521 from astropy.modeling import models, fitting p_init = models.Moffat2D(amplitude=Z[5, 5], x_0=5, y_0=5, gamma=fwhm, alpha=3.0) fitter = fitting.LevMarLSQFitter() p_fit = fitter(p_init, X, Y, Z) Z1 = p_fit(X, Y) print(p_fit) print(p_fit.fwhm) tfwhm = FWHM_moffat(p_fit.gamma, p_fit.alpha) print(tfwhm) fig = plt.figure() fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax.scatter(X, Y, Z1) plt.show() break
def fit_moon(img, x, y): """Fit a Moffat function to the moon in a given image. Parameters --------- img : image.AllSkyImage The image. x : float The x coordinate of the moon"s center. y : float The y coordinate of the moon"s center. Returns ------- float The Full Width at Half Maximum of the Moffat function. """ # This block of code runs straight vertical from the center of the moon # It gives a predicted rough radius of the moon, it starts counting at the # first white pixel it encounters (the center may be black) # and stops at the last white pixel. White here defined as > 250 greyscale. yfloor = math.floor(y) count = False size = 0 xfloor = math.floor(x) start = xfloor # The only reason we have this if block is to ensure we don"t run for # moon radii greater than 35 in this case. for i in range(0, 35): start += 1 # Breaks if it reaches the edge of the image. if start == img.data.shape[1]: break if not count and img.data[yfloor, start] >= 250: count = True elif count and img.data[yfloor, start] >= 250: size += 1 elif count and img.data[yfloor, start] < 250: break # Add some buffer pixels in case the center is black and the edges of the # moon are fuzzed and then convert radius to diameter. size = (size + 10) * 2 # Makes sure the lower/upper slices don"t out of bounds error. lowerx = xfloor - size if (xfloor - size > 0) else 0 lowery = yfloor - size if (yfloor - size > 0) else 0 upperx = xfloor + size if (xfloor + size < 511) else 511 uppery = yfloor + size if (yfloor + size < 511) else 511 # Size of the moon enclosing square. deltax = (upperx - lowerx) deltay = (uppery - lowery) # Creates two arrays, with the array values being the x or y coordinate of # that location in the array. y, x = np.mgrid[0:deltay, 0:deltax] # Slices out the moon square and finds center coords. z = img.data[lowery:uppery, lowerx:upperx] midy = deltay / 2 midx = deltax / 2 # Moffat fit, centered in square, stdev of 20 as a start. stddev = 20 model_init = models.Moffat2D(amplitude=200, x_0=midx, y_0=midy, gamma=stddev) fit = fitting.LevMarLSQFitter() with warnings.catch_warnings(): # Ignore model linearity warning from the fitter warnings.simplefilter("ignore") model = fit(model_init, x, y, z) # /2 is average FWHM but FWHM = diameter, so divide by two again. #fwhm = (model.x_fwhm + model.y_fwhm) / 4 fwhm = model.fwhm / 2 return fwhm
def simulate_image(psffits=None, theofits=None, obsfits=None, distance=None, extfits=None, wlen=None, pfov=None, filt=None, psfext=0, obsext=0, bgstd=0, bgmed=0, silent=False, posang=0, foi_as=4, outname=None, manscale=None, fnucdiam_as=0.45, suffix=None, fitsize=None, outfolder='.', writesimfits=False, writetheofits=False, writeallfits=False, writefitplot=False, debug=False, returncmod=True, fitpsf=False, saveplot=False, meastime=False): """ Simulate a (VISIR) imaging observaion given a real image, a PSF reference image and a model image for a provided object distance and position angle Current constraints: - The routine assumes that the images are squares (x = y) """ if meastime: tstart = time.time() psfim = fits.getdata(psffits, ext=psfext) psfhead = fits.getheader(psffits) # print(obsfits, obsext) # ==== 1. Load Observational and PSF data ==== if obsfits is not None: obsim = fits.getdata(obsfits, ext=obsext) obshead = fits.getheader(obsfits) if wlen is None: wlen = float(obshead['WAVELEN']) if pfov is None: pfov = obshead['PFOV'] if filt is None: filt = obshead['Filter'] else: if wlen is None: wlen = float(psfhead['WAVELEN']) if pfov is None: pfov = psfhead['PFOV'] if filt is None: filt = psfhead['Filter'] wlenstr = "{:.1f}".format(wlen) diststr = "{:.1f}".format(distance) pastr = "{:.0f}".format(posang) # --- optinal cropping of the PSF image if fitsize is not None: psfim = _crop_image(psfim, box=fitsize, cenpos=_get_pointsource(psfim)[0][2:4]) psfsize = np.array(np.shape(psfim)) psfsize_as = psfsize[0] * pfov if not silent: print("Obs. wavelength [um]: " + wlenstr) # ==== 2. Prepare theoretical image for convolution ==== theohdu = fits.open(theofits) theohead = theohdu[0].header # --- check if provided file is the full cube if 'NAXIS3' in theohead: # find the right frame to be extracted mind, mwlen = find_wlen_match(theofits, wlen) theoim = theohdu[0].data[mind] if not silent: print(" - Model wavelength [um] | frame no: " + str(mwlen) + " | " + str(mind)) else: theoim = theohdu[0].data theohdu.close() if meastime: print(" - All files read. Elapsed time: ", time.time() - tstart) if debug is True: print("THEOIM: ") plt.imshow(theoim, origin='bottom', norm=LogNorm(), interpolation='nearest') plt.show() # --- if a manual scaling (fudge) factor was provided, apply to the model if manscale: theoim = theoim * manscale # --- rotate the model image (this changes its extent and thus has to be # done first) # unrot = np.copy(theoim) if np.abs(posang - 0) > 0.01: theoim = ndimage.interpolation.rotate(theoim, 180 - posang, order=0) if meastime: print(" - Model rotated. Elapsed time: ", time.time() - tstart) if debug is True: print("THEOIM_ROT: ") plt.imshow(theoim, origin='bottom', norm=LogNorm(), interpolation='nearest') plt.show() # --- size units of the theoretical image theopixsize_pc = theohead['CDELT1'] theosize = np.array(np.shape(theoim)) theosize_pc = theopixsize_pc * theosize[0] # pc theosize_as = 2 * np.arctan( theosize_pc / distance / 2.0e6) * 180 / np.pi * 3600 theopixsize_as = (2 * np.arctan(theopixsize_pc / distance / 2.e6) * 180 / np.pi * 3600) # plt.imshow(unrot, origin='bottom', norm=LogNorm()) # plt.imshow(theoim, origin='bottom', norm=LogNorm()) # plt.imshow(theoim, origin='bottom') # theopfov = angsize/theosize[0] # normalize image to flux = 1 # theoim = theoim/np.sum(theoim) # theoim = theoim/np.max(theoim) # --- convert to the right flux units # surface brightness unit in cube is W/m^2/arcsec2 # -> convert to flux density in mJy # print(np.sum(theoim)) freq = 2.99793e8 / (1e-6 * wlen) # print(freq) theoim = theoim * 1.0e29 / freq * theopixsize_as**2 theototflux = np.sum(theoim) # --- resample to the same pixel size as the observation # print( "- Resampling the model image to instrument pixel size...") # --- the ndimage.zoom sometimes creates weird artifacts and thus might not # be a good choice. Instead, use the self-made routine # theoim_resres = ndimage.zoom(theoim_res, theopixsize_as/pfov, order=0) # --- do the rasmpling before scaling it to the size of the observation to # save computing time theoim_res, sizerat = _increase_pixelsize(theoim, oldpfov=theopixsize_as, newpfov=pfov, meastime=False) if meastime: print(" - Pixelsize increased. Elapsed time: ", time.time() - tstart) if debug is True: print("sizerat: ", sizerat) print("THEOIM_RESRES: ") plt.imshow(theoim_res, origin='bottom', norm=LogNorm(), interpolation='nearest') plt.show() # --- if the PSF frame size is larger than the model extend the model frame framerat = psfsize_as / theosize_as if debug is True: print("psfsize_as: ", psfsize_as) print("theosize_as: ", theosize_as) print("theopixsize_as: ", theopixsize_as) print("theosize_pc: ", theosize_pc) print("theosize: ", theosize) print("framerat: ", framerat) #print("newsize: ", newsize) if framerat > 1.0: theoim_resres = _extend_image(theoim_res, fac=framerat) if meastime: print(" - Frame extended. Elapsed time: ", time.time() - tstart) if debug is True: print("thesize_ext: ", np.shape(theoim_resres)) print("THEOIM_RESRES: ") plt.imshow(theoim_resres, origin='bottom', norm=LogNorm(), interpolation='nearest') plt.show() # plt.imshow(theoim_res, origin='bottom') else: theoim_resres = np.copy(theoim_res) theosize_new = np.array(np.shape(theoim_resres)) if debug is True: print("theosize_new: ", theosize_new) print("new theosize_as: ", theosize_new * pfov) # print(np.sum(theoim_resres)) # --- after resampling we need to re-establish the right flux levels theoim_resres = theoim_resres / np.sum(theoim_resres) * theototflux # plt.imshow(theoim_resres, origin='bottom', norm=LogNorm()) # plt.imshow(theoim_resres, origin='bottom') # ==== 4. Optionally, apply a foreground extinction map if provided ==== if extfits: exthdu = fits.open(extfits) exthead = exthdu[0].header extpfov = exthead["PFOV"] # check if provided file is the full cube if 'NAXIS3' in exthead: # find the right frame to be extracted if not mind: mind, mwlen = find_wlen_match(theofits, wlen) extim = exthdu[0].data[mind] else: extim = exthdu[0].data # plt.imshow(extim,origin='bottom') # plt.show() exthdu.close() # --- Do we have to resample the extinction map if np.abs(extpfov - pfov) > 0.001: # print("Resampling extinction map... ") extim = ndimage.zoom(extim, 1.0 * pfov / extpfov, order=0) # --- match the size of the extinction map to the one of the model # image extsize = np.array(np.shape(extim)) if theosize_new[0] > extsize[0]: print(" - ERROR: provided extinction map is too small to cover \ the imaged region. Abort...") return () if extsize[0] > theosize_new[0]: # print("Cuttting exctinction map...") extim = _crop_image(extim, box=theosize_new) # --- finally apply the extinction map theoim_resres = theoim_resres * extim # print('Maximum extinction: ',np.min(extim)) # ==== 5. Obtain the Airy function from the calibrator ==== if debug is True: print("PSF_IM: ") plt.imshow(psfim, origin='bottom', norm=LogNorm(), interpolation='nearest') plt.show() # --- set up the fitting of the STD star image # --- assume that the maximum of the (cropped) image is the STD star if fitpsf is True: psfmax = np.max(psfim) psfmaxpos = np.unravel_index(np.argmax(psfim), psfsize) # print(im_bg, im_max, im_size, im_maxpos) # --- Use an Airy plus a Moffat + constant as PSF model c_init = models.Const2D(amplitude=np.median(psfim)) oa_init = obsc_AiryDisk2D(amplitude=psfmax, x_0=psfmaxpos[1], y_0=psfmaxpos[0], radius=5, obsrat=0.15) m_init = models.Moffat2D(amplitude=psfmax, x_0=psfmaxpos[1], y_0=psfmaxpos[0], gamma=5, alpha=1) a_plus_m = oa_init + m_init + c_init # print(psfmax, psfmaxpos) # --- Selection of fitting method fit_meth = fitting.LevMarLSQFitter() # --- Do the Fitting: y, x = np.mgrid[:psfsize[0], :psfsize[1]] am_fit = fit_meth(a_plus_m, x, y, psfim) # print(am_fit.radius_0.value, am_fit.obsrat_0.value) # then refine the fit by subtracting another Moffat # a_plus_m_minus_m = am_fit - m_init # amm_fit = fit_meth(a_plus_m_minus_m, x, y, psfim) # z_amm = amm_fit(x, y) # res = psfim - z_amm #print(amm_fit.radius_0.value, amm_fit.obsrat_0.value) if meastime: print(" - PSf fit. Elapsed time: ", time.time() - tstart) if debug is True: print("PSF_FIT: ") plt.imshow(am_fit(x, y), origin='bottom', norm=LogNorm(), interpolation='nearest') plt.show() # --- Genarate the PSF image with the fitted model # --- check whether the model image is on sky larger than the PSF image # and if this is the case increase the grid and adjust the positions # of the fits size_diff = theosize_new[0] - psfsize[0] if size_diff > 0: y, x = np.mgrid[:theosize_new[0], :theosize_new[1]] am_fit.x_0_0 = am_fit.x_0_0 + 0.5 * size_diff am_fit.y_0_0 = am_fit.y_0_0 + 0.5 * size_diff am_fit.x_0_1 = am_fit.x_0_1 + 0.5 * size_diff am_fit.y_0_1 = am_fit.y_0_1 + 0.5 * size_diff # --- create the final PSF image by subtracting the constant terms # and normalise by its area psf = am_fit(x, y) - am_fit.amplitude_2.value psf = psf / np.sum(psf) if debug is True: print("PSF_FINAL: ") plt.imshow(psf, origin='bottom', norm=LogNorm(), interpolation='nearest') plt.show() # plt.imshow(psf, origin='bottom', norm=LogNorm()) # plt.show() if writeallfits is True: fout = psffits.replace('.fits', '_fit.fits') if 'OBS NAME' in psfhead: psfhead.remove('OBS NAME') fits.writeto(fout, psf, psfhead, overwrite=True) # --- optinally, write the a plot documenting the quality of the PSF fit if writefitplot: fitplotfname = psffits.split('/')[-1] fitplotfname = fitplotfname.replace('.fits', '_fitplot.pdf') fitplotfname = outfolder + '/' + fitplotfname maxrad = int(0.5 * np.sqrt(0.5) * np.min(psfsize)) _make_fit_plots(psfim, am_fit(x, y), fitplotfname, am_fit.x_0_0, am_fit.y_0_0, maxrad, inv=True, cmap='gist_heat', labelcolor='black') # --- alternatively the PSF can also be direclty provided so that no fit is # necessary else: # normalise the provided psf psf = psfim - np.nanmin(psfim) psf = psf / np.nansum(psf) psf[np.argwhere(np.isnan(psf))] = 0 # ==== 6. Convolution of the model image with the PSF ==== simim = scipy.signal.convolve2d(theoim_resres, psf, mode='same') # print(np.sum(simim)) if meastime: print(" - Model convolved. Elapsed time: ", time.time() - tstart) if debug is True: print("SIMIM:") plt.imshow(simim, origin='bottom', norm=LogNorm(), interpolation='nearest') plt.show() # ==== 7. Flux measurements and application of noise ==== # --- Flux measurement on the model image before applying noise ftotmod = int(np.nansum(simim)) apos = 0.5 * np.array(theosize_new) nucrad_px = 0.5 * fnucdiam_as / pfov from aper import aper (mag, magerr, flux, fluxerr, sky, skyerr, badflag, outstr) = aper(simim, apos[1], apos[0], apr=nucrad_px, exact=True, setskyval=0) fnucmod = int(flux) if not silent: print(' - Model total | nuclear flux [mJy]: ' + str(ftotmod) + ' | ' + str(fnucmod)) plotsize_px = int(1.0 * foi_as / pfov) # --- Measure the background noise from the real observation if obsfits is not None: bg = np.copy(obsim) # bgsize = np.shape(bg) params, _, _ = _get_pointsource(obsim) xpos = params[2] ypos = params[3] bg[int(ypos - 0.5 * plotsize_px):int(ypos + 0.5 * plotsize_px), int(xpos - 0.5 * plotsize_px):int(xpos + 0.5 * plotsize_px)] = 0 if bgstd == 0: bgstd = np.std(bg[bg != 0]) if bgmed == 0: bgmed = np.median(bg[bg != 0]) # plt.imshow(bg, origin='bottom', norm=LogNorm(), cmap='gist_heat') # --- crop the observational image to the requested plot size cim = _crop_image(obsim, box=plotsize_px, cenpos=_get_pointsource(obsim)[0][2:4]) newsize = np.shape(cim) # --- make flux measurements on the nucleus and total ftotobs = int(np.sum(cim - bgmed)) apos = 0.5 * np.array(newsize) (mag, magerr, flux, fluxerr, sky, skyerr, badflag, outstr) = aper(cim, apos[1], apos[0], apr=nucrad_px, exact=True, setskyval=0) fnucobs = int(flux) if not silent: print(' - Observed total | nuclear flux [mJy]: ' + str(ftotobs) + ' | ' + str(fnucobs)) else: cim = None if meastime: print(" - Fluxes measured. Elapsed time: ", time.time() - tstart) # pdb.set_trace() # --- crop the simulated image to the requested plot size params, _, _ = _get_pointsource(simim) csimim = _crop_image(simim, box=plotsize_px, cenpos=_get_pointsource(simim)[0][2:4]) if debug is True: plt.imshow(simim, origin='bottom', cmap='gist_heat', norm=LogNorm()) plt.title('simim') plt.show() print(plotsize_px, np.shape(csimim)) plt.imshow(csimim, origin='bottom', cmap='gist_heat', norm=LogNorm()) plt.title('csimim') plt.show() if meastime: print(" - Obs cropped. Elapsed time: ", time.time() - tstart) # --- generate an artifical noise frame with same properties as in real # image if bgstd > 0: artbg = np.random.normal(scale=bgstd, size=(plotsize_px, plotsize_px)) + bgmed # artbg = 0 # --- apply the artificial background # csimim = csimim/np.max(csimim)*(np.max(cim)-bgmed)+bgmed+artbg csimim = csimim + artbg # --- crop the PSF image to the requested plot size cpsf = _crop_image(psf, box=plotsize_px, cenpos=_get_pointsource(psf)[0][2:4]) # print(bgmed, bgstd, np.std(artbg), np.median(cim), np.median(csimim), # np.std(cim), np.std(csimim), np.max(cim), np.min(cim), np.max(csimim), # np.min(csimim)) if meastime: print(" - PSF cropped. Elapsed time: ", time.time() - tstart) theopfov = theopixsize_as theobox = int(np.round(plotsize_px * pfov / theopfov)) if returncmod: # --- crop the model imae to the requested plot size if framerat > 1.0: theoim_res_0 = _extend_image(theoim, fac=framerat) else: theoim_res_0 = theoim cmod = _crop_image(theoim_res_0, box=theobox, exact=False) # plt.imshow(cmod, origin='bottom', norm=LogNorm(), cmap='gist_heat') if meastime: print(" - Theo cropped. Elapsed time: ", time.time() - tstart) else: cmod = None # --- write out the simulated image as a fits file if not outname: theofitsfile = theofits.split("/")[-1] out_str = theofitsfile.replace("_total.fits", "") out_str = (out_str + "_pa" + pastr + "_dist" + diststr + "_wlen" + wlenstr) if suffix: out_str = out_str + "_" + suffix else: out_str = outname out_str = outfolder + '/' + out_str if writeallfits or writetheofits: fout = out_str + '_mod.fits' theohead["Filter"] = filt theohead["WAVELEN"] = wlen theohead["PFOV"] = theopixsize_as fits.writeto(fout, cmod, theohead, overwrite=True) if saveplot: _simple_image_plot(cmod, fout.replace(".fits", ".png"), log=True) if writeallfits or writesimfits: fout = out_str + '_sim.fits' theohead["PFOV"] = pfov theohead["BUNIT"] = 'mJy' ts = np.shape(simim) theohead["CRPIX1"] = 0.5 * ts[1] theohead["CDELT1"] = pfov theohead["CTYPE1"] = 'arcsec' theohead["CRPIX2"] = 0.5 * ts[0] theohead["CDELT2"] = pfov theohead["CTYPE2"] = 'arcsec' fits.writeto(fout, csimim, theohead, overwrite=True) if saveplot: _simple_image_plot(csimim, fout.replace(".fits", ".png"), log=True) if meastime: print(" - All finished: ", time.time() - tstart) return (cpsf, cmod, cim, csimim, wlen, pfov, theopfov)
def extract_photometry(fn): f = fits.open(fn) d1 = f[1].data # Background subtraction : https://photutils.readthedocs.io/en/stable/background.html sigma_clip = SigmaClip(sigma=sclip) bkg_estimator = MedianBackground() bkg = Background2D(d1, (bg_wsize, bg_wsize), sigma_clip=sigma_clip, bkg_estimator=bkg_estimator) d2 = d1 - bkg.background # Building local positions, fitting Moffat2D models positions = [] invalid = [] for i, pt in enumerate(src_list): # Extracting the window for fitting x, y = pt x_min = x - mod_fit_size x_max = x + mod_fit_size y_min = y - mod_fit_size y_max = y + mod_fit_size window = d2[y_min:y_max + 1, x_min:x_max + 1] # Initial guess z0 = d2[y, x] m_init = models.Moffat2D(z0, x, y) # Fitting, we catch warnings as exceptions in case the fit fails with warnings.catch_warnings(record=True) as w: fit_m = fitting.LevMarLSQFitter() xv, yv = np.meshgrid(range(x_min, x_max + 1), range(y_min, y_max + 1)) p = fit_m(m_init, xv, yv, window) if w and issubclass(w[-1].category, AstropyUserWarning): print( 'Warning : The fit might not have converged for source #{} at position {}' .format(i, pt)) invalid.append(i) px = p.x_0.value py = p.y_0.value pz = p.amplitude.value # Storing info for animation if i == wid: global xvv, yvv, windows xvv = xv yvv = yv ix = int(round(px)) iy = int(round(py)) x_min = ix - mod_fit_size x_max = ix + mod_fit_size y_min = iy - mod_fit_size y_max = iy + mod_fit_size nw = d2[y_min:y_max + 1, x_min:x_max + 1] windows += [nw] # Rendering fit to file if os.path.exists('fits') and plot_fits: fig = plt.figure(figsize=(10, 10)) ax = fig.add_subplot(111, projection='3d') ax.plot_surface(xv, yv, window) ax.scatter(px, py, pz, s=3, color='red') plt.savefig('fits/fit_{}_{}_{}.png'.format(fn, x, y)) plt.close('all') positions += [(p.x_0.value, p.y_0.value)] # Aperture photometry : https://photutils.readthedocs.io/en/stable/aperture.html apertures = CircularAperture(positions, r=aperture_r) annulus_apertures = CircularAnnulus(positions, r_in=sky_in, r_out=sky_out) apers = [apertures, annulus_apertures] phot_table = aperture_photometry(d2, apers) # Mean sky subtraction bkg_mean = phot_table['aperture_sum_1'] / annulus_apertures.area() bkg_sum = bkg_mean * apertures.area() final_sum = phot_table['aperture_sum_0'] - bkg_sum # Calculating zero-point : http://www.stsci.edu/hst/wfpc2/analysis/wfpc2_cookbook.html h0 = f[0].header h1 = f[1].header phot_zpt = h1['PHOTZPT'] phot_flam = h1['PHOTFLAM'] zero_pt = -2.5 * np.log10(phot_flam) + phot_zpt # TODO : Correct from STMAG to Cousins magnitudes = [] for i, flux in enumerate(final_sum): if i in invalid: magnitudes.append(np.nan) else: m = -2.5 * np.log10(flux) + zero_pt magnitudes.append(m) print(fn, h0['EXPEND'], magnitudes) return h0['EXPEND'], magnitudes
def moffats(self, x0, y0, fwhmpix=3., dxymax=5., verbose=True, **kwargs): """ Fits N Moffat profiles to N different locations in the image. This multiple-component fitting might be done, for example, to fit to the multiple lensed quasar images in a gravitational lens system, or to a set of stars in an image The number of components to be fit is set by the number of elements of the x0 (and y0) arrays. """ """ Get the number of components to fit """ if isinstance(x0, int): x0 = float(x0) if isinstance(y0, int): y0 = float(y0) xinit = np.atleast_1d(x0) yinit = np.atleast_1d(y0) if xinit.size != yinit.size: raise ValueError('x0 and y0 are not the same size') nmoffat = xinit.size """ Create the Moffat profiles with initial guess parameters """ x2 = np.zeros(nmoffat) y2 = np.zeros(nmoffat) for i in range(nmoffat): """ First refine the position guess by calling the moments method """ objstats = self.moments(xinit[i], yinit[i], **kwargs) x2[i] = objstats['mux'] y2[i] = objstats['muy'] """ Do a crude sky subtraction to estimate the starting amplitude. Note that the mean_clip attribute will have been set through the call to self.moments """ amp0 = self.data[int(y2[i]), int(x2[i])] - self.mean_clip """ Create a 2d Moffat profile with initial guess values The initial value for what astropy calls alpha and others call beta, i.e., 4.765, comes from Trujillo et al. 2001 The initial value for what astropy calls gamma and others call alpha is related to the FWHM and (alpha/beta) """ alpha0 = 4.765 gamma0 = fwhmpix / (2. * sqrt(2.**(1./alpha0) - 1.)) tmpmod = models.Moffat2D(amplitude=amp0, x_0=x2[i], y_0=y2[i], alpha=alpha0, gamma=gamma0) """ Set bounds for the parameters """ tmpmod.x_0.bounds = (x2[i] - dxymax, x2[i] + dxymax) tmpmod.y_0.bounds = (y2[i] - dxymax, y2[i] + dxymax) tmpmod.amplitude.bounds = (0., None) """ Tie the alpha and gamma parameters together, and add this model to the compound model """ if i==0: mod = tmpmod else: mod += tmpmod mod[i].alpha.tied = tie_alpha mod[i].gamma.tied = tie_gamma """ Fit the model to the data """ fit = fitting.LevMarLSQFitter() outmod = fit(mod, self.x, self.y, self.data) # outmod = fit(mod, self.x, self.y, self.data, weights=1.0/rms) """ Report on fit if requested """ print(' Initial Refined Final') print('------------- ------------- -------------') for i in range(nmoffat): print('%6.2f %6.2f %6.2f %6.2f %6.2f %6.2f' % (xinit[i], yinit[i], x2[i], y2[i], outmod[i].x_0.value, outmod[i].y_0.value)) return outmod
def moffatFit(self, image): self._getInitParameters(image) fit_model = models.Moffat2D(x_0=self.init_guessTable['xcentroid'], y_0=self.init_guessTable['ycentroid'], amplitude=self.init_guessTable['peak']) self._fit = self._fitter(fit_model, self._x, self._y, image)
def create_synth_psf(model='gauss', shape=(9, 9), amplitude=1, x_mean=None, y_mean=None, fwhm=4, theta=0, gamma=None, alpha=1.5, radius=None, msdi=False): """ Creates a synthetic 2d or 3d PSF with a 2d model: Airy disk, Gaussian or Moffat, depending on ``model``. Parameters ---------- model : {'gauss', 'moff', 'airy'}, str optional Model to be used to create the synthetic PSF. shape : tuple of ints, optional Shape of the output 2d array. amplitude : float, optional Value of the amplitude of the 2d distribution. x_mean : float or None, optional Value of the centroid in X of the distributions: the mean of the Gaussian or the location of the maximum of the Moffat or Airy disk models. If None, the centroid is placed at the center of the array. y_mean : float or None, optional Value of the centroid in Y of the distributions: the mean of the Gaussian or the location of the maximum of the Moffat or Airy disk models. If None, the centroid is placed at the center of the array. fwhm : float, tuple of floats, list or np.ndarray, optional FWHM of the model in pixels. For the Gaussian case, it controls the standard deviation of the Gaussian. If a tuple is given, then the Gaussian will be elongated (fwhm in x, fwhm in y). For the Moffat, it is related to the gamma and alpha parameters. For the Airy disk, it is related to the radius (of the first zero) parameter. If ``msdi`` is True then ``fwhm`` must be a list of 1d np.ndarray (for example for SPHERE/IFS this sounds like a reasonable FWHM: np.linspace(4.5,6.7,39)). theta : float, optional Rotation angle in degrees of the Gaussian. gamma : float or None, optional Gamma parameter of core width of the Moffat model. If None, then it is calculated to correspond to the given ``fwhm``. alpha : float, optional Power index of the Moffat model. radius : float or None, optional The radius of the Airy disk (radius of the first zero). If None, then it is calculated to correspond to the given ``fwhm``. msdi : bool, optional Creates a 3d PSF, for emulating an IFS PSF. Returns ------- im : numpy ndarray 2d array with given ``shape`` and containing the synthetic PSF. Notes ----- http://docs.astropy.org/en/stable/api/astropy.modeling.functional_models.Gaussian2D.html http://docs.astropy.org/en/stable/api/astropy.modeling.functional_models.Moffat2D.html http://docs.astropy.org/en/stable/api/astropy.modeling.functional_models.AiryDisk2D.html https://www.gnu.org/software/gnuastro/manual/html_node/PSF.html web.ipac.caltech.edu/staff/fmasci/home/astro_refs/PSFtheory.pdf web.ipac.caltech.edu/staff/fmasci/home/astro_refs/PSFsAndSampling.pdf """ # 2d case if not msdi: sizex, sizey = shape if x_mean is None or y_mean is None: y_mean, x_mean = frame_center(np.zeros((sizey, sizex))) x = np.arange(sizex) y = np.arange(sizey) x, y = np.meshgrid(x, y) if model == 'gauss': if np.isscalar(fwhm): fwhm_y = fwhm fwhm_x = fwhm else: fwhm_x, fwhm_y = fwhm gauss = models.Gaussian2D(amplitude=amplitude, x_mean=x_mean, y_mean=y_mean, x_stddev=fwhm_x * gaussian_fwhm_to_sigma, y_stddev=fwhm_y * gaussian_fwhm_to_sigma, theta=np.deg2rad(theta)) im = gauss(x, y) elif model == 'moff': if gamma is None and fwhm is not None: gamma = fwhm / (2. * np.sqrt(2**(1 / alpha) - 1)) moffat = models.Moffat2D(amplitude=amplitude, x_0=x_mean, y_0=y_mean, gamma=gamma, alpha=alpha) im = moffat(x, y) elif model == 'airy': if radius is None and fwhm is not None: diam_1st_zero = (fwhm * 2.44) / 1.028 radius = diam_1st_zero / 2. airy = models.AiryDisk2D(amplitude=amplitude, x_0=x_mean, y_0=y_mean, radius=radius) im = airy(x, y) return im # 3d case else: if np.isscalar(fwhm): raise ValueError('`Fwhm` must be a 1d vector') cube = [] for fwhm_i in fwhm: cube.append( create_synth_psf(model, shape, amplitude, x_mean, y_mean, fwhm_i, theta, gamma, alpha, radius)) cube = np.array(cube) return cube
def fit_2dmoffat(array, crop=False, cent=None, cropsize=15, fwhm=4, threshold=False, sigfactor=6, full_output=True, debug=True): """ Fitting a 2D Moffat to the 2D distribution of the data. Parameters ---------- array : numpy ndarray Input frame with a single PSF. crop : bool, optional If True a square sub image will be cropped equal to cropsize. cent : tuple of int, optional X,Y integer position of source in the array for extracting the subimage. If None the center of the frame is used for cropping the subframe (the PSF is assumed to be ~ at the center of the frame). cropsize : int, optional Size of the subimage. fwhm : float, optional Initial values for the FWHM of the fitted 2d Moffat, in px. threshold : bool, optional If True the background pixels (estimated using sigma clipped statistics) will be replaced by small random Gaussian noise. sigfactor : int, optional The background pixels will be thresholded before fitting a 2d Moffat to the data using sigma clipped statistics. All values smaller than (MEDIAN + sigfactor*STDDEV) will be replaced by small random Gaussian noise. full_output : bool, optional If False it returns just the centroid, if True also returns the FWHM in X and Y (in pixels), the amplitude and the rotation angle. debug : bool, optional If True, the function prints out parameters of the fit and plots the data, model and residuals. Returns ------- mean_y : float Source centroid y position on input array from fitting. mean_x : float Source centroid x position on input array from fitting. If ``full_output`` is True it returns a Pandas dataframe containing the following columns: 'alpha': Float value. Alpha parameter. 'amplitude' : Float value. Moffat Amplitude. 'centroid_x' : Float value. X coordinate of the centroid. 'centroid_y' : Float value. Y coordinate of the centroid. 'fwhm' : Float value. FHWM [px]. 'gamma' : Float value. Gamma parameter. """ check_array(array, dim=2, msg='array') if crop: if cent is None: ceny, cenx = frame_center(array) else: cenx, ceny = cent imside = array.shape[0] psf_subimage, suby, subx = get_square(array, min(cropsize, imside), ceny, cenx, position=True) else: psf_subimage = array.copy() if threshold: _, clipmed, clipstd = sigma_clipped_stats(psf_subimage, sigma=2) indi = np.where(psf_subimage <= clipmed + sigfactor * clipstd) subimnoise = np.random.randn(psf_subimage.shape[0], psf_subimage.shape[1]) * clipstd psf_subimage[indi] = subimnoise[indi] # Creating the 2D Moffat model init_amplitude = np.ptp(psf_subimage) xcom, ycom = cen_com(psf_subimage) moffat = models.Moffat2D(amplitude=init_amplitude, x_0=xcom, y_0=ycom, gamma=fwhm / 2., alpha=1) # Levenberg-Marquardt algorithm fitter = fitting.LevMarLSQFitter() y, x = np.indices(psf_subimage.shape) fit = fitter(moffat, x, y, psf_subimage) if crop: mean_y = fit.y_0.value + suby mean_x = fit.x_0.value + subx else: mean_y = fit.y_0.value mean_x = fit.x_0.value fwhm = fit.fwhm amplitude = fit.amplitude.value alpha = fit.alpha.value gamma = fit.gamma.value if debug: if threshold: label = ('Subimage thresholded', 'Model', 'Residuals') else: label = ('Subimage', 'Model', 'Residuals') plot_frames((psf_subimage, fit(x, y), psf_subimage - fit(x, y)), grid=True, grid_spacing=1, label=label) print('FWHM =', fwhm) print('centroid y =', mean_y) print('centroid x =', mean_x) print('centroid y subim =', fit.y_0.value) print('centroid x subim =', fit.x_0.value, '\n') print('amplitude =', amplitude) print('alpha =', alpha) print('gamma =', gamma) # compute uncertainties if fitter.fit_info['param_cov'] is not None: perr = np.sqrt(np.diag(fitter.fit_info['param_cov'])) amplitude_err, mean_x_err, mean_y_err, gamma_err, alpha_err = perr fwhm_err = 2 * gamma_err else: amplitude_err, mean_x_err, mean_y_err = None, None, None gamma_err, alpha_err, fwhm_err = None, None, None if full_output: return pd.DataFrame( { 'centroid_y': mean_y, 'centroid_x': mean_x, 'fwhm': fwhm, 'alpha': alpha, 'gamma': gamma, 'amplitude': amplitude, 'centroid_y_err': mean_y_err, 'centroid_x_err': mean_x_err, 'fwhm_err': fwhm_err, 'alpha_err': alpha_err, 'gamma_err': gamma_err, 'amplitude_err': amplitude_err }, index=[0], dtype=np.float64) else: return mean_y, mean_x
def moffat(xcen,ycen,amp,wid,power): return models.Moffat2D(amplitude=amp, x_0=xcen, y_0=ycen, gamma=wid, alpha=power)
def fit_2D_Moffat(box, center=None, fixed_center=False, deviation_center=None, x_shift=0.0, y_shift=0.0, zoom_factor=1.0, mask=None): """ This function ... :param box: :param center: :param fixed_center: :param deviation_center: :param x_shift: :param y_shift: :param zoom_factor: :param mask: :return: """ # Get the dimensions of the box box_ysize = box.shape[0] box_xsize = box.shape[1] # Set the initial guess for the center of the model (the one that is specified, otherwise the center of the box) init_x0 = center[0] if center is not None else 0.5 * (box_xsize - 1) init_y0 = center[1] if center is not None else 0.5 * (box_ysize - 1) # Initialize an empty dictionary to specify fixed parameters fixed_parameters = {} if fixed_center: fixed_parameters['x_0'] = True fixed_parameters['y_0'] = True # Initialize an empty dictionary to specify bounds bounds = {} if deviation_center is not None: bounds['x_mean'] = [ init_x0 - deviation_center, init_x0 + deviation_center ] bounds['y_mean'] = [ init_y0 - deviation_center, init_y0 + deviation_center ] # Fit the data using astropy.modeling moffat_init = models.Moffat2D(amplitude=1., x_0=init_x0, y_0=init_y0, gamma=1.0, alpha=1.0, fixed=fixed_parameters, bounds=bounds) fit_model = fitting.LevMarLSQFitter() x_values = [] y_values = [] z_values = [] for x in range(box_xsize): for y in range(box_ysize): # If no mask is specified or the pixel is not masked, add the coordinates and value to the appropriate lists if mask is None or not mask[y, x]: x_values.append(x) y_values.append(y) z_values.append(box[y, x]) # Ignore model linearity warning from the fitter with warnings.catch_warnings(): warnings.simplefilter('ignore') moffat = fit_model( moffat_init, x_values, y_values, z_values) # What comes out is the model with the parameters set # Adjust the position of the model to a different coordinate frame if zoom_factor > 1.0: moffat.x_0.value = moffat.x_0.value / zoom_factor + x_shift moffat.y_0.value = moffat.y_0.value / zoom_factor + y_shift else: moffat.x_0.value += x_shift moffat.y_0.value += y_shift # Return the fitted two-dimensional Moffat model return moffat
def addstarpeak(dir, debug=False, mask=False, ghost=False, wl='Line'): """ fits star (or ghost) and adds STARPEAK to header. needed for PyKLIP. :param dir: directory :param amp: amplitude guess :param fwhm: fwhm :param debug: will print comparison of peak pixel values to fit values if set :param mask: will mask NaNs with zeros for fitting if set :param ghost: will fit ghost in lieu of star and return ghost peak and estimate for star peak in header. :param wl: set if ghost is set so it knows which scale factor to pull. values are "Line" or "Cont" :return: list of star (or ghost) peaks """ filelist = glob.glob(dir + '/*.fits') ##sort sequentially filelist.sort(key=lambda f: int(''.join(filter(str.isdigit, f)))) dummy_im = fits.getdata(filelist[0]) size = dummy_im.shape xcen = int((size[0] - 1) / 2) ycen = int((size[1] - 1) / 2) # guesses for fit parameters. used as starting point # input_parameters = [0, amp, xcen, ycen, fwhm, fwhm, 0] if ghost == True: xcen = int(xcen + 157.5) ycen = int(ycen - 7) if wl == "Line": ghost_scale = 184.7 if wl == "Cont": ghost_scale = 193.6 if wl == "False": "wl keyword must be set for ghost calibration" return diff = np.zeros(len(filelist)) peaks = [] fwhmlist = [] #size of image stamp width = 31 stmpsz = int((width - 1) / 2) for i in np.arange(len(filelist)): im = fits.getdata(filelist[i]) # make a copy - only invoked in case of ghost=True, but can't think of a cleverer way imcopy = np.copy(im) head = fits.getheader(filelist[i]) #crop around star (or ghost) for fitting imcopy = imcopy[ycen - stmpsz - 1:ycen + stmpsz, xcen - stmpsz - 1:xcen + stmpsz] #set up fit y, x = np.mgrid[:width, :width] #Moffat PSF model g_init = models.Moffat2D(np.nanmax(imcopy), stmpsz, stmpsz, 6, 1) fit_g = fitting.LevMarLSQFitter() #if ghost == True: # CROP AROUND GHOST #im = im[ycen - 50:ycen + 50 + 1, xcen - 50:xcen + 50 + 1] # do fit p = fit_g(g_init, x, y, imcopy) # populate headers for each individual image if ghost == True: head['GSTPEAK'] = p.amplitude.value head['STARPEAK'] = p.amplitude.value * ghost_scale head['FWHM'] = str(p.fwhm) else: head['STARPEAK'] = p.amplitude.value head['FWHM'] = str(p.fwhm) # record peak peaks.append(p.amplitude.value) # record fwhm fwhmlist.append(p.fwhm) # print a warning if any peak values are unphysical if (p.amplitude.value < 0) or (p.amplitude.value > 17000) or (np.isnan( p.amplitude.value)) == True: print("warning: unphysical peak value of", p.amplitude.value, 'for image', i + 1) # write out file with peak info in header if ghost == True: fits.writeto(filelist[i], im, header=head, overwrite=True) else: fits.writeto(filelist[i], im, header=head, overwrite=True) if debug == True: # print(filelist[i]) # print('fit peak is:', p[0], '. max pixel is: ', np.nanmax(im[cen-10:cen+10,cen-10:cen+10])) imsz = im.shape[1] imcen = int((imsz - 1) / 2.) diff[i] = p.amplitude.value - np.nanmax(im[imcen - 10:imcen + 10, imcen - 10:imcen + 10]) # write out list of peaks one directory up so KLIP doesn't try to pull it if ghost == True: fits.writeto(dir + '../' + str(wl) + 'ghostpeaks.fits', np.array(peaks), overwrite=True) fits.writeto(dir + '../' + str(wl) + 'ghostfwhmlist.fits', np.array(fwhmlist), overwrite=True) else: fits.writeto(dir + '../' + str(wl) + 'starpeaks.fits', np.array(peaks), overwrite=True) fits.writeto(dir + '../' + str(wl) + 'starfwhmlist.fits', np.array(fwhmlist), overwrite=True) if debug == True: print( 'standard deviation of difference between fit peak and max pixel is: ', np.std(diff)) print('max difference is:', np.max(abs(diff))) print('median difference is:', np.median(diff)) return (peaks)