def test_Sersic2D_theta(): theta = Angle(90, 'deg') model1 = models.Sersic2D(1, 5, 4, 25, 25, 0.5, theta=theta) theta2 = np.pi / 2. model2 = models.Sersic2D(1, 5, 4, 25, 25, 0.5, theta=theta2) assert model1.theta.quantity.to('radian').value == model2.theta.value assert model1(619.42, 31.314) == model2(619.42, 31.314)
def plot_model(image,model_morph): ny, nx = image.shape imy, imx = np.mgrid[0:ny, 0:nx] sersic_model = models.Sersic2D( amplitude=model_morph.sersic_amplitude, r_eff=model_morph.sersic_rhalf, n=model_morph.sersic_n, x_0=model_morph.sersic_xc, y_0=model_morph.sersic_yc, ellip=model_morph.sersic_ellip, theta=model_morph.sersic_theta) fitted_model = sersic_model(imx, imy) plt.subplot(121) plt.imshow(image, cmap='rainbow', origin='lower', norm=simple_norm(image, stretch='log', log_a=10000)) plt.subplot(122) plt.imshow(fitted_model, cmap='rainbow', origin='lower', norm=simple_norm(image, stretch='log', log_a=10000)) plt.show() plt.imshow(image-fitted_model, cmap='rainbow', origin='lower') plt.colorbar() plt.show() plt.hist((image-fitted_model).flatten(),bins=25) plt.show()
def test_print_special_operator_CompoundModel(capsys): """ Test that issue #11310 has been fixed """ model = convolve_models(models.Sersic2D(), models.Gaussian2D()) print(model) true_out = "Model: CompoundModel\n" +\ "Inputs: ('x', 'y')\n" +\ "Outputs: ('z',)\n" +\ "Model set size: 1\n" +\ "Expression: convolve_fft (([0]), ([1]))\n" +\ "Components: \n" +\ " [0]: <Sersic2D(amplitude=1., r_eff=1., n=4., x_0=0., y_0=0., ellip=0., theta=0.)>\n" +\ "\n" +\ " [1]: <Gaussian2D(amplitude=1., x_mean=0., y_mean=0., x_stddev=1., y_stddev=1., theta=0.)>\n" +\ "Parameters:\n" +\ " amplitude_0 r_eff_0 n_0 x_0_0 y_0_0 ... y_mean_1 x_stddev_1 y_stddev_1 theta_1\n" +\ " ----------- ------- --- ----- ----- ... -------- ---------- ---------- -------\n" +\ " 1.0 1.0 4.0 0.0 0.0 ... 0.0 1.0 1.0 0.0\n" out, err = capsys.readouterr() assert err == '' assert out == true_out
def test_convolved_sersic(): from scipy.signal import fftconvolve # Create Gaussian PSF. size = 10 # on each side from the center sigma_psf = 2.0 y, x = np.mgrid[-size:size + 1, -size:size + 1] psf = np.exp(-(x**2 + y**2) / (2.0 * sigma_psf**2)) psf /= np.sum(psf) # Create 2D Sersic profile. ny, nx = 25, 25 y, x = np.mgrid[0:ny, 0:nx] sersic = models.Sersic2D(amplitude=1, r_eff=5, n=1.5, x_0=12, y_0=12, ellip=0.5, theta=0) z = sersic(x, y) # Create "convolved" Sersic profile with same properties as normal one. convolved_sersic = statmorph.ConvolvedSersic2D(amplitude=1, r_eff=5, n=1.5, x_0=12, y_0=12, ellip=0.5, theta=0) with pytest.raises(AssertionError): _ = convolved_sersic(x, y) # PSF not set yet convolved_sersic.set_psf(psf) z_convolved = convolved_sersic(x, y) # Compare results. assert_allclose(z_convolved, fftconvolve(z, psf, mode='same'))
def ellipsefit(self, plot=True, mode='sersic'): """ Fit cube with elliptical isophote Args: plot (bool): if 'True', make plots mode (str): method to use to do fit ('sersic' for Sersic fitting, 'isophote' for elliptical isophotes) Outputs: Re (float): half-light radius """ print('Fitting ellipse to white-light image...') if mode == 'sersic': # Fit with 2D Sersic profile y, x = np.mgrid[:self.ysize, :self.xsize] p_init = models.Sersic2D(amplitude=0.05, r_eff=25, n=4, x_0=25, y_0=43, ellip=0.5, theta=5) fit_p = fitting.LevMarLSQFitter() p = fit_p(p_init, x, y, self.bband) print(p) if plot: fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(10, 5), sharex=True) axs[0].imshow(self.bband, origin='lower', cmap='viridis', vmin=0, vmax=13) axs[1].imshow(p(x, y), origin='lower', cmap='viridis', vmin=0, vmax=13) plt.show() # TODO: finish elliptical isophote fitting if mode == 'isophote': pass return
def fit_sersic2d(image, ellip=0.5, theta=0, fixed={}): # Estimate center of target y_mean, x_mean = np.array(image.shape) // 2 # Center guess # Create model to fit model = models.Sersic2D(amplitude=image.max(), r_eff=x_mean // 4, n=2, x_0=x_mean, y_0=y_mean, ellip=ellip, theta=theta, fixed=fixed) # Fit model to grid fitted_line, fit = fit_model(image, model) return fitted_line
def demo(): """ Show test where the source count distribution is a grism model """ import astropy.io.fits as pyfits flt = pyfits.open('id7h04dvq_GrismFLT.fits') flt = pyfits.open('id7h44toq_GrismFLT.fits') flt = pyfits.open('id7h01ovq_GrismFLT.fits') mask = (flt['GDQ'].data == 0) & (flt['GERR'].data < 0.1) & (flt['GERR'].data > 0) noise = np.random.normal(size=mask.sum())*np.median(flt['GERR'].data[mask]) data = flt['MODEL'].data[mask] + noise R = np.arange(-10,10,1.e-4) noise = np.random.normal(size=R.size)*ss data = np.exp(-R**2/2/sig**2)**2 + noise from astropy.modeling import models n = 1 r_e = 100 ser = models.Sersic2D(amplitude=1, r_eff=r_e, n=n, x_0=500, y_0=500, ellip=0, theta=0) yp, xp = np.indices((1014,1014)) ser_data = ser(xp, yp) ser_data /= ser_data.max() noise = np.random.normal(size=ser_data.shape)*ss data = (ser_data + noise).flatten() #data *= 100 #data = noise import mywfc3.npl self = mywfc3.npl.NoisyPowerlaw(data=data, c0=[0,0,0,0,-2]) self.dx_min = 0 x = np.arange(self.range[0], self.range[1], self.s0/50) res, fit = self.fit(x=x, tol=1.e-6) plt.plot(self.xh, self.yh, color='k', linestyle='steps-mid') plt.plot(self.xh, fit, color='r', linestyle='steps-mid') sn_mask = (flt['MODEL'].data < 0.5*flt['GERR'].data)[mask]
def test_print_special_operator_CompoundModel(capsys): """ Test that issue #11310 has been fixed """ model = convolve_models(models.Sersic2D(), models.Gaussian2D()) with astropy.conf.set_temp('max_width', 80): assert str(model) == "Model: CompoundModel\n" +\ "Inputs: ('x', 'y')\n" +\ "Outputs: ('z',)\n" +\ "Model set size: 1\n" +\ "Expression: convolve_fft (([0]), ([1]))\n" +\ "Components: \n" +\ " [0]: <Sersic2D(amplitude=1., r_eff=1., n=4., x_0=0., y_0=0., ellip=0., theta=0.)>\n" +\ "\n" +\ " [1]: <Gaussian2D(amplitude=1., x_mean=0., y_mean=0., x_stddev=1., y_stddev=1., theta=0.)>\n" +\ "Parameters:\n" +\ " amplitude_0 r_eff_0 n_0 x_0_0 y_0_0 ... y_mean_1 x_stddev_1 y_stddev_1 theta_1\n" +\ " ----------- ------- --- ----- ----- ... -------- ---------- ---------- -------\n" +\ " 1.0 1.0 4.0 0.0 0.0 ... 0.0 1.0 1.0 0.0"
def test_psf_convolved_image_model(): """Test fitting and PSF convolution""" # Make model: imsize = 300 sersic_model = models.Sersic2D( amplitude=1, r_eff=25, n=2, x_0=imsize / 2, y_0=imsize / 2, ellip=0, theta=0, bounds={ 'amplitude': (0., None), 'r_eff': (0, None), 'n': (0, 10), 'ellip': (0, 1), 'theta': (-2 * np.pi, 2 * np.pi), }, ) # Make model image image = model_to_image(sersic_model, imsize) # Make a PSF x_grid, y_grid = make_grid(51, factor=1) PSF = Moffat2D(x_0=25.0, y_0=25.0)(x_grid, y_grid) PSF /= PSF.sum() # Make a PSF image using model image and PSF psf_sersic_image = convolve(image, PSF) # Make a PSFConvolvedModel2D psf_sersic_model = PSFConvolvedModel2D(sersic_model, psf=PSF, oversample=None) psf_sersic_model.fixed['psf_pa'] = True # Make a PSFConvolvedModel2D image psf_sersic_model_image = model_to_image(psf_sersic_model, imsize) # Compare the PSF image to PSFConvolvedModel2D image error_arr = abs(psf_sersic_model_image - psf_sersic_image) / psf_sersic_image assert np.max(error_arr) < 0.01 # max error less than 1% error # Test fitting # Change params with offset psf_sersic_model.r_eff = 23 psf_sersic_model.x_0 = 0.6 + imsize / 2 psf_sersic_model.y_0 = 0.1 + imsize / 2 psf_sersic_model.n = 3 # Fit fitted_model, fit_info = fit_model( psf_sersic_image, psf_sersic_model, maxiter=10000, epsilon=1.4901161193847656e-10, acc=1e-9, ) # Generate a model image from the fitted model fitted_model_image = model_to_image(fitted_model, imsize) # Check if fit is close to actual error_arr = abs(fitted_model_image - psf_sersic_image) / psf_sersic_image assert np.max(error_arr) < 0.01 # max error less than 1% error
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.), astmodels.Tangent1D(amplitude=10., frequency=0.5, phase=1.), astmodels.ArcSine1D(amplitude=10., frequency=0.5, phase=1.), astmodels.ArcCosine1D(amplitude=10., frequency=0.5, phase=1.), astmodels.ArcTangent1D(amplitude=10., frequency=0.5, phase=1.), astmodels.Trapezoid1D(amplitude=10., x_0=0.5, width=5., slope=1.), astmodels.TrapezoidDisk2D(amplitude=10., x_0=0.5, y_0=1.5, R_0=5., slope=1.),
def generate_petrosian_sersic_correction(output_yaml_name, psf=None, r_eff_list=None, n_list=None, oversample=('x_0', 'y_0', 10, 10), plot=True): """ This function generates corrections for Petrosian radii by simulating a galaxy image and measuring its properties. This is done to identify the correct `epsilon` value that, multiplied with `r_petrosian`, would give `r_total_flux`. To achieve this, an image created from a Sersic model and convolved with a PSF (if provided). The Petrosian radii and concentrations are computed using the default `epsilon` = 2. Since the real `r_total_flux` of the simulated galaxy is known, the correct `epsilon` can be determined by `epsilon = r_petrosian / r_total_flux`. The resulting grid will be used to map measured `r_petrosian` and `C2080` to the correct `epsilon` value. After the gird is computed, it will be saved to a yaml file which is readable by `petrofit.petrosian.PetrosianCorrection`. Parameters ---------- output_yaml_name : str Name of output file, must have .yaml or .yml extension. psf : numpy.array or None 2D PSF image to pass to `petrofit.fitting.models.PSFConvolvedModel2D`. r_eff_list : list, (optional) List of `r_eff` (half light radii) in pixels to evaluate. n_list : list, (optional) List of Sersic indices to evaluate. oversample : int or tuple oversampling to pass to `petrofit.fitting.models.PSFConvolvedModel2D`. plot : bool Shows plot of photometry and Petrosian. Returns ------- petrosian_grid : dict Dictionary that is readable by `petrofit.petrosian.PetrosianCorrection` Also saves Yaml file that is readable by `petrofit.petrosian.PetrosianCorrection`. """ if r_eff_list is None: r_eff_list = np.arange(10, 100 + 5, 5) if n_list is None: n_list = np.arange(0.5, 6.0 + 0.5, 0.5) petrosian_grid = {} with ProgressBar(len(r_eff_list) * len(n_list), ipython_widget=True) as bar: for r_eff_idx, r_eff in enumerate(r_eff_list): c_pet_list_output = [] n_list_output = [] r_p_list_output = [] epsilon_list_output = [] r_p_hl_list_output = [] for n_idx, n in enumerate(n_list): bar.update() n = np.round(n, 2) amplitude = 100 / np.exp(gammaincinv(2. * n, 0.5)) # Total flux total_flux = sersic_enclosed(np.inf, amplitude=amplitude, r_eff=r_eff, n=n) total_flux = total_flux * 0.99 # Total flux radius r_total_flux = sersic_enclosed_inv(total_flux, amplitude=amplitude, r_eff=r_eff, n=n) ori_r_total_flux = r_total_flux max_r = r_total_flux * 2 if n < 2 else r_total_flux * 1.2 # Make r_list if max_r >= 200: r_list = [x for x in range(1, 201, 2)] r_list += [x for x in range(300, int(max_r) + 100, 100)] else: r_list = [x for x in range(1, int(max_r) + 2, 2)] r_list = np.array(r_list) image_size = max(r_list) * 2 x_0 = image_size // 2 y_0 = image_size // 2 # Define model galaxy_model = models.Sersic2D( amplitude=amplitude, r_eff=r_eff, n=n, x_0=x_0, y_0=y_0, ellip=0., theta=0., ) # PSF weap galaxy_model = PSFConvolvedModel2D(galaxy_model, psf=psf, oversample=oversample) galaxy_image = model_to_image(galaxy_model, image_size, center=(x_0, y_0)) flux_list, area_list, err = photometry_step( (image_size // 2, image_size // 2), r_list, galaxy_image, plot=plot, vmax=amplitude / 100) plt.show() if plot: print(r_eff, n, r_total_flux) plt.show() # Petrosian from Photometry p = Petrosian(r_list, area_list, flux_list) rc1, rc2, c_index = p.concentration_index() if np.any(np.isnan(np.array([rc1, rc2, c_index]))): raise Exception("concentration_index cant be computed") # Compute new r_total_flux u, indices = np.unique(flux_list, return_index=True) indices = np.array(indices) f = interp1d(flux_list[indices], r_list[indices], kind='linear') corrected_r_total_flux = f(total_flux) # Compute corrections corrected_epsilon = corrected_r_total_flux / p.r_petrosian corrected_p = copy(p) corrected_p.epsilon = corrected_epsilon if plot: corrected_p.plot(True, True) plt.show() print(corrected_epsilon) print(r_eff, p.r_half_light, corrected_p.r_half_light) print(ori_r_total_flux, p.r_total_flux, corrected_p.r_total_flux, corrected_r_total_flux) print(" ") c_pet_list_output.append(p.concentration_index()[-1]) n_list_output.append(n) r_p_list_output.append(p.r_petrosian) epsilon_list_output.append(corrected_epsilon) r_p_hl_list_output.append(corrected_p.r_half_light) del galaxy_model, galaxy_image petrosian_grid[r_eff] = { 'c_index': c_pet_list_output, 'n': n_list_output, 'epsilon': epsilon_list_output, 'r_petrosian': r_p_list_output, 'r_hl_petrosian': r_p_hl_list_output } if output_yaml_name is not None: with open(output_yaml_name, 'w') as outfile: print(outfile.name) yaml.dump(petrosian_grid, outfile) return petrosian_grid
def remove_fitted_sources(image, image_residual, cat, r_inner_mult=1, r_outter_mult=7, r_output_mult=20, index_start=None, index_end=None, n_models=2, show_progress=True, print_fit=False, show_fit=True): """ Fits and removes bright sersic profiles from image. Parameters ---------- image : array Input image to remove sources from. This image should be noise subtracted image_residual : array Image resulting from subtracting the input image by all the catalog sources. cat : `SourceCatalog` instance A `SourceCatalog` instance containing the properties of each source. r_inner_mult : int Multiples the object radius to give the radius of the object's core. Should be >= 1. r_outter_mult : int Multiples the object radius to give the radius of the outer components. Should be >= 1. r_output_mult : int Multiples the object radius to give the radius of the final cutout that is subtracted from the image. Should be >= 1. index_start : int Index of the first source in the area sorted catalog. So if this is set to 0, it will start removing at the object with the largest area in the catalog. index_end : int Index of last source in the area sorted catalog. n_models : int Number of models to use for fitting 1 = Sersic2D 2 = Sersic2D_core + Sersic2D_outter 3 = Sersic2D_core + Sersic2D_outter + models.Gaussian2D all models are centered around the same pixel (the core). show_progress : bool Show jupyter notebook progress bar print_fit : bool Print fit params show_fit : bool Plot fit Returns ------- subtracted_image : array An image with the fitted sources subtracted from it. subtracted_image_residual : array The input residual image with the fitted sources subtracted from it. fitted_sources : array An image showing only the fitted sources from the models. """ order = order_cat(cat) order = order[index_start:index_end] if show_progress: pb = widgets.IntProgress( value=0, min=0, max=len(order), step=1, description='Loading:', bar_style='', orientation='horizontal' ) display(pb) # Copy image for output image_copy = image.copy() image_residual_copy = image_residual.copy() image_zero = np.zeros_like(image_copy) photometric_sums = {} for i, cat_index in enumerate(order): if show_fit or print_fit: print(index_start + i, cat_index) if show_progress: # Update progress bar pb.value = i + 1 pb.description = "{}/{}".format(pb.value, len(order)) # Load object and aperture obj = cat[cat_index] # Estimate center of bobject cut = obj.segment.make_cutout(image, masked_array=True) cy, cx = np.unravel_index(cut.argmax(), cut.shape) x = obj.segment.bbox.ixmin + cx y = obj.segment.bbox.iymin + cy # Estimate other parts theta = obj.orientation.to(u.rad).value ellip = (1 - obj.semiminor_axis_sigma.value / obj.semimajor_axis_sigma.value) amp = cut.data.max() # Define fitting radius r_object = int(np.round(obj.semimajor_axis_sigma.value)) # inner cutout radius r_inner = r_object * r_inner_mult r_outter = r_object * r_outter_mult # Make images to fit target_zoom = cutout(image_copy, x, y, r_inner) target = cutout(image_copy, x, y, r_outter) target_residual = cutout(image_residual_copy, x, y, r_outter) # Fit inner core # -------------- # Find center of zoomed cutout image y_0, x_0 = np.array(target_zoom.shape) // 2 # Make inner model xy_slack = 10 # x and y value range / 2 model_1 = models.Sersic2D( amplitude=amp, n=2, r_eff=r_inner, ellip=ellip, theta=theta, x_0=x_0, y_0=y_0, # fixed={'theta':True, 'ellip':True}, bounds={ 'amplitude': (0, None), 'r_eff': (0, None), 'n': (0, 6), 'ellip': (0, 1), 'theta': (0, 2 * np.pi), 'x_0': (x_0 - xy_slack, x_0 + xy_slack), 'y_0': (y_0 - xy_slack, y_0 + xy_slack), }) # Fit core model to zoomed cutout to tighten first guess model_1, fit = fit_model(target_zoom, model_1, maxiter=10000, epsilon=1e-40) # Fix the center x and y of all models to match the fitted guess """ for pn in model.param_names: model.fixed[pn] = True """ model_1.fixed.update({ 'x_0': True, 'y_0': True, }) del model_1.bounds['x_0'] del model_1.bounds['y_0'] # Fit glow around target # ---------------------- # Remove old center and add new center y_0, x_0 = np.array(target_zoom.shape) // 2 model_1.x_0 -= x_0 model_1.y_0 -= y_0 y_0, x_0 = np.array(target.shape) // 2 model_1.x_0 += x_0 model_1.y_0 += y_0 # Setup second model for fitting xy_slack = 5 model_2 = models.Sersic2D( amplitude=0, n=0.1, r_eff=model_1.r_eff * 3, ellip=ellip, theta=theta, x_0=model_1.x_0, y_0=model_1.y_0, fixed={'x_0': True, 'y_0': True, 'theta': True, 'ellip': True}, bounds={ 'amplitude': (0, None), 'r_eff': (0, None), 'n': (0, 2), 'ellip': (0, 1), 'theta': (0, 2 * np.pi), }) # Fit second model to the residual of the image (image with segmented area masked) model_2, fits = fit_model(target_residual, model_2, maxiter=10000, epsilon=1e-40) # PSF Models # ---------- # Setup PSF image and fit it to image of target to estimate param guess # Normal sources model_3 = models.Gaussian2D( amplitude=amp, x_mean=model_1.x_0, y_mean=model_1.y_0, x_stddev=model_1.r_eff, y_stddev=model_1.r_eff, fixed={'x_mean': True, 'y_mean': True, } ) model_3, fits = fit_model(target, model_3, maxiter=10000, epsilon=1e-40) # Final Fit # --------- # Combine models if n_models == 1: model = model_1 elif n_models == 2: model = model_1 + model_2 elif n_models == 3: model = model_1 + model_2 + model_3 else: raise Exception("n_models should be 1, 2 or 3") # Fit combined model model, fit = fit_model(target, model, maxiter=10000, epsilon=1e-40) model, fit = fit_model(target, model, maxiter=10000, epsilon=1e-40) # Make center of model the center of model_1 model.x_0 = model_1.x_0 model.y_0 = model_1.y_0 # Plot and Print # -------------- if show_fit: fig, ax = plot_fit(target, model) # , vmin=vmin, vmax=vmax) plt.show() if print_fit: print("\n".join([str(j) for j in zip(model.param_names, model.parameters)]) + "\n" * 3) # Subtract from image # ------------------- if fit.fit_info['ierr'] > 4: # Fit failed continue # Make new image size = r_inner * r_output_mult y_arange, x_arange = np.mgrid[ int(model.y_0.value) - size:int(model.y_0.value) + size, int(model.x_0.value) - size:int(model.x_0.value) + size, ] model_image = model(x_arange, y_arange) # Subtract cutout from main copy image image_copy = model_subtract(image_copy, np.array(model_image), x, y) image_copy = np.clip(image_copy, 0, image_copy.max()) #image_copy[y - 8:y + 8, x - 8:x + 8] = np.nan # Add cutout to main cutout only image image_zero = model_subtract(image_zero, -1 * np.array(model_image), x, y) # Subtract cutout second component from residual image image_residual_copy = model_subtract(image_residual_copy, np.array(model_image), x, y) image_residual_copy = np.clip(image_residual_copy, 0, image_residual_copy.max()) # Photometry photometric_sums[obj.label] = model_image.sum() return image_copy, image_residual_copy, image_zero, photometric_sums
import scipy.ndimage as ndi from astropy.visualization import simple_norm from astropy.modeling import models from astropy.convolution import convolve import photutils import time import statmorph # %matplotlib inline #%% ny, nx = 240, 240 y, x = np.mgrid[0:ny, 0:nx] sersic_model = models.Sersic2D(amplitude=1, r_eff=20, n=2.5, x_0=120.5, y_0=96.5, ellip=0.5, theta=-0.5) image = sersic_model(x, y) plt.imshow(image, cmap='gray', origin='lower', norm=simple_norm(image, stretch='log', log_a=10000)) plt.show() #%% size = 20 # on each side from the center sigma_psf = 2.0 y, x = np.mgrid[-size:size + 1, -size:size + 1] psf = np.exp(-(x**2 + y**2) / (2.0 * sigma_psf**2))
def fitSersic(image: np.ndarray, centroid: List[float], fwhms: List[float], theta: float, starMask=None): '''Function that fits a 2D sersic function to an image of a Galaxy. Parameters ---------- image : np.ndarray image to which a 2D Sersic function will be fit centroid : List[float] Centre of object of interest fwhms : List[float] Full width half maximums of object theta : float rotation of object anticlockwise from positive x axis starMask : np.ndarray Mask contains star locations. Returns ------- Parameters : astropy.modeling.Model object Collection of best fit parameters for the 2D sersic function ''' if starMask is None: imageCopy = image else: imageCopy = image * starMask fit_p = fitting.LevMarLSQFitter() # amplitude => Surface brightness at r_eff # r_eff => Effective (half-light) radius # n => sersic index, guess 1.? b = 2. * min(fwhms) a = 2. * max(fwhms) ellip = 1. - (b / a) apertureTotal = EllipticalAperture(centroid, a, b, theta) totalSum = apertureTotal.do_photometry(imageCopy, method="exact")[0][0] # get bracketing values for root finder to find r_eff deltaA = (a / 100.) * 2. aCurrent = a - deltaA aMin = 0 aMax = 0 while True: apertureCurrent = EllipticalAperture(centroid, aCurrent, b, theta) currentSum = apertureCurrent.do_photometry(imageCopy, method="exact")[0][0] currentFraction = currentSum / totalSum if currentFraction <= .5: aMin = aCurrent aMax = aCurrent + deltaA break aCurrent -= deltaA # get root r_eff = brentq(_fractionTotalFLuxEllipse, aMin, aMax, args=(imageCopy, b, theta, centroid, totalSum)) # calculate amplitude at r_eff a_in = r_eff - 0.5 a_out = r_eff + 0.5 b_out = a_out - (1. * ellip) ellip_annulus = EllipticalAnnulus(centroid, a_in, a_out, b_out, theta) totalR_effFlux = ellip_annulus.do_photometry(imageCopy, method="exact")[0][0] meanR_effFlux = totalR_effFlux / ellip_annulus.area sersic_init = models.Sersic2D(amplitude=meanR_effFlux, r_eff=r_eff, n=2.0, x_0=centroid[0], y_0=centroid[1], ellip=ellip, theta=theta) ny, nx = imageCopy.shape y, x = np.mgrid[0:ny, 0:nx] Parameters = fit_p(sersic_init, x, y, imageCopy, maxiter=1000, acc=1e-8) return Parameters
def make_image(): """Make 2D Sersic2D image""" imsize = 500 d = 5 sersic_model = models.Sersic2D( amplitude=1, r_eff=25, n=2, x_0=imsize / 2, y_0=imsize / 2, ellip=0, theta=0, bounds={ 'amplitude': (0., None), 'r_eff': (0, None), 'n': (0, 10), 'ellip': (0, 1), 'theta': (-2 * np.pi, 2 * np.pi), }, ) sersic_model += models.Sersic2D( amplitude=1, r_eff=25, n=2, x_0=imsize * 0.75, y_0=imsize * 0.75, ellip=0.5, theta=np.pi / 4, bounds={ 'amplitude': (0., None), 'r_eff': (0, None), 'n': (0, 10), 'ellip': (0, 1), 'theta': (-2 * np.pi, 2 * np.pi), }, ) sersic_model += models.Sersic2D( amplitude=1, r_eff=25, n=1, x_0=imsize * 0.25, y_0=imsize * 0.25, ellip=0.2, theta=np.pi / 6, bounds={ 'amplitude': (0., None), 'r_eff': (0, None), 'n': (0, 10), 'ellip': (0, 1), 'theta': (-2 * np.pi, 2 * np.pi), }, ) model_image = model_to_image(sersic_model, imsize) image_mean, image_median, image_stddev = sigma_clipped_stats(model_image, sigma=3) # model_image += np.random.normal(0, 3*image_stddev, size=model_image.shape) wcs = WCS() header = wcs.to_header() for param_name, param_val in zip(sersic_model.param_names, sersic_model.parameters): header[param_name] = param_val header['BUNIT'] = 'electron / s ' path = "sersic_2d_image.fits.gz" sersic_2d_path = os.path.join(os.path.dirname(__file__), path) fits.writeto(sersic_2d_path, data=model_image, header=header, overwrite=True)
def make_niriss_image(plot=False): """ Create a direct image with sources that have been convolved with the NIRISS PSF from webbpsf as well as the segmentation (ID) image. Args: plot (Bool): True if plots should be saved. """ # Add only ra-dec, rotation, and wcs to add the sources at the right places # `size` is image dimension in arcsec hdu = grizli.utils.make_wcsheader(size=NAXIS[0] * 0.0656, pixscale=0.0656, get_hdu=True) nis_header = hdu.header nis_header["CRVAL1"] = RA_CENTER nis_header["CRVAL2"] = DEC_CENTER # Rotate image to desired PA nis_wcs = pywcs.WCS(nis_header) cd_rot = rotate_CD_matrix(nis_wcs.wcs.cd, PA_APER) for i in range(2): for j in range(2): nis_header["CD{0}_{1}".format(i + 1, j + 1)] = cd_rot[i][j] nis_wcs = pywcs.WCS(nis_header) nis_wcs.pscale = 0.0656 #grizli.utils.get_wcs_pscale(nis_wcs) nis_header["PA_APER"] = PA_APER # Load the catalog and compute detector flux gfit = Table.read(SOURCE_CATALOG, format='ascii.commented_header') object_mag = gfit[MAG_COL] ZP = ZPs[NIS_FILTER.lower()] object_flux = 10**(-0.4 * (object_mag - ZP)) # Determine which objects are within the NIRISS FoV xc, yc = nis_wcs.all_world2pix(gfit['ra'], gfit['dec'], 0) obj_in_img = (xc > 1) & (yc > 1) & (xc < NAXIS[0] - 1) & (yc < NAXIS[1] - 1) obj_in_img &= object_flux > 0 # Point sources and faint sources stars = obj_in_img & ((gfit['star_flag'] == 1) | (object_mag > MAX_MAG)) star_idx = np.arange(len(gfit))[stars] # Galaxies if 're' not in gfit.colnames: gals = obj_in_img < -100 # False else: gals = obj_in_img & (~stars) & (object_mag <= MAX_MAG) & (gfit['re'] > 0) gal_idx = np.arange(len(gfit))[gals] # Put sources in the image # Initialize model and segmentation images with zeros nis_model = np.zeros(NAXIS[::-1], dtype=np.float32) nis_seg = np.zeros(NAXIS[::-1], dtype=int) # pixel indices yp, xp = np.indices(NAXIS[::-1]) # Add star point sources xpix = np.cast[int](np.round(xc)) ypix = np.cast[int](np.round(yc)) for ix in star_idx: nis_model[ypix[ix], xpix[ix]] = object_flux[ix] if SEG_THRESHOLD > 0: Rseg = 5 for i, ix in enumerate(star_idx): print('seg: {0} ({1}/{2})'.format(ix, i, stars.sum())) R = np.sqrt((xp - xpix[ix])**2 + (yp - ypix[ix])**2) clip_seg = (R <= Rseg) & (nis_seg == 0) nis_seg[clip_seg] = gfit['id'][ix] # Add Sersic sources for i, ix in enumerate(gal_idx): print('ID: {0} ({1}/{2})'.format(ix, i, gals.sum())) # Effective radius, in pixels re = gfit['re'][ix] / nis_wcs.pscale se = models.Sersic2D(amplitude=1., r_eff=re, n=gfit['n'][ix], x_0=xc[ix], y_0=yc[ix], ellip=1 - gfit['q'][ix], theta=(gfit['pa'][ix] + 90 - PA_APER) / 180 * np.pi) # Normalize to catalog flux m = se(xp, yp) renorm = object_flux[ix] / m.sum() if not np.isfinite(renorm): continue # Add to model image nis_model += m * renorm if SEG_THRESHOLD > 0: # Add to segmentation image clip_seg = (m * renorm > SEG_THRESHOLD) & (nis_seg == 0) nis_seg[clip_seg] = gfit['id'][ix] # Check image if plot is True: plt.figure(figsize=(10, 10)) plt.subplot(projection=pywcs.WCS(nis_header)) plt.imshow(np.log10(nis_model)) #plt.imshow(nis_seg) plt.grid(color='black', ls='solid') plt.xlabel('Galactic Longitude') plt.ylabel('Galactic Latitude') plt.title("Sources (logscale)") figname = "{}_sources.png".format(OUTROOT) plt.savefig(figname) print("Wrote {}".format(figname)) #plt.show() # Convolve with NIRISS PSF and save image and segmentation map. nis = webbpsf.NIRISS() # Add -0.5, -0.5 pixel offset to get convolved image in correct place ?? nis.options[ 'source_offset_r'] = 0.5 * nis_wcs.pscale # offset in arcseconds nis.options['source_offset_theta'] = 135. # degrees CCW from +Y # Get the PSF nis.filter = NIS_FILTER.upper() psf_hdu = nis.calcPSF(fov_pixels=64) # power of 2 for fast FFT convolution psf = psf_hdu[1].data psf /= psf.sum() # Convolve the model image nis_fullsim = stsci.convolve.convolve2d(nis_model, psf, output=None, mode='nearest', cval=0.0, fft=1) # Mask low fluxes to make images gzip smaller #mask = nis_fullsim > CLIP_FLUX #nis_fullsim[~mask] = 0 # Save the output image filename = '{0}-{1}.fits'.format(OUTROOT, nis.filter.lower()) fits.writeto(filename, data=nis_fullsim, header=nis_header, overwrite=True, output_verify='fix') print("Wrote {}".format(filename)) # Save the segmentation image if SEG_THRESHOLD > 0: filename = '{0}-{1}_seg.fits'.format(OUTROOT, nis.filter.lower()) fits.writeto(filename, data=nis_seg, header=nis_header, overwrite=True, output_verify='fix') print("Wrote {}".format(filename)) # Check image. if plot is True: plt.subplot(projection=pywcs.WCS(nis_header)) # plt.imshow(nis_fullsim) plt.imshow(np.log10(nis_fullsim)) #plt.imshow(nis_seg) plt.grid(color='black', ls='solid') plt.xlabel('Galactic Longitude') plt.ylabel('Galactic Latitude') plt.title("Distorted sources convolved with PSF (logscale)") figname = "{}_sourcepsf.png".format(OUTROOT) plt.savefig(figname) print("Wrote {}".format(figname))