def test_run_through_Telescope_methods(): duet = Telescope() duet_offax = Telescope(config='reduced_baseline') duet.info() duet.update_bandpass() duet.calc_radial_profile() duet.calc_psf_fwhm() duet.update_effarea() duet.fluence_to_rate(1 * u.ph / u.s / u.cm**2) duet.psf_model() duet.compute_psf_norms()
def construct_image(frame, exposure, duet=None, band=None, gal_type=None, gal_params=None, source=None, source_loc=None, sky_rate=None, n_exp=1, duet_no=None): """Construct a simualted image with an optional background galaxy and source. 1. Generate the empty image 2. Add galaxy (see sim_galaxy) 3. Add source (Poisson draw based on source*expossure) 4. Convolve with PSF 5. Rebin to the DUET pixel size. 6. Add in expected background rates per pixel and dark current. 7. Draw Poisson values and add read noise. Parameters ---------- frame : ``numpy.array`` Number of pixel along x and y axis. i.e., frame = np.array([30, 30]) exposure : ``astropy.units.Quantity`` Exposure time used for the light curve Other parameters ---------------- duet : ``astroduet.config.Telescope`` If None, a default one is created band : DUET bandpass (deprecated in favor of duet_no; defaults to DUET1) gal_type : string Default galaxy string ("spiral"/"elliptical") or "custom" w/ Sersic parameters in gal_params gal_params : dict Dictionary of parameters for Sersic model (see sim_galaxy) source : ``astropy.units.Quantity`` Source photon rate in ph / s; can be array for multiple sources source_loc : ``numpy.array`` Coordinates of source(s) relative to frame (values between 0 and 1). If source is an array, source_loc must be the same length. format: np.array([[X1,X2,X3,...,Xn],[Y1,Y2,Y3,...,Yn]]) sky_rate : ``astropy.units.Quantity`` Background photon rate in ph / s / pixel n_exp : int Number of simualted frames to co-add. NB: I don't like this here! duet_no : int (1 or 2) DUET band number (defaults to DUET1) Returns ------- image : array with astropy.units NxM image array with integer number of counts observed per pixel. """ from astroduet.utils import duet_no_from_band assert type( frame ) is np.ndarray, 'construct_image: Please enter frame as a numpy array' # Load telescope parameters: 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) read_noise = duet.read_noise oversample = 6 pixel_size_init = duet.pixel / oversample # Load the PSF kernel. Note that this does NOT HAVE POINTING JITTER! psf_kernel = duet.psf_model(pixel_size=pixel_size_init) # 1. Generate the empty image # Initialise an image, oversampled by the oversample parameter to begin with im_array = np.zeros(frame * oversample) * u.ph / u.s # 2. Add a galaxy? if gal_type is not None: # Get a patch with a simulated galaxy on it gal = sim_galaxy(frame * oversample, pixel_size_init, gal_type=gal_type, gal_params=gal_params, duet=duet, duet_no=duet_no) im_array += gal # 3. Add a source? if source is not None: # Place source as a delta function at the center of the frame if source_loc is None: im_array[im_array.shape[0] // 2 + 1, im_array.shape[1] // 2 + 1] += source # Otherwise place sources at given source locations in frame else: source_inv = np.array([ source_loc[1], source_loc[0] ]) # Invert axes because python is weird that way source_pix = (source_inv.transpose() * np.array(im_array.shape)).transpose().astype(int) im_array[tuple(source_pix)] += source # Result should now be (floats) expected number of photons per pixel per second # in the oversampled imae # 4. Convolve with the PSF im_psf = convolve_fft(im_array.value, psf_kernel) * im_array.unit # Convolve again, now with the pointing jitter (need to re-apply units here as it's lost in convolution) #im_psf = convolve(im_psf_temp, Gaussian2DKernel((duet.jitter_rms/pixel_size_init).value)) * im_array.unit # 5. Bin up the image by oversample parameter to the correct pixel size shape = (frame[0], oversample, frame[1], oversample) im_binned = im_psf.reshape(shape).sum(-1).sum(1) # 6. Add sky background (these are both given in ph / pix / s) if sky_rate is not None: # Add sky rate per pixel across the whole image im_binned += sky_rate # 6b: Add dark current: im_binned += duet.dark_current # Convert to expected counts -- TEMPORARY: .value transform photons in a number im_counts = (im_binned * exposure) # Co-add a number of separate exposures im_final = np.zeros(frame) for i in range(n_exp): # Apply Poisson noise and instrument read noise. Note that read noise here # is im_noise = np.random.poisson(im_counts.value) + \ np.random.normal(loc=0, scale=read_noise,size=im_counts.shape) im_noise = np.floor(im_noise) im_noise[im_noise < 0] = 0 # Add to the co-add im_final += im_noise # Return image return im_final * im_counts.unit