def create_Sersic2D(imshape, amplitude=1, x_0=50, y_0=50, radius=25, n=1, ellip=.5, theta=-1): """ Create a galaxy profile defined by a Sersic distribution Parameters: imshape (numpy.array): image shape given for calculating size of distribution amplitude (float): amplitude of the Sersic distribution x_0 (float): horizontal position of center y_0 (float): vertical position of center radius (float): radius of major axis of the distribution n (int (0.5, 10) ): Sersic number ellip (float (0,1)): eccentricity of the Sersic distribution theta (float): orientation angle of the major axis Returns: numpy.array with galaxy distribution """ x, y = create_meshgrid(imshape) mod = Sersic2D(amplitude=amplitude, x_0=x_0, y_0=y_0, r_eff=radius, n=n, ellip=ellip, theta=theta) return mod(x, y)
def SersicGalaxy(cell_name, center, r_eff, sersic_n, ellip, angle, num_squares, square_size=1.0, layer=1): # We're going to create a brightness profile by randomly dithering # num_squares squares of size square_size # Create a Cell and add the box cell = gds.core.Cell(cell_name) for n in range(num_squares): # Here we use rejection sampling to generate the 2D Sersic profile Reject = True while Reject: x0 = -6.0 * r_eff + 12.0 * r_eff * rand() y0 = -6.0 * r_eff + 12.0 * r_eff * rand() if Sersic2D.evaluate(x0, y0, 1.0, r_eff, sersic_n, 0.0, 0.0, ellip, 0.0) > rand(): Reject = False x = center[0] + x0 * cos(angle) + y0 * sin(angle) y = center[1] - x0 * sin(angle) + y0 * cos(angle) x = round(x / square_size) * square_size y = round(y / square_size) * square_size pixel = gds.shapes.Rectangle( (x - square_size / 2.0, y - square_size / 2.0), (x + square_size / 2.0, y + square_size / 2.0), layer=layer) cell.add(pixel) return cell
def fit_sersic_to_stamp(image): im = fits.open(image) data = im[0].data mod = Sersic2D(amplitude=1, r_eff=1, n=3) img = mod(data, data.T) print(img) return img
def make_galaxy(self): """ This function ... :return: """ # Inform the user log.info("Adding smooth galaxy source ...") effective_radius = self.config.galaxy_effective_radius effective_galaxy_angle = self.config.galaxy_angle + self.effective_rotation_angle axial_ratio = self.config.galaxy_axial_ratio angle_deg = effective_galaxy_angle.to("deg").value # Produce guess values initial_sersic_amplitude = self.config.galaxy_central_flux initial_sersic_r_eff = effective_radius initial_sersic_n = self.config.galaxy_sersic_index initial_sersic_x_0 = self.config.galaxy_position.x initial_sersic_y_0 = self.config.galaxy_position.y initial_sersic_ellip = (axial_ratio - 1.0) / axial_ratio initial_sersic_theta = np.deg2rad(angle_deg) # Produce sersic model from guess parameters, for time trials sersic_x, sersic_y = np.meshgrid(np.arange(self.xsize), np.arange(self.ysize)) sersic_model = Sersic2D(amplitude=initial_sersic_amplitude, r_eff=initial_sersic_r_eff, n=initial_sersic_n, x_0=initial_sersic_x_0, y_0=initial_sersic_y_0, ellip=initial_sersic_ellip, theta=initial_sersic_theta) sersic_map = sersic_model(sersic_x, sersic_y) # Set the galaxy frame self.galaxy = Frame(sersic_map) # Mask self.galaxy[self.rotation_mask] = 0.0 limit_radius = self.config.galaxy_relative_asymptotic_radius * effective_radius # Create galaxy region galaxy_center = PixelCoordinate(initial_sersic_x_0, initial_sersic_y_0) galaxy_radius = PixelStretch(limit_radius, limit_radius / axial_ratio) self.galaxy_region = PixelEllipseRegion(galaxy_center, galaxy_radius, effective_galaxy_angle) # Set galaxy map zero outside certain radius self.galaxy[self.galaxy_mask.inverse()] = 0.0 # Plot if self.config.plot: plotting.plot_box(self.galaxy, title="galaxy")
def fit(self): """ This function ... :return: """ sersic_model = Sersic2D(amplitude=sersic_amplitide, r_eff=sersic_r_eff, n=sersic_n, x_0=sersic_x_0, y_0=sersic_y_0, ellip=sersic_ellip, theta=sersic_theta) sersic_map = sersic_model(sersic_x, sersic_y)
def make_gals(el, pa, re, sersic, size=(60, 60)): x, y = np.meshgrid(np.arange(size[0]), np.arange(size[1])) gal_array = [] for i in range(len(el)): mod = Sersic2D(amplitude=1, r_eff=re[i], n=sersic[i], x_0=(size[0] - 1) / 2.0, y_0=(size[1] - 1) / 2.0, ellip=el[i], theta=pa[i]) img = mod(x, y) norm = img / np.max(img) gal_array.append(norm) return np.array(gal_array)
def SersicGalaxy(boundingcell, cell_name, center, r_eff, sersic_n, ellip, angle, m, square_size=1.0, layer=1): # We're going to create a brightness profile by randomly dithering # num_squares squares of size square_size # magnitudes are calculated assuming a 10" exposure at 100% light intensity # Create a Cell and add the box text_step = 2.0 * boundingcell.PixelSizeX num_squares = int(pow(10.0, 0.4 * (27.9 - m))) cell=gds.core.Cell(cell_name) nxmin = max(0, int((center[0] - 10.0 * r_eff - boundingcell.xmin) / boundingcell.dx)) nxmax = min(boundingcell.nx-1, int((center[0] + 10.0 * r_eff - boundingcell.xmin) / boundingcell.dx)) nymin = max(0, int((center[1] - 10.0 * r_eff - boundingcell.ymin) / boundingcell.dy)) nymax = min(boundingcell.ny-1, int((center[1] + 10.0 * r_eff - boundingcell.ymin) / boundingcell.dy)) for n in range(num_squares): # Here we use rejection sampling to generate the 2D Sersic profile Reject = True while Reject: AlreadyFilled = True while AlreadyFilled: nx = randint(nxmin, nxmax) ny = randint(nymin, nymax) AlreadyFilled = boundingcell.data[nx,ny] x = boundingcell.x[nx] y = boundingcell.y[ny] r = sqrt((x-center[0])**2 + (y-center[1])**2) #print n, center, nx, ny, x, y, r, Sersic2D.evaluate(x, y, 1.0, r_eff, sersic_n, center[0], center[1], ellip, angle) if Sersic2D.evaluate(x, y, 1.0, r_eff, sersic_n, center[0], center[1], ellip, angle) > rand(): Reject = False boundingcell.data[nx,ny] = True pixel=gds.shapes.Rectangle((x-boundingcell.dx/2.0, y-boundingcell.dy/2.0), (x+boundingcell.dx/2.0, y+boundingcell.dy/2.0), layer=layer) cell.add(pixel) # Now we annotate it with non-printing text. name = gds.core.Text('Galaxy', (center[0] - 2.0 * text_step, center[1] - 2.0 * text_step), layer=3, magnification=0.002) cell.add(name) mag = gds.core.Text('Mag = %.2f'%m, (center[0] - 2.0 * text_step, center[1] - 3.0 * text_step), layer=3, magnification=0.002) cell.add(mag) reff = gds.core.Text('r_eff = %.2f arcseconds'%(r_eff / boundingcell.PixelSizeX * 0.2), (center[0] - 2.0 * text_step, center[1] - 4.0 * text_step), layer=3, magnification=0.002) cell.add(reff) sersicn = gds.core.Text('n = %.2f'%sersic_n, (center[0] - 2.0 * text_step, center[1] - 5.0 * text_step), layer=3, magnification=0.002) cell.add(sersicn) ell = gds.core.Text('ellip = %.2f'%ellip, (center[0] - 2.0 * text_step, center[1] - 6.0 * text_step), layer=3, magnification=0.002) cell.add(ell) rot = gds.core.Text('angle = %.2f degrees'%(angle*180.0/pi), (center[0] - 2.0 * text_step, center[1] - 7.0 * text_step), layer=3, magnification=0.002) cell.add(rot) #print "Galaxy, m = %f, n = %d"%(m, num_squares) return cell
def intensity(self): """ 2D light distribution following a Sersic2D profile Returns ------- numpy array """ mod = Sersic2D(x_0=self.x_0, y_0=self.y_0, amplitude=self.amplitude, r_eff=self.r_eff, n=self.n, ellip=self.ellip, theta=self.theta) return mod(self.x, self.y)
def __init__(self, psf, amplitude=1, r_eff=1, n=4, x_0=0, y_0=0, ellip=0, theta=0 * u.deg, **kwargs): theta = _check_theta_units(theta) super().__init__(amplitude, r_eff, n, x_0, y_0, ellip, theta, **kwargs) self.sersic_deconvolved = Sersic2D(amplitude, r_eff, n, x_0, y_0, ellip, theta, **kwargs) psf /= psf.sum() self.psf = psf
def make_galaxies_astropy(self, image, flux, galsize, x, y, ar, pa, n=4): for f, s, xi, yi, pai, ari, in zip(flux, galsize, x, y, pa, ar): fluxlim = 0.0001 * f # 0.1 scale = 1 # arcsec/pixel r_e = s # effective radius ellip = ari # ellipticity theta = numpy.deg2rad(pai) # position angle x_cent = 0 # x centroid y_cent = 0 # x centroid tot_flux = f # total flux s1 = Sersic1D(amplitude=1, r_eff=r_e, n=n) r = numpy.arange(0, 1000, scale) s1_n = s1(r) / sum(s1(r)) extent = numpy.where(s1_n * f > fluxlim)[0].max() if extent % 2 > 0: extent += 1 ser_model = Sersic2D(r_eff=r_e, n=n, ellip=ellip, theta=theta, x_0=x_cent, y_0=y_cent) x = numpy.arange(-extent / 1., extent / 1., scale) + x_cent / scale y = numpy.arange(-extent / 1., extent / 1., scale) + y_cent / scale X, Y = numpy.meshgrid(x, y) img = ser_model(X, Y) img /= numpy.sum(img) img *= tot_flux xi, yi = int(xi), int(yi) # COLUMNS FIRST -- because FITS are silly image[yi - img.shape[1] // 2:yi + img.shape[1] // 2, xi - img.shape[0] // 2:xi + img.shape[0] // 2] += img return image
def make_model(n, re, MLs, x0, y0, x, y, dist, edges, lastedge=50): ''' This function is where we generate the grid of all possible models for a given galaxy. Given a sersic index "n" and a half-light radius "re", we make models of the 2D light distribution of a galaxy with that n/re. Then, we apply a range of power-law M/L gradients to turn that light profile into a bunch of theoretical mass profiles. We convolve the 2D mass profiles with the HST F160W PSF to get a set of theoretical 'as-observed' mass profiles, then extract the 1D versions of those mass profiles in annuli. We return the set of both the intrinsic and convolved possible mass profiles for the galaxy. "MLs" : list of possible M/L gradients "x0, y0" : where to center the galaxy "x, y" : grid of x/y values where we want to evaluate the model "edges" : list of the radii of the elliptical apertures we used to calculate the M/L gradient. We want to pull out the model M/L at these same radii. In units of pixels. "dist" : array that has the distance of each pixel in the model from the center x0,y0 location. this is used to calculate the actual M/L gradient. ''' # make 2D sersic model. re should be given in *pixels*! lightModel = Sersic2D(amplitude=1e5, r_eff=re, n=n, x_0=x0, y_0=y0)(x,y) # multiply M/L gradients by that light profile to make mass profiles massModel = lightModel[:,:, np.newaxis] * \ dist[:, :, np.newaxis] ** (np.log10(MLs[:-1]) / np.log10(lastedge)) # get the half-mass radii for all those mass profiles rehalf = np.array([get_rehalf(re, n, ml, edges, lastedge) for ml in MLs]) # Convolve both the light and mass profiles with the HST PSF lightConv = fftconvolve(lightModel, psf, mode='same') massConv = np.array([fftconvolve(massModel[:,:,m], psf, mode='same') for m in range(len(MLs[:-1]))]) # initialize some arrays that will hold the model light and mass # profiles (both in observed & deconvolved space) light = np.zeros((len(edges))) mass = np.zeros((len(MLs), len(edges))) lightInt = np.zeros((len(edges))) massInt = np.zeros((len(MLs), len(edges))) edges = np.append(edges, edges[-1]+1) # measure aperture photometry in annuli for both light and mass for an in range(len(edges)-1): # make the aperture at this radius ap = photutils.CircularAperture((x0,y0), r = edges[an]) # and measure aperture photometry on light profile in that annulus light[an] = photutils.aperture_photometry(lightConv, ap)['aperture_sum'][0] lightInt[an] = photutils.aperture_photometry(lightModel, ap)['aperture_sum'][0] # measure aperture photometry for all possible mass maps in that annulus for slope_idx in range(len(MLs[:-1])): mass[slope_idx, an] = photutils.aperture_photometry(massConv[slope_idx,:,:], \ ap)['aperture_sum'][0] massInt[slope_idx, an] = photutils.aperture_photometry(massModel[:,:,slope_idx], \ ap)['aperture_sum'][0] mass[-1, :] = light massInt[-1,:] = lightInt # ultimatly we want the profile in *annuli* so just subtract smaller apertures # from larger apertures to make the profile in annuli not cumulative distributions lightDiff = light - np.insert(light[:-1], 0, 0) massDiff = mass - np.insert(mass[:, :-1], 0, np.zeros((len(MLs))), axis=1) lightDiffInt = lightInt - np.insert(lightInt[:-1], 0, 0) massDiffInt = massInt - np.insert(massInt[:, :-1], 0, np.zeros((len(MLs))), axis=1) # what we want to return: # 1) half-mass radii for each M/L gradient # 2) "as-observed" eg convolved M/L profile for each possible M/L gradient # 3) intrinsic M/L profile for each possible M/L gradient # keep in mind that the "r" axis of these arrays matches our measurements return (rehalf, massDiff / lightDiff[np.newaxis, :], massDiffInt / lightDiffInt[np.newaxis, :])
def make_extended_sources(self): """ This function ... :return: """ # Inform the user log.info("Making extended sources ...") # Loop over the filters for fltr in self.frames: # Get the frame frame = self.frames[fltr] # Loop over the sources for index in range(len(self.extended_source_catalog)): principal = self.extended_source_catalog["Principal"][index] # Determine flux if principal: central_flux = 1e2 else: central_flux = np.random.uniform(10, 50) # Get pixel position coordinate = self.extended_source_catalog.get_position( index).to_pixel(frame.wcs) initial_sersic_amplitude = central_flux initial_sersic_x_0 = coordinate.x initial_sersic_y_0 = coordinate.y if principal: s4g_name, pa, ellipticity, n, re, mag = catalogs.get_galaxy_s4g_one_component_info( "M81") angle = Angle(pa, "deg") angle_deg = pa effective_radius = re.to( "arcsec").value / frame.average_pixelscale.to( "arcsec").value initial_sersic_n = n initial_sersic_r_eff = effective_radius initial_sersic_ellip = ellipticity initial_sersic_theta = np.deg2rad(angle_deg) # 1 / axial_ratio = 1 - ellipticity axial_ratio = 1. / (1. - ellipticity) else: # Get position angle and axes lengths pa = self.extended_source_catalog["Posangle"][index] major = self.extended_source_catalog["Major"][index] minor = self.extended_source_catalog["Minor"][index] axial_ratio = major / minor angle = Angle(pa, "deg") angle_deg = pa effective_radius = major # Produce guess values initial_sersic_r_eff = effective_radius initial_sersic_n = self.config.galaxy_sersic_index initial_sersic_ellip = (axial_ratio - 1.0) / axial_ratio initial_sersic_theta = np.deg2rad(angle_deg) # Produce sersic model from guess parameters, for time trials sersic_x, sersic_y = np.meshgrid(np.arange(frame.xsize), np.arange(frame.ysize)) sersic_model = Sersic2D(amplitude=initial_sersic_amplitude, r_eff=initial_sersic_r_eff, n=initial_sersic_n, x_0=initial_sersic_x_0, y_0=initial_sersic_y_0, ellip=initial_sersic_ellip, theta=initial_sersic_theta) sersic_map = sersic_model(sersic_x, sersic_y) # Limit galaxy? if self.config.limit_galaxy: limit_radius = self.config.galaxy_relative_asymptotic_radius * effective_radius # Create galaxy region galaxy_center = PixelCoordinate(initial_sersic_x_0, initial_sersic_y_0) galaxy_radius = PixelStretch(limit_radius, limit_radius / axial_ratio) galaxy_region = PixelEllipseRegion(galaxy_center, galaxy_radius, angle) galaxy_mask = galaxy_region.to_mask( frame.xsize, frame.ysize) # Set galaxy map zero outside certain radius sersic_map[galaxy_mask.inverse()] = 0.0 # Add frame += sersic_map # mask frame[self.rotation_masks[fltr]] = 0.0
def __init__(self, sp, r_eff, n, theta, ellip, xy_dim, pixel_scale, num_r_eff=10, dx=0, dy=0, labels=None): self.sp = sp self.mag_limit = sp.mag_limit self.mag_limit_band = sp.mag_limit_band self.smooth_model = None if self.mag_limit is not None and sp.frac_num_sampled < 1.0: _r_eff = check_units(r_eff, 'kpc').to('Mpc').value _theta = check_units(theta, 'deg').to('radian').value _distance = check_units(sp.distance, 'Mpc').to('Mpc').value _pixel_scale = check_units(pixel_scale, u.arcsec / u.pixel) if _r_eff <= 0: raise Exception('Effective radius must be greater than zero.') xy_dim = check_xy_dim(xy_dim) x_0, y_0 = xy_dim // 2 x_0 += dx y_0 += dy self.n = n self.ellip = ellip self.r_sky = np.arctan2(_r_eff, _distance) * u.radian.to('arcsec') self.r_sky *= u.arcsec r_pix = self.r_sky.to('pixel', u.pixel_scale(_pixel_scale)).value self.smooth_model = Sersic2D(x_0=x_0, y_0=y_0, n=n, r_eff=r_pix, theta=_theta, ellip=ellip) self.xy_kw = dict(num_stars=sp.num_stars, r_eff=r_eff, n=n, theta=theta, ellip=ellip, distance=sp.distance, xy_dim=xy_dim, num_r_eff=num_r_eff, dx=dx, dy=dy, pixel_scale=pixel_scale, random_state=sp.rng) _xy = sersic_xy(**self.xy_kw) super(SersicSP, self).__init__(_xy, sp.mag_table, xy_dim, pixel_scale, labels)
#function: Composite moffat psf for multiple objects def E2moff_multi((x,y), psftype, given, free): out = 0 count = 0 for i, psf in enumerate(psftype): #add moffat to output for each moffat if psf == '3': #given is empty, general psf params are all in free out+= E2moff((x, y),*free[count:count+7]) count = count+7 if psf == '2': #given contains [ax,ay,b,theta], free has [A, x0, y0] out+= E2moff((x,y),free[count],given[i][0],given[i][1],given[i][2],given[i][3],free[count+1],free[count+2]) count = count+3 if psf == '1': #given contains [ax,ay,b,theta,x0,y0], free has [A] out+= E2moff((x, y),free[count],*given[i]) count = count+1 if psf[0] == 's': #we need to use sersic profile from astropy.modeling.models import Sersic2D if psf[1] == 'n': #full sersic profile out+= Sersic2D(*free[count:count+7])(x,y) count = count+7 else: #known sersic n out+= Sersic2D(amplitude=free[count],r_eff=free[count+1],n=float(psf[1:]),x_0=free[count+2],y_0=free[count+3],ellip=free[count+4],theta=free[count+5])(x,y) count = count+6 return out
def sersic_xy(num_stars, r_eff, n, theta, ellip, distance, xy_dim, pixel_scale=0.2, num_r_eff=10, random_state=None): """ Sample xy positions from a two-dimensional Sersic distribution. .. note:: To sample the Sersic distribution, `~artpop.space.sample.xy_from_grid` is used. This means that large sources (and/or large `num_r_eff`) eat up a lot of memory. Parameters ---------- num_stars : int Number of stars (i.e., positions) to sample. r_eff : float or `~astropy.units.Quantity` Effective radius of the source. If a float is given, the units are assumed to be `~astropy.units.kpc`. Must be greater than zero. n : float Sersic index. Must be greater than zero. theta : float or `~astropy.units.Quantity` Rotation angle, counterclockwise from the positive x-axis. If a float is given, the units are assumed to be `degree`. ellip : float Ellipticity. distance : float or `~astropy.units.Quantity` Distance to source. If float is given, the units are assumed to be `~astropy.units.Mpc`. xy_dim : list-like Dimensions of the mock image in xy coordinates. If int is given, will make the x and y dimensions the same. pixel_scale : float or `~astropy.units.Quantity`, optional The pixel scale of the mock image. If a float is given, the units will be assumed to be `~astropy.units.arcsec` per `~astropy.units.pixels`. num_r_eff : float, optional Number of r_eff to sample positions within. This parameter is needed because the current Sersic sampling function samples from within a discrete grid. Default is 10. random_state : `None`, int, list of ints, or `~numpy.random.RandomState` If `None`, return the `~numpy.random.RandomState` singleton used by ``numpy.random``. If `int`, return a new `~numpy.random.RandomState` instance seeded with the `int`. If `~numpy.random.RandomState`, return it. Otherwise raise ``ValueError``. Returns ------- xy : `~numpy.ma.MaskedArray` Masked numpy array of xy positions. Positions that fall outside the mock image are masked. """ if n <= 0: raise Exception('Sersic index n must be greater than zero.') xy_dim = check_xy_dim(xy_dim) r_eff = check_units(r_eff, 'kpc').to('Mpc').value theta = check_units(theta, 'deg').to('radian').value distance = check_units(distance, 'Mpc').to('Mpc').value pixel_scale = check_units(pixel_scale, u.arcsec / u.pixel) if r_eff <= 0: raise Exception('Effective radius must be greater than zero.') r_pix = np.arctan2(r_eff, distance) * u.radian.to('arcsec') * u.arcsec r_pix = r_pix.to('pixel', u.pixel_scale(pixel_scale)).value sample_dim = 2 * np.ceil(r_pix * num_r_eff).astype(int) + 1 x_0, y_0 = sample_dim // 2, sample_dim // 2 model = Sersic2D(x_0=x_0, y_0=y_0, amplitude=1, r_eff=r_pix, n=n, ellip=ellip, theta=theta) xy = xy_from_grid(num_stars, model, xy_dim, sample_dim, random_state=random_state) return xy
def sersic_profile(r_eff=100, n=4, ellipticity=0.5, angle=30, normalization="total", width=1024, height=1024, x_offset=0, y_offset=0, oversample=1): """ Returns a 2D array with a normailised sersic profile Parameters ---------- r_eff : float [pixel] Effective (half-light) radius n : float Power law index. - n=1 for exponential (spiral), - n=4 for de Vaucouleurs (elliptical) ellipticity : float Ellipticity is defined as (a - b)/a. Default = 0.5 angle : float [deg] Default = 30. Rotation anti-clockwise from the x-axis normalization : str, optional ["half-light", "centre", "total"] Where the profile equals unity If normalization equals: - "half-light" : the pixels at the half-light radius are set to 1 - "centre" : the maximum values are set to 1 - "total" : the image sums to 1 width, height : int [pixel] Dimensions of the image x_offset, y_offset : float [pixel] The distance between the centre of the profile and the centre of the image oversample : int Factor of oversampling, default factor = 1. If > 1, the model is discretized by taking the average of an oversampled grid. Returns ------- img : 2D array Notes ----- Most units are in [pixel] in this function. This differs from :func:`.galaxy` where parameter units are in [arcsec] or [pc] """ from astropy.modeling.models import Sersic2D # Silently cast to integer os_factor = np.int(oversample) if os_factor <= 0: raise ValueError("Oversampling factor must be >=1.") width_os = os_factor * width height_os = os_factor * height x, y = np.meshgrid(np.arange(width_os), np.arange(height_os)) dx = 0.5 * width_os + x_offset * os_factor dy = 0.5 * height_os + y_offset * os_factor r_eff_os = r_eff * os_factor mod = Sersic2D(amplitude=1, r_eff=r_eff_os, n=n, x_0=dx, y_0=dy, ellip=ellipticity, theta=np.deg2rad(angle)) img_os = mod(x, y) # Rebin os_factord image img = _rebin(img_os, os_factor) if "cen" in normalization.lower(): img /= np.max(img) elif "tot" in normalization.lower(): img /= np.sum(img) return img
def sim_galaxy(patch_size, pixel_size, gal_type=None, gal_params=None, duet=None, band=None, duet_no=None): ''' Return 2D array of a Sersic profile to simulate a galaxy Required inputs: patch_size = Axis sizes of returned galaxy patch in pixels (15,15) pixel_size = Angular size of pixel (6 * ur.arcsec) Optional inputs: gal_type = String that loads a pre-built 'average' galaxy or allows custom definition gal_params = Dictionary of parameters for Sersic model: ... duet = Telescope instance band = duet.bandpass (defaults to DUET1; deprecated) duet_no = integer (1 or 2) for DUET bandpass ''' from astropy.modeling.models import Sersic2D from astroduet.utils import duet_abmag_to_fluence, duet_no_from_band if duet is None: duet = Telescope() if band is None: band = duet.bandpass1 if duet_no is None: duet_no = duet_no_from_band(band) x = np.linspace(-(patch_size[0] // 2), patch_size[0] // 2, patch_size[0]) y = np.linspace(-(patch_size[1] // 2), patch_size[1] // 2, patch_size[1]) x, y = np.meshgrid(x, y) # Takes either a keyword or Sersic profile parameters # Typical galaxy parameters based on Bai et al. (2013) # Hardcoded for now, to-do: take distance as an input if gal_type == 'spiral': # A typical spiral galaxy at 100 Mpc surface_mag = 26.2 * u.ABmag # surface brightness (per arcsec**2) surface_rate = duet.fluence_to_rate( duet_abmag_to_fluence(surface_mag, duet_no, duet=duet)) # surface count rate at r_eff amplitude = surface_rate * pixel_size.value**2 # surface brightness (per pixel) r_eff = 16.5 / pixel_size.value n = 1 theta = 0 ellip = 0.5 x_0, y_0 = r_eff, 0 elif gal_type == 'elliptical': # A typical elliptical galaxy at 100 Mpc surface_mag = 25.0 * u.ABmag surface_rate = duet.fluence_to_rate( duet_abmag_to_fluence(surface_mag, duet_no, duet=duet)) # surface count rate at r_eff amplitude = surface_rate * pixel_size.value**2 # surface brightness (per pixel) r_eff = 12.5 / pixel_size.value n = 4 theta = 0 ellip = 0.5 x_0, y_0 = r_eff, 0 elif gal_type == 'dwarf': # A typical dwarf galaxy at 10 Mpc surface_mag = 25.8 * u.ABmag surface_rate = duet.fluence_to_rate( duet_abmag_to_fluence(surface_mag, duet_no, duet=duet)) # surface count rate at r_eff amplitude = surface_rate * pixel_size.value**2 # surface brightness (per pixel) r_eff = 70 / pixel_size r_eff = r_eff.value n = 4 theta = 0 ellip = 0.5 x_0, y_0 = r_eff, 0 elif (gal_type == 'custom') | (gal_type == None): # Get args from gal_params, default to spiral values surface_mag = gal_params.get('magnitude', 26) * u.ABmag surface_rate = duet.fluence_to_rate( duet_abmag_to_fluence(surface_mag, duet_no, duet=duet)) # surface count rate at r_eff amplitude = surface_rate * pixel_size.value**2 # surface brightness (per pixel) r_eff = gal_params.get('r_eff', 16.5 / pixel_size.value) n = gal_params.get('n', 1) theta = gal_params.get('theta', 0) ellip = gal_params.get('ellip', 0.5) x_0 = gal_params.get('x_0', 16.5 / pixel_size.value) y_0 = gal_params.get('y_0', 0) mod = Sersic2D(amplitude=amplitude, r_eff=r_eff, n=n, x_0=x_0, y_0=y_0, ellip=ellip, theta=theta) gal = mod(x, y) return gal