Esempio n. 1
0
def get_sky_TE(optical_system,
	plotit=True):

	" Sky thermal background photon flux in the J, H and K bands "

	detector = optical_system.detector
	telescope = optical_system.telescope
	cryostat = optical_system.cryostat
	sky = optical_system.sky

	I_sky = {
		'J' : 0.0,
		'H' : 0.0,
		'K' : 0.0
	}

	# Atmospheric properties	
	# eps_sky = get_sky_emissivity()

	for key in I_sky:
		wavelength_min = FILTER_BANDS_M[key][2]
		wavelength_max = FILTER_BANDS_M[key][3]
		I_sky[key] = etcutils.thermal_emission_intensity(
			T = sky.T, 
			wavelength_min = wavelength_min, 
			wavelength_max = wavelength_max, 
			Omega = optical_system.omega_px_sr, 
			A = telescope.A_collecting_m2, 
			eps = sky.eps,
			eta = detector.qe * telescope.tau * cryostat.Tr_win
			)

	if plotit:
		D = np.ones(1000)*detector.dark_current
		wavelengths = np.linspace(0.80, 2.5, 1000)*1e-6

		# Plotting
		mu.newfigure(1,1)
		plt.plot(wavelengths*1e6, D, 'g--', label=r'Dark current')
		# Imager mode
		for key in I_sky:
			plt.errorbar(FILTER_BANDS_M[key][0]*1e6, I_sky[key], 0, FILTER_BANDS_M[key][1]/2*1e6, fmt='o')
			plt.text(FILTER_BANDS_M[key][0]*1e6, I_sky[key]*5, key)

		plt.yscale('log')
		plt.axis('tight')
		plt.ylim(ymax=100*I_sky['K'],ymin=1e-5)
		plt.legend(loc='lower right')
		plt.xlabel(r'$\lambda$ ($\mu$m)')
		plt.ylabel(r'Count ($e^{-}$ s$^{-1}$ pixel$^{-1}$)')
		plt.title(r'Estimated count from sky thermal emission')
		mu.show_plot()

	return I_sky
Esempio n. 2
0
def convolve_psf(image, psf, 
	padFactor=1,
	plotit=False):
	"""
		 Convolve an input PSF with an input image. 
	"""

	# Padding the source image.
	height, width = image.shape
	pad_ud = height // padFactor // 2
	pad_lr = width // padFactor // 2
	
	# If the image dimensions are odd, need to ad an extra row/column of zeros.
	image_padded = np.pad(
		image, 
		((pad_ud,pad_ud + height % 2),
		(pad_lr,pad_lr + width % 2)), 
		mode='constant')
	conv_height = 2 * pad_ud + height + (height % 2)
	conv_width = 2 * pad_lr + width + (width % 2)

	# Convolving the kernel with the image.
	image_conv = np.ndarray((conv_height, conv_width))
	image_conv_cropped = np.ndarray((height, width))

	image_padded = np.pad(image, ((pad_ud,pad_ud + height % 2),(pad_lr,pad_lr + width % 2)), mode='constant')
	
	image_conv = fftwconvolve.fftconvolve(image_padded, psf, mode='same')

	image_conv_cropped = image_conv[pad_ud : height + pad_ud, pad_lr : width + pad_lr]		

	if plotit:
		mu.newfigure(2,2)
		plt.suptitle('Seeing-limiting image')
		plt.subplot(2,2,1)
		plt.imshow(image)
		mu.colorbar()
		plt.title('Input image')
		plt.subplot(2,2,2)
		plt.imshow(psf)
		mu.colorbar()
		plt.title('Kernel')
		plt.subplot(2,2,3)
		plt.imshow(image_conv)
		mu.colorbar()
		plt.title('Convolved image (padded)')
		plt.subplot(2,2,4)
		plt.imshow(image_conv_cropped)
		mu.colorbar()
		plt.title('Convolved image (original size)')
		mu.show_plot()

	return image_conv_cropped
Esempio n. 3
0
def plot_noise_sources(optical_system):
	"""
	Plot the empirical sky brightness, thermal sky emission, thermal telescope 
	emission and dark current as a function of wavelength_m 
	"""

	detector = optical_system.detector
	telescope = optical_system.telescope
	cryostat = optical_system.cryostat
	sky = optical_system.sky

	counts = {
		'H' : 0,
		'J' : 0,
		'K' : 0
	}
	counts['H'] = exposure_time_calc(band='H', t_exp=1, optical_system=optical_system)
	counts['J'] = exposure_time_calc(band='J', t_exp=1, optical_system=optical_system)
	counts['K'] = exposure_time_calc(band='K', t_exp=1, optical_system=optical_system)
	D = np.ones(1000)*detector.dark_current
	wavelengths = np.linspace(1.0, 2.5, 1000)*1e-6

	# Plotting
	mu.newfigure(1.5,1.5)
	plt.plot(wavelengths*1e6, D, 'g--', label=r'Dark current')
	plotColors = {
		'H' : 'orangered',
		'J' : 'darkorange',
		'K' : 'darkred'
	}
	for key in counts:
		if key == 'J':
			plt.errorbar(FILTER_BANDS_M[key][0]*1e6, counts[key]['gain-multiplied']['N_sky_emp'], 0, FILTER_BANDS_M[key][1]/2*1e6, fmt='o', ecolor=plotColors[key], mfc=plotColors[key], label='Empirical sky background')
			plt.errorbar(FILTER_BANDS_M[key][0]*1e6, counts[key]['gain-multiplied']['N_sky_thermal'], 0, FILTER_BANDS_M[key][1]/2*1e6, fmt='^', ecolor=plotColors[key], mfc=plotColors[key], label='Thermal sky background')
			plt.errorbar(FILTER_BANDS_M[key][0]*1e6, counts[key]['gain-multiplied']['N_tel'], 0, FILTER_BANDS_M[key][1]/2*1e6, fmt='*', ecolor=plotColors[key], mfc=plotColors[key], label='Thermal telescope background')
			plt.errorbar(FILTER_BANDS_M[key][0]*1e6, counts[key]['gain-multiplied']['N_tel'] + counts[key]['gain-multiplied']['N_sky_thermal'], 0, FILTER_BANDS_M[key][1]/2*1e6, fmt='x', ecolor=plotColors[key], mfc=plotColors[key], label='Thermal telescope + sky background')
		else:
			plt.errorbar(FILTER_BANDS_M[key][0]*1e6, counts[key]['gain-multiplied']['N_sky_emp'], 0, FILTER_BANDS_M[key][1]/2*1e6, fmt='o', ecolor=plotColors[key], mfc=plotColors[key])
			plt.errorbar(FILTER_BANDS_M[key][0]*1e6, counts[key]['gain-multiplied']['N_sky_thermal'], 0, FILTER_BANDS_M[key][1]/2*1e6, fmt='^', ecolor=plotColors[key], mfc=plotColors[key])
			plt.errorbar(FILTER_BANDS_M[key][0]*1e6, counts[key]['gain-multiplied']['N_tel'], 0, FILTER_BANDS_M[key][1]/2*1e6, fmt='*', ecolor=plotColors[key], mfc=plotColors[key])
			plt.errorbar(FILTER_BANDS_M[key][0]*1e6, counts[key]['gain-multiplied']['N_tel'] + counts[key]['gain-multiplied']['N_sky_thermal'], 0, FILTER_BANDS_M[key][1]/2*1e6, fmt='x', ecolor=plotColors[key], mfc=plotColors[key])

		plt.text(FILTER_BANDS_M[key][0]*1e6, counts[key]['gain-multiplied']['N_sky_emp']*5, key)

	plt.yscale('log')
	plt.axis('tight')
	plt.ylim(ymax=100*counts['K']['gain-multiplied']['N_tel'],ymin=1e-5)
	plt.legend(loc='lower right')
	plt.xlabel(r'$\lambda$ ($\mu$m)')
	plt.ylabel(r'Count ($e^{-}$ s$^{-1}$ pixel$^{-1}$)')
	plt.title(r'Expected background noise levels (gain-multiplied by %d)' % detector.gain)
	mu.show_plot()
Esempio n. 4
0
def get_sky_TE(optical_system, plotit=True):

    " Sky thermal background photon flux in the J, H and K bands "

    detector = optical_system.detector
    telescope = optical_system.telescope
    cryostat = optical_system.cryostat
    sky = optical_system.sky

    I_sky = {'J': 0.0, 'H': 0.0, 'K': 0.0}

    # Atmospheric properties
    # eps_sky = get_sky_emissivity()

    for key in I_sky:
        wavelength_min = FILTER_BANDS_M[key][2]
        wavelength_max = FILTER_BANDS_M[key][3]
        I_sky[key] = etcutils.thermal_emission_intensity(
            T=sky.T,
            wavelength_min=wavelength_min,
            wavelength_max=wavelength_max,
            Omega=optical_system.omega_px_sr,
            A=telescope.A_collecting_m2,
            eps=sky.eps,
            eta=detector.qe * telescope.tau * cryostat.Tr_win)

    if plotit:
        D = np.ones(1000) * detector.dark_current
        wavelengths = np.linspace(0.80, 2.5, 1000) * 1e-6

        # Plotting
        mu.newfigure(1, 1)
        plt.plot(wavelengths * 1e6, D, 'g--', label=r'Dark current')
        # Imager mode
        for key in I_sky:
            plt.errorbar(FILTER_BANDS_M[key][0] * 1e6,
                         I_sky[key],
                         0,
                         FILTER_BANDS_M[key][1] / 2 * 1e6,
                         fmt='o')
            plt.text(FILTER_BANDS_M[key][0] * 1e6, I_sky[key] * 5, key)

        plt.yscale('log')
        plt.axis('tight')
        plt.ylim(ymax=100 * I_sky['K'], ymin=1e-5)
        plt.legend(loc='lower right')
        plt.xlabel(r'$\lambda$ ($\mu$m)')
        plt.ylabel(r'Count ($e^{-}$ s$^{-1}$ pixel$^{-1}$)')
        plt.title(r'Estimated count from sky thermal emission')
        mu.show_plot()

    return I_sky
Esempio n. 5
0
def convolve_psf(image, psf, padFactor=1, plotit=False):
    """
		 Convolve an input PSF with an input image. 
	"""

    # Padding the source image.
    height, width = image.shape
    pad_ud = height // padFactor // 2
    pad_lr = width // padFactor // 2

    # If the image dimensions are odd, need to ad an extra row/column of zeros.
    image_padded = np.pad(image, ((pad_ud, pad_ud + height % 2),
                                  (pad_lr, pad_lr + width % 2)),
                          mode='constant')
    conv_height = 2 * pad_ud + height + (height % 2)
    conv_width = 2 * pad_lr + width + (width % 2)

    # Convolving the kernel with the image.
    image_conv = np.ndarray((conv_height, conv_width))
    image_conv_cropped = np.ndarray((height, width))

    image_padded = np.pad(image, ((pad_ud, pad_ud + height % 2),
                                  (pad_lr, pad_lr + width % 2)),
                          mode='constant')

    image_conv = fftwconvolve.fftconvolve(image_padded, psf, mode='same')

    image_conv_cropped = image_conv[pad_ud:height + pad_ud,
                                    pad_lr:width + pad_lr]

    if plotit:
        mu.newfigure(2, 2)
        plt.suptitle('Seeing-limiting image')
        plt.subplot(2, 2, 1)
        plt.imshow(image)
        mu.colorbar()
        plt.title('Input image')
        plt.subplot(2, 2, 2)
        plt.imshow(psf)
        mu.colorbar()
        plt.title('Kernel')
        plt.subplot(2, 2, 3)
        plt.imshow(image_conv)
        mu.colorbar()
        plt.title('Convolved image (padded)')
        plt.subplot(2, 2, 4)
        plt.imshow(image_conv_cropped)
        mu.colorbar()
        plt.title('Convolved image (original size)')
        mu.show_plot()

    return image_conv_cropped
Esempio n. 6
0
def field_star(psf,
               band,
               mag,
               optical_system,
               star_coords_as,
               final_sz,
               plate_scale_as_px,
               gain=1,
               magnitude_system='AB',
               plotit=False):
    """
		Returns an image of a star in a field with a specified position offset
		(specified w.r.t. the centre of the image).

		The returned image IS NOT gain-multiplied by default. Be careful!
	"""
    # Scale up to the correct magnitude
    star = psf * etcutils.surface_brightness_to_count_rate(
        mu=mag,
        A_tel=optical_system.telescope.A_collecting_m2,
        tau=optical_system.telescope.tau,
        qe=optical_system.detector.qe,
        gain=gain,
        magnitude_system=magnitude_system,
        band=band)

    # Pad the sides appropriately.
    star_coords_px = [int(x / plate_scale_as_px) for x in star_coords_as]
    pad_ud, pad_lr = (int((x - y) // 2) for x, y in zip(final_sz, psf.shape))
    star_padded = np.pad(array=star,
                         pad_width=((pad_ud + star_coords_px[0],
                                     pad_ud - star_coords_px[0]),
                                    (pad_lr + star_coords_px[1],
                                     pad_lr - star_coords_px[1])),
                         mode='constant')

    if plotit:
        mu.newfigure(1, 2)
        plt.suptitle("Field star image")
        mu.astroimshow(im=psf,
                       plate_scale_as_px=plate_scale_as_px,
                       title="PSF",
                       subplot=121)
        mu.astroimshow(im=star_padded,
                       plate_scale_as_px=plate_scale_as_px,
                       title='Moved to coordinates ({:.2f}",{:.2f}")'.format(
                           star_coords_as[0], star_coords_as[1]),
                       subplot=122)
        mu.show_plot()

    return star_padded
Esempio n. 7
0
def field_star(psf, band, mag, optical_system, star_coords_as, final_sz, plate_scale_as_px,
	gain = 1,
	magnitude_system = 'AB',
	plotit = False
	):
	"""
		Returns an image of a star in a field with a specified position offset
		(specified w.r.t. the centre of the image).

		The returned image IS NOT gain-multiplied by default. Be careful!
	"""
	# Scale up to the correct magnitude
	star = psf * etcutils.surface_brightness_to_count_rate(
		mu = mag, 
		A_tel = optical_system.telescope.A_collecting_m2, 
		tau = optical_system.telescope.tau,
		qe = optical_system.detector.qe,
		gain = gain,
		magnitude_system = magnitude_system,
		band = band)

	# Pad the sides appropriately.
	star_coords_px = [int(x/plate_scale_as_px) for x in star_coords_as]
	pad_ud, pad_lr = ( int((x - y) // 2) for x, y in zip(final_sz, psf.shape) )
	star_padded = np.pad(
		array = star, 
		pad_width = (
			(pad_ud + star_coords_px[0], pad_ud - star_coords_px[0]), 
			(pad_lr + star_coords_px[1], pad_lr - star_coords_px[1])
			),
		mode='constant')

	if plotit:
		mu.newfigure(1,2)
		plt.suptitle("Field star image")
		mu.astroimshow(
			im = psf, 
			plate_scale_as_px = plate_scale_as_px, 
			title="PSF", 
			subplot=121)
		mu.astroimshow(
			im = star_padded, 
			plate_scale_as_px = plate_scale_as_px, 
			title='Moved to coordinates ({:.2f}",{:.2f}")'.format(
				star_coords_as[0], 
				star_coords_as[1]), 
			subplot=122)
		mu.show_plot()

	return star_padded
Esempio n. 8
0
def plot_alignment_err_histogram(errs_as, li_method=''):
    x_errs_as = errs_as[:, 0]
    y_errs_as = errs_as[:, 1]
    # Plot a pretty histogram showing the distribution of the alignment errors, and fit a Gaussian to them.
    range_as = 2 * max(max(np.abs(y_errs_as)), max(np.abs(x_errs_as)))
    nbins = int(errs_as.shape[0] / 100)
    mu.newfigure(1.5, 1)
    plt.suptitle(
        '{} Lucky Imaging shifting-and-stacking alignment errors'.format(
            li_method))

    plt.subplot(211)
    if nbins > 5:
        plt.hist(x_errs_as,
                 bins=nbins,
                 range=(-range_as / 2, +range_as / 2),
                 normed=True)
    else:
        plt.hist(x_errs_as, range=(-range_as / 2, +range_as / 2), normed=True)
    mean_x = np.mean(x_errs_as)
    sigma_x = np.sqrt(np.var(x_errs_as))
    x = np.linspace(-range_as / 2, range_as / 2, 100)
    plt.plot(x,
             normpdf(x, mean_x, sigma_x),
             'r',
             label=r'$\sigma_x$ = %.4f"' % (sigma_x))
    plt.title(r'$x$ alignment error')
    plt.xlabel('arcsec')
    plt.legend()

    plt.subplot(212)
    if nbins > 5:
        plt.hist(y_errs_as,
                 bins=nbins,
                 range=(-range_as / 2, +range_as / 2),
                 normed=True)
    else:
        plt.hist(y_errs_as, range=(-range_as / 2, +range_as / 2), normed=True)
    mean_y = np.mean(y_errs_as)
    sigma_y = np.sqrt(np.var(y_errs_as))
    y = np.linspace(-range_as / 2, range_as / 2, 100)
    plt.plot(y,
             normpdf(y, mean_y, sigma_y),
             'r',
             label=r'$\sigma_y$ = %.4f"' % (sigma_y))
    plt.title(r'$y$ alignment error')
    plt.xlabel('arcsec')
    plt.legend()
    mu.show_plot()
Esempio n. 9
0
def sersic_2D(n, R_e, mu_e,
	theta_rad = 0,		# Angle between major axis and detector horizontal (radians)
	i_rad = 0,			# Inclination angle (radians; face-on corresponds to i = 0)
	R_max = None,		# Plotting limit. Default is 20 * R_e
	R_trunc = np.inf, 	# Disc truncation radius
	gridsize = 500,		# Number of returned grid points
	zeropoint = 0,
	wavelength_m = None,
	plotit = False,
	R_units = 'kpc'
	):	
	" Returns 2D Sersic intensity and surface brightness plots. "
	if R_max == None:
		R_max = 20 * R_e

	# Making a 2D intensity plot of the galaxy given its inclination and orientation.
	dR = 2 * R_max / gridsize
	scaleFactor = 2
	imsize = gridsize*scaleFactor
	r = np.linspace(-R_max*scaleFactor, +R_max*scaleFactor, imsize)
	X, Y = np.meshgrid(r, r)
	if theta_rad != 0:
		print('WARNING: rotations are still kinda dodgy. Proceed with caution!')
	R = imutils.rotateAndCrop(image_in_array = np.sqrt(X * X + Y * Y / (np.cos(i_rad) * np.cos(i_rad))), angle=theta_rad * 180 / np.pi, cropArg=(imsize-gridsize)//2)
	# Calculating the Sersic flux and surface brightness profiles
	R, mu_map, F_map = sersic(n=n, R_e=R_e, R=R, mu_e=mu_e, zeropoint=zeropoint, wavelength_m=wavelength_m)
	# Truncating the profiles
	mu_map[R>R_trunc] = np.inf
	for key in F_map:
		F_map[key][R>R_trunc] = 0

	if plotit:
		mu.newfigure(2,1)
		plt.subplot(1,2,1)
		plt.imshow(F_map['F_nu_cgs'], norm=LogNorm(), extent = [-dR*gridsize/2,dR*gridsize/2,-dR*gridsize/2,dR*gridsize/2])
		plt.xlabel(r'$R$ (%s)' % R_units)
		plt.ylabel(r'$R$ (%s)' % R_units)
		mu.colorbar()
		plt.title('2D intensity map')
		plt.subplot(1,2,2)
		plt.imshow(mu_map, extent = [-dR*gridsize/2,dR*gridsize/2,-dR*gridsize/2,dR*gridsize/2])
		plt.xlabel(r'$R$ (%s)' % R_units)
		plt.ylabel(r'$R$ (%s)' % R_units)
		mu.colorbar()
		plt.title('2D surface brightness map')
		plt.suptitle('2D Sersic profiles')
		mu.show_plot()

	return R, dR, F_map, mu_map
Esempio n. 10
0
def image_from_fits(fname, plotit=False, idx=0):
    " Return an array of the image(s) stored in the FITS file fname. "
    if not fname.lower().endswith('fits'):
        fname += '.fits'
    hdulist = astropy.io.fits.open(fname)
    images_raw = hdulist[idx].data
    hdulist.close()

    images_raw, N, height, width = get_image_size(images_raw)
    if plotit:
        for k in range(N):
            plt.imshow(images_raw[k])
            plt.title('Raw image %d from FITS file' % (k + 1))
            plt.pause(0.1)
        mu.show_plot()

    return np.squeeze(images_raw), hdulist
Esempio n. 11
0
def image_from_fits(fname, 
	plotit=False,
	idx=0):
	" Return an array of the image(s) stored in the FITS file fname. "
	if not fname.lower().endswith('fits'):
		fname += '.fits'
	hdulist = astropy.io.fits.open(fname)
	images_raw = hdulist[idx].data
	hdulist.close()

	images_raw, N, height, width = get_image_size(images_raw)
	if plotit:
		for k in range(N):
			plt.imshow(images_raw[k])
			plt.title('Raw image %d from FITS file' % (k + 1))
			plt.pause(0.1)
		mu.show_plot()

	return np.squeeze(images_raw), hdulist
Esempio n. 12
0
def plot_alignment_err_histogram(errs_as,
	li_method=''):
	x_errs_as = errs_as[:,0]
	y_errs_as = errs_as[:,1]
	# Plot a pretty histogram showing the distribution of the alignment errors, and fit a Gaussian to them.
	range_as = 2 * max(max(np.abs(y_errs_as)), max(np.abs(x_errs_as)))
	nbins = int(errs_as.shape[0] / 100)
	mu.newfigure(1.5,1)
	plt.suptitle('{} Lucky Imaging shifting-and-stacking alignment errors'.format(li_method))

	plt.subplot(211)
	if nbins > 5:
		plt.hist(x_errs_as, bins=nbins, range=(-range_as/2,+range_as/2), normed=True)
	else:
		plt.hist(x_errs_as, range=(-range_as/2,+range_as/2), normed=True)
	mean_x = np.mean(x_errs_as)
	sigma_x = np.sqrt(np.var(x_errs_as))
	x = np.linspace(-range_as/2, range_as/2, 100)
	plt.plot(x, normpdf(x,mean_x,sigma_x), 'r', label=r'$\sigma_x$ = %.4f"' % (sigma_x))
	plt.title(r'$x$ alignment error')
	plt.xlabel('arcsec')
	plt.legend()

	plt.subplot(212)
	if nbins > 5:
		plt.hist(y_errs_as, bins=nbins, range=(-range_as/2,+range_as/2), normed=True)
	else:
		plt.hist(y_errs_as, range=(-range_as/2,+range_as/2), normed=True)
	mean_y = np.mean(y_errs_as)
	sigma_y = np.sqrt(np.var(y_errs_as))
	y = np.linspace(-range_as/2, range_as/2, 100)
	plt.plot(y, normpdf(y,mean_y,sigma_y), 'r', label=r'$\sigma_y$ = %.4f"' % (sigma_y))
	plt.title(r'$y$ alignment error')
	plt.xlabel('arcsec')
	plt.legend()	
	mu.show_plot()
Esempio n. 13
0
def get_telescope_TE(optical_system,
	plotit=True):

	detector = optical_system.detector
	telescope = optical_system.telescope
	cryostat = optical_system.cryostat
	sky = optical_system.sky

	I_tel = {
		'J' : 0.0,
		'H' : 0.0,
		'K' : 0.0
	}

	for key in I_tel:
		wavelength_min = FILTER_BANDS_M[key][2]
		wavelength_max = FILTER_BANDS_M[key][3]
			
		# Mirrors
		# Assumptions:
		#	1. The area we use for the etendue is the collecting (i.e. reflective) area of the telescope, not the total area.
		#	2. For now we are ignoring the baffle on M2.
		#	3. We are not assuming the worst case for the spider (i.e. it is still substantially reflective). But you should see how substantial of a difference it makes. Always lean towards the worst-case. 
		I_mirrors = 0
		for mirror in telescope.mirrors:
			I_mirrors += etcutils.thermal_emission_intensity(
				T = telescope.T, 
				wavelength_min = wavelength_min, 
				wavelength_max = wavelength_max, 
				Omega = optical_system.omega_px_sr, 
				A = telescope.A_collecting_m2, 
				eps = mirror.eps_eff,
				eta = detector.qe * cryostat.Tr_win
				)
		
		# Spider 
		if telescope.has_spider:
			I_spider = etcutils.thermal_emission_intensity(
					T = telescope.T, 
					wavelength_min = wavelength_min, 
					wavelength_max = wavelength_max, 
					Omega = optical_system.omega_px_sr, 
					A = telescope.A_collecting_m2, 
					eps = telescope.eps_spider_eff,
					eta = telescope.tau * detector.qe * cryostat.Tr_win)\
			  + etcutils.thermal_emission_intensity(
			  		T = sky.T, 	
			  		wavelength_min = wavelength_min, 
			  		wavelength_max = wavelength_max, 
			  		Omega = optical_system.omega_px_sr, 
			  		A = telescope.A_collecting_m2, 
			  		eps = lambda wavelength_m : (1 - telescope.eps_spider_eff) * sky.eps(wavelength_m),
			  		eta = telescope.tau * detector.qe * cryostat.Tr_win)
		
		# Cryostat window 
		I_window = etcutils.thermal_emission_intensity(
			T = cryostat.T, 
			wavelength_min = wavelength_min, 
			wavelength_max = wavelength_max, 
			Omega = optical_system.omega_px_sr, 
			A = telescope.A_collecting_m2, 
			eps = cryostat.eps_win,
			eta = detector.qe	# No cryostat window or telescope throughput terms because the radiation from the walls doesn't pass through it
			)

		I_tel[key] = I_mirrors + I_spider + I_window

	if plotit:
		D = np.ones(1000)*detector.dark_current
		wavelengths = np.linspace(0.80, 2.5, 1000)*1e-6

		# Plotting
		mu.newfigure(1,1)
		plt.plot(wavelengths*1e6, D, 'g--', label=r'Dark current')

		for key in I_tel:
			plt.errorbar(FILTER_BANDS_M[key][0]*1e6, I_tel[key], 0, FILTER_BANDS_M[key][1]/2*1e6, fmt='o')
			plt.text(FILTER_BANDS_M[key][0]*1e6, I_tel[key]*5, key)

		plt.yscale('log')
		plt.axis('tight')
		plt.ylim(ymax=100*I_tel['K'],ymin=1e-5)
		plt.legend(loc='lower right')
		plt.xlabel(r'$\lambda$ ($\mu$m)')
		plt.ylabel(r'Count ($e^{-}$ s$^{-1}$ pixel$^{-1}$)')
		plt.title(r'Estimated count from telescope thermal emission')
		mu.show_plot()

	return I_tel
Esempio n. 14
0
def get_seeing_limited_image(images, seeing_diameter_as, 
	plate_scale_as=1,
	padFactor=1,
	plotit=False):
	"""
		 Convolve a Gaussian PSF with an input image to simulate seeing with a FWHM of seeing_diameter_as. 
	"""
	print("Seeing-limiting image(s)",end="")

	images, N, height, width = get_image_size(images)

	# Padding the source image.
	pad_ud = height // padFactor // 2
	pad_lr = width // padFactor // 2
	
	# If the image dimensions are odd, need to ad an extra row/column of zeros.
	image_padded = np.pad(images[0], ((pad_ud,pad_ud + height % 2),(pad_lr,pad_lr + width % 2)), mode='constant')
	# conv_height = image_padded.shape[0]
	# conv_width = image_padded.shape[1]
	conv_height = 2 * pad_ud + height + (height % 2)
	conv_width = 2 * pad_lr + width + (width % 2)

	# Generate a Gaussian kernel.
	kernel = np.zeros((conv_height, conv_width))
	y_as = np.arange(-conv_width//2, +conv_width//2 + conv_width%2, 1) * plate_scale_as
	x_as = np.arange(-conv_height//2, +conv_height//2 + conv_height%2, 1) * plate_scale_as
	X, Y = np.meshgrid(x_as, y_as)
	sigma = seeing_diameter_as / (2 * np.sqrt(2 * np.log(2)))
	kernel = np.exp(-(np.power(X, 2) + np.power(Y, 2)) / (2 * np.power(sigma,2)))
	kernel /= sum(kernel.flatten())
	kernel = np.pad(kernel, ((pad_ud, pad_ud + height % 2), (pad_lr, pad_lr + width % 2)), mode='constant')

	# Convolving the kernel with the image.
	image_seeing_limited = np.ndarray((N, conv_height, conv_width))
	image_seeing_limited_cropped = np.ndarray((N, height, width))

	for k in range(N):
		print('.',end="")
		image_padded = np.pad(images[k], ((pad_ud,pad_ud + height % 2),(pad_lr,pad_lr + width % 2)), mode='constant')
		image_seeing_limited[k] = fftwconvolve.fftconvolve(image_padded, kernel, mode='same')
		image_seeing_limited_cropped[k] = image_seeing_limited[k,pad_ud : height + pad_ud, pad_lr : width + pad_lr]		

	if plotit:
		mu.newfigure(2,2)
		plt.suptitle('Seeing-limiting image')
		plt.subplot(2,2,1)
		plt.imshow(images[0])
		mu.colorbar()
		plt.title('Input image')
		plt.subplot(2,2,2)
		plt.imshow(kernel, extent=axes_kernel)
		mu.colorbar()
		plt.title('Kernel')
		plt.subplot(2,2,3)
		plt.imshow(image_seeing_limited[0])
		mu.colorbar()
		plt.title('Convolved image')
		plt.subplot(2,2,4)
		plt.imshow(image_seeing_limited_cropped[0])
		mu.colorbar()
		plt.title('Cropped, convolved image')
		mu.show_plot()

	return np.squeeze(image_seeing_limited_cropped)
Esempio n. 15
0
def get_diffraction_limited_image(image_truth, l_px_m, f_ratio, wavelength_m, 
	f_ratio_in=None, wavelength_in_m=None, # f-ratio and imaging wavelength of the input image (if it has N_os > 1)
	N_OS_psf=4,
	detector_size_px=None,
	plotit=False):
	""" Convolve the PSF of a given telescope at a given wavelength with image_truth to simulate diffraction-limited imaging. 
	It is assumed that the truth image has the appropriate plate scale of, but may be larger than, the detector. 
	If the detector size is not given, then it is assumed that the input image and detector have the same dimensions. 

	The flow should really be like this:
		1. Generate the PSF with N_OS = 4, say.
		2. Rescale the image to achieve the same plate scale.
		3. Convolve.
		4. Resample back down to the original plate scale.

	"""
	print("Diffraction-limiting truth image(s)...")
	image_truth, N, height, width = imutils.get_image_size(image_truth)

	# If the input image is already sampled by N_os > 1, then the PSF that we convolve with the image needs to add in quadrature with the PSF that has already been convolved with the image to get to the scaling we want.
	if f_ratio_in != None and wavelength_in_m != None:
		# Then we need to add the PSFs in quadrature.
		f_ratio_out = f_ratio
		wavelength_out_m = wavelength_m

		efl = 1
		D_in = efl / f_ratio_in
		D_out = efl / f_ratio_out
		FWHM_in = wavelength_in_m / D_in
		FWHM_out = wavelength_out_m / D_out
		FWHM_prime = np.sqrt(FWHM_out**2 - FWHM_in**2)

		wavelength_prime_m = wavelength_in_m
		D_prime = wavelength_prime_m / FWHM_prime
		f_ratio_prime = efl / D_prime

		f_ratio = f_ratio_prime
		wavelength_m = wavelength_prime_m

	# Because we specify the PSF in terms of Nyquist sampling, we need to express N_OS in terms of the f ratio and wavelength of the input image.
	N_OS_input = wavelength_m * f_ratio / 2 / l_px_m / (np.deg2rad(206265 / 3600))

	# Calculating the PSF
	psf = psf_airy_disk_kernel(wavelength_m=wavelength_m, N_OS=N_OS_psf, l_px_m=l_px_m)
	# TODO need to check that the PSF is not larger than image_truth_large

	# Convolving the PSF and the truth image to obtain the simulated diffraction-limited image
	# image_difflim = np.ndarray((N, height, width))
	for k in range(N):
		# Resample the image up to the appropriate plate scale.
		image_truth_large = resizeImagesToDetector(image_truth[k], 1/N_OS_input, 1/N_OS_psf)
		# Convolve with the PSF.
		image_difflim_large = fftwconvolve.fftconvolve(image_truth_large, psf, mode='same')
		# Resize the image to its original plate scale.
		if k == 0:
			im = resizeImagesToDetector(image_difflim_large, 1/N_OS_psf, 1/N_OS_input)
			image_difflim = np.ndarray((N, im.shape[0], im.shape[1]))
			image_difflim[0] = im
		else:
			image_difflim[k] = resizeImagesToDetector(image_difflim_large, 1/N_OS_psf, 1/N_OS_input)


	if plotit:
		mu.newfigure(1,3)
		plt.subplot(1,3,1)
		plt.imshow(psf)
		mu.colorbar()
		plt.title('Diffraction-limited PSF of telescope')
		plt.subplot(1,3,2)
		plt.imshow(image_truth[0])
		mu.colorbar()
		plt.title('Truth image')
		plt.subplot(1,3,3)
		plt.imshow(image_difflim[0])
		mu.colorbar()
		plt.title('Diffraction-limited image')
		plt.suptitle('Diffraction-limiting image')
		mu.show_plot()

	return np.squeeze(image_difflim)
Esempio n. 16
0
def airy_disc(wavelength_m, f_ratio, l_px_m, 
	detector_size_px=None,
	trapz_oversampling=8,	# Oversampling used in the trapezoidal rule approximation.
	coords=None,
	P_0=1,
	plotit=False):
	"""
		Returns the PSF of an optical system with a circular aperture given the f ratio, pixel and detector size at a given wavelength_m.

		If desired, an offset (measured from the top left corner of the detector) can be specified in vector coords = (x, y).

		The PSF is normalised such that the sum of every pixel in the PSF (extended to infinity) is equal to P_0 (unity by default), where P_0 is the total energy incident upon the telescope aperture. 

		P_0 represents the *ideal* total energy in the airy disc (that is, the total energy incident upon the telescope aperture), whilst P_sum measures the actual total energy in the image (i.e. the pixel values). 
	"""

	# Output image size 
	detector_height_px, detector_width_px = detector_size_px[0:2]

	# Intensity map grid size
	# Oversampled image size
	oversampled_height_px = detector_height_px * trapz_oversampling
	oversampled_width_px = detector_width_px * trapz_oversampling
	# Coordinates of the centre of the Airy disc in the intensity map grid
	if coords == None:
		x_offset = oversampled_height_px/2
		y_offset = oversampled_width_px/2
	else:
		x_offset = coords[0] * trapz_oversampling
		y_offset = coords[1] * trapz_oversampling
	dx = oversampled_height_px/2 - x_offset
	dy = oversampled_width_px/2 - y_offset
	# Intensity map grid indices (in metres)
	x = np.arange(-oversampled_height_px//2, +oversampled_height_px//2 + oversampled_height_px%2 + 1, 1) + dx
	y = np.arange(-oversampled_width_px//2, +oversampled_width_px//2 + oversampled_width_px%2 + 1, 1) + dy
	x *= l_px_m / trapz_oversampling
	y *= l_px_m / trapz_oversampling
	Y, X = np.meshgrid(y, x)

	# Central intensity (W m^-2)
	I_0 = P_0 * np.pi / 4 / wavelength_m / wavelength_m / f_ratio / f_ratio

	# Calculating the Airy disc
	r = lambda x, y: np.pi / wavelength_m / f_ratio * np.sqrt(np.power(x,2) + np.power(y,2))
	I_fun = lambda x, y : np.power((2 * scipy.special.jv(1, r(x,y)) / r(x,y)), 2) * I_0 
	I = I_fun(X,Y)
	# I = np.swapaxes(I,0,1)
	nan_idx = np.where(np.isnan(I))
	if nan_idx[0].shape != (0,):
		I[nan_idx[0][0],nan_idx[1][0]] = I_0 # removing the NaN in the centre of the image if necessary

	""" Converting intensity values to count values in each pixel """
	# Approximation using top-hat intensity profile in each pixel
	count_approx = I * l_px_m**2 / trapz_oversampling**2
	count_approx = count_approx.astype(np.float64)

	# Approximation using trapezoidal rule
	count_cumtrapz = np.zeros((detector_height_px,detector_width_px))
	cumsum = 0
	for j in range(detector_width_px):
		for k in range(detector_height_px):
			px_grid = I[trapz_oversampling*k:trapz_oversampling*k+trapz_oversampling+1,trapz_oversampling*j:trapz_oversampling*j+trapz_oversampling+1]
			res1 = scipy.integrate.cumtrapz(px_grid, dx = l_px_m/trapz_oversampling, axis = 0, initial = 0)
			res2 = scipy.integrate.cumtrapz(res1[-1,:], dx = l_px_m/trapz_oversampling, initial = 0)
			count_cumtrapz[k,j] = res2[-1]
	# Total energy in image
	P_sum = sum(count_cumtrapz.flatten())
	count_cumtrapz /= P_sum

	if plotit:
		mu.newfigure(1,2)
		plt.subplot(1,2,1)
		plt.imshow(I, norm=LogNorm())
		mu.colorbar()
		plt.title('Intensity (oversampled by a factor of %d)' % trapz_oversampling)
		plt.subplot(1,2,2)
		plt.imshow(count_cumtrapz, norm=LogNorm())
		mu.colorbar()
		plt.title('Count (via trapezoidal rule)')
		mu.show_plot()

	return count_cumtrapz, I, P_0, P_sum, I_0
Esempio n. 17
0
def lucky_frame(
	im, 							# In electron counts/s.
	psf, 							# Normalised.
	scale_factor, 					
	t_exp, 
	final_sz,
	tt = np.array([0, 0]),
	im_star = None,					# In electron counts/s.					
	noise_frame_gain_multiplied = 0,		# Noise injected into the system that is multiplied up by the detector gain after conversion to counts via a Poisson distribution, e.g. sky background, emission from telescope, etc. Must have shape final_sz. It is assumed that this noise frame has already been multiplied up by the detector gain!
	noise_frame_post_gain = 0,		# Noise injected into the system after gain multiplication, e.g. read noise. Must have shape final_sz.
	gain = 1,						# Detector gain.
	detector_saturation=np.inf,		# Detector saturation.
	plate_scale_as_px_conv = 1,		# Only used for plotting.
	plate_scale_as_px = 1,			# Only used for plotting.
	plotit=False):
	""" 
		This function can be used to generate a short-exposure 'lucky' image that can be input to the Lucky Imaging algorithms.
			Input: 	one 'raw' countrate image of a galaxy; one PSF with which to convolve it (at the same plate scale)
			Output: a 'Lucky' exposure. 			
			Process: convolve with PSF --> resize to detector --> add tip and tilt (from a premade vector of tip/tilt values) --> convert to counts --> add noise --> subtract the master sky/dark current. 
	"""	
	# Convolve with PSF.
	im_raw = im
	im_convolved = obssim.convolve_psf(im_raw, psf)

	# Add a star to the field. We need to add the star at the convolution plate scale BEFORE we resize down because of the tip-tilt adding step!
	if is_numlike(im_star):
		if im_star.shape != im_convolved.shape:
			print("ERROR: the input image of the star MUST have the same size and plate scale as the image of the galaxy after convolution!")
			raise UserWarning
		im_convolved += im_star

	# Resize to detector (+ edge buffer).
	im_resized = imutils.fourier_resize(
		im = im_convolved,
		scale_factor = scale_factor,
		conserve_pixel_sum = True)

	# Add tip and tilt. To avoid edge effects, max(tt) should be less than or equal to the edge buffer.
	edge_buffer_px = (im.shape[0] - final_sz[0]) / 2
	if edge_buffer_px > 0 and max(tt) > edge_buffer_px:
		print("WARNING: the edge buffer is less than the supplied tip and tilt by a margin of {:.2f} pixels! Shifted image will be clipped.".format(np.abs(edge_buffer_px - max(tt))))
	im_tt = obssim.add_tt(image = im_resized, tt_idxs = tt)[0]	
	# Crop back down to the detector size.
	if edge_buffer_px > 0:
		im_tt = imutils.centre_crop(im_tt, final_sz)	
	# Convert to counts. Note that we apply the gain AFTER we convert to integer
	# counts.
	im_counts = etcutils.expected_count_to_count(im_tt, t_exp = t_exp) * gain
	# Add the pre-gain noise. Here, we assume that the noise frame has already 
	# been multiplied by the gain before being passed into this function.
	im_noisy = im_counts + noise_frame_gain_multiplied
	# Add the post-gain noise (i.e. read noise)
	im_noisy += noise_frame_post_gain
	# Account for detector saturation
	im_noisy = np.clip(im_noisy, a_min=0, a_max=detector_saturation)

	if plotit:
		plate_scale_as_px = plate_scale_as_px_conv * scale_factor
		# Plotting
		mu.newfigure(1,3)
		plt.suptitle('Convolving input image with PSF and resizing to detector')
		mu.astroimshow(im=im_raw, 
			title='Truth image (electrons/s)', 
			plate_scale_as_px = plate_scale_as_px_conv, 
			colorbar_on=True, 
			subplot=131)
		mu.astroimshow(im=psf, 
			title='Point spread function (normalised)', 
			plate_scale_as_px = plate_scale_as_px_conv, 
			colorbar_on=True, 
			subplot=132)
		# mu.astroimshow(im=im_convolved, 
		# 	title='Star added, convolved with PSF (electrons/s)', 
		# 	plate_scale_as_px = plate_scale_as_px_conv, 
		# 	colorbar_on=True, 
		# 	subplot=143)
		mu.astroimshow(im=im_resized, 
			title='Resized to detector plate scale (electrons/s)', 
			plate_scale_as_px=plate_scale_as_px, 
			colorbar_on=True, 
			subplot=133)

		# Zooming in on the galaxy
		# mu.newfigure(1,4)
		# plt.suptitle('Convolving input image with PSF and resizing to detector')
		# mu.astroimshow(im=imutils.centre_crop(im=im_raw, units='arcsec', plate_scale_as_px=plate_scale_as_px_conv, sz_final=(6, 6)), title='Raw input image (electrons/s)', plate_scale_as_px = plate_scale_as_px_conv, colorbar_on=True, subplot=141)
		# mu.astroimshow(im=psf, title='Point spread function (normalised)', plate_scale_as_px = plate_scale_as_px_conv, colorbar_on=True, subplot=142)
		# mu.astroimshow(im=imutils.centre_crop(im=im_convolved, units='arcsec', plate_scale_as_px=plate_scale_as_px_conv, sz_final=(6, 6)), title='Star added, convolved with PSF (electrons/s)', plate_scale_as_px = plate_scale_as_px_conv, colorbar_on=True, subplot=143)
		# mu.astroimshow(im=imutils.centre_crop(im=im_resized, units='arcsec', plate_scale_as_px=plate_scale_as_px, sz_final=(6, 6)), title='Resized to detector plate scale (electrons/s)', plate_scale_as_px=plate_scale_as_px, colorbar_on=True, subplot=144)

		mu.newfigure(1,3)
		plt.suptitle('Adding tip and tilt, converting to integer counts and adding noise')
		mu.astroimshow(im=im_tt, 
			title='Atmospheric tip and tilt added (electrons/s)', 
			plate_scale_as_px=plate_scale_as_px, 
			colorbar_on=True,
			subplot=131)
		mu.astroimshow(im=im_counts, 
			title=r'Converted to integer counts and gain-multiplied by %d (electrons)' % gain, 
			plate_scale_as_px=plate_scale_as_px, 
			colorbar_on=True, 
			subplot=132)
		mu.astroimshow(im=im_noisy, 
			title='Noise added (electrons)', 
			plate_scale_as_px=plate_scale_as_px, 
			colorbar_on=True, 
			subplot=133)

		# plt.subplot(1,4,4)
		plt.figure()
		x = np.linspace(-im_tt.shape[0]/2, +im_tt.shape[0]/2, im_tt.shape[0]) * plate_scale_as_px
		plt.plot(x, im_tt[:, im_tt.shape[1]/2], 'g', label='Electron count rate')
		plt.plot(x, im_counts[:, im_tt.shape[1]/2], 'b', label='Converted to integer counts ($t_{exp} = %.2f$ s)' % t_exp)
		plt.plot(x, im_noisy[:, im_tt.shape[1]/2], 'r', label='Noise added')
		plt.xlabel('arcsec')
		plt.ylabel('Pixel value (electrons)')
		plt.title('Linear profiles')
		plt.axis('tight')
		plt.legend(loc='lower left')
		mu.show_plot()

	return im_noisy
Esempio n. 18
0
def plot_noise_sources(optical_system):
    """
	Plot the empirical sky brightness, thermal sky emission, thermal telescope 
	emission and dark current as a function of wavelength_m 
	"""

    detector = optical_system.detector
    telescope = optical_system.telescope
    cryostat = optical_system.cryostat
    sky = optical_system.sky

    counts = {'H': 0, 'J': 0, 'K': 0}
    counts['H'] = exposure_time_calc(band='H',
                                     t_exp=1,
                                     optical_system=optical_system)
    counts['J'] = exposure_time_calc(band='J',
                                     t_exp=1,
                                     optical_system=optical_system)
    counts['K'] = exposure_time_calc(band='K',
                                     t_exp=1,
                                     optical_system=optical_system)
    D = np.ones(1000) * detector.dark_current
    wavelengths = np.linspace(1.0, 2.5, 1000) * 1e-6

    # Plotting
    mu.newfigure(1.5, 1.5)
    plt.plot(wavelengths * 1e6, D, 'g--', label=r'Dark current')
    plotColors = {'H': 'orangered', 'J': 'darkorange', 'K': 'darkred'}
    for key in counts:
        if key == 'J':
            plt.errorbar(FILTER_BANDS_M[key][0] * 1e6,
                         counts[key]['gain-multiplied']['N_sky_emp'],
                         0,
                         FILTER_BANDS_M[key][1] / 2 * 1e6,
                         fmt='o',
                         ecolor=plotColors[key],
                         mfc=plotColors[key],
                         label='Empirical sky background')
            plt.errorbar(FILTER_BANDS_M[key][0] * 1e6,
                         counts[key]['gain-multiplied']['N_sky_thermal'],
                         0,
                         FILTER_BANDS_M[key][1] / 2 * 1e6,
                         fmt='^',
                         ecolor=plotColors[key],
                         mfc=plotColors[key],
                         label='Thermal sky background')
            plt.errorbar(FILTER_BANDS_M[key][0] * 1e6,
                         counts[key]['gain-multiplied']['N_tel'],
                         0,
                         FILTER_BANDS_M[key][1] / 2 * 1e6,
                         fmt='*',
                         ecolor=plotColors[key],
                         mfc=plotColors[key],
                         label='Thermal telescope background')
            plt.errorbar(FILTER_BANDS_M[key][0] * 1e6,
                         counts[key]['gain-multiplied']['N_tel'] +
                         counts[key]['gain-multiplied']['N_sky_thermal'],
                         0,
                         FILTER_BANDS_M[key][1] / 2 * 1e6,
                         fmt='x',
                         ecolor=plotColors[key],
                         mfc=plotColors[key],
                         label='Thermal telescope + sky background')
        else:
            plt.errorbar(FILTER_BANDS_M[key][0] * 1e6,
                         counts[key]['gain-multiplied']['N_sky_emp'],
                         0,
                         FILTER_BANDS_M[key][1] / 2 * 1e6,
                         fmt='o',
                         ecolor=plotColors[key],
                         mfc=plotColors[key])
            plt.errorbar(FILTER_BANDS_M[key][0] * 1e6,
                         counts[key]['gain-multiplied']['N_sky_thermal'],
                         0,
                         FILTER_BANDS_M[key][1] / 2 * 1e6,
                         fmt='^',
                         ecolor=plotColors[key],
                         mfc=plotColors[key])
            plt.errorbar(FILTER_BANDS_M[key][0] * 1e6,
                         counts[key]['gain-multiplied']['N_tel'],
                         0,
                         FILTER_BANDS_M[key][1] / 2 * 1e6,
                         fmt='*',
                         ecolor=plotColors[key],
                         mfc=plotColors[key])
            plt.errorbar(FILTER_BANDS_M[key][0] * 1e6,
                         counts[key]['gain-multiplied']['N_tel'] +
                         counts[key]['gain-multiplied']['N_sky_thermal'],
                         0,
                         FILTER_BANDS_M[key][1] / 2 * 1e6,
                         fmt='x',
                         ecolor=plotColors[key],
                         mfc=plotColors[key])

        plt.text(FILTER_BANDS_M[key][0] * 1e6,
                 counts[key]['gain-multiplied']['N_sky_emp'] * 5, key)

    plt.yscale('log')
    plt.axis('tight')
    plt.ylim(ymax=100 * counts['K']['gain-multiplied']['N_tel'], ymin=1e-5)
    plt.legend(loc='lower right')
    plt.xlabel(r'$\lambda$ ($\mu$m)')
    plt.ylabel(r'Count ($e^{-}$ s$^{-1}$ pixel$^{-1}$)')
    plt.title(r'Expected background noise levels (gain-multiplied by %d)' %
              detector.gain)
    mu.show_plot()
Esempio n. 19
0
def get_telescope_TE(optical_system, plotit=True):

    detector = optical_system.detector
    telescope = optical_system.telescope
    cryostat = optical_system.cryostat
    sky = optical_system.sky

    I_tel = {'J': 0.0, 'H': 0.0, 'K': 0.0}

    for key in I_tel:
        wavelength_min = FILTER_BANDS_M[key][2]
        wavelength_max = FILTER_BANDS_M[key][3]

        # Mirrors
        # Assumptions:
        #	1. The area we use for the etendue is the collecting (i.e. reflective) area of the telescope, not the total area.
        #	2. For now we are ignoring the baffle on M2.
        #	3. We are not assuming the worst case for the spider (i.e. it is still substantially reflective). But you should see how substantial of a difference it makes. Always lean towards the worst-case.
        I_mirrors = 0
        for mirror in telescope.mirrors:
            I_mirrors += etcutils.thermal_emission_intensity(
                T=telescope.T,
                wavelength_min=wavelength_min,
                wavelength_max=wavelength_max,
                Omega=optical_system.omega_px_sr,
                A=telescope.A_collecting_m2,
                eps=mirror.eps_eff,
                eta=detector.qe * cryostat.Tr_win)

        # Spider
        if telescope.has_spider:
            I_spider = etcutils.thermal_emission_intensity(
              T = telescope.T,
              wavelength_min = wavelength_min,
              wavelength_max = wavelength_max,
              Omega = optical_system.omega_px_sr,
              A = telescope.A_collecting_m2,
              eps = telescope.eps_spider_eff,
              eta = telescope.tau * detector.qe * cryostat.Tr_win)\
              + etcutils.thermal_emission_intensity(
                T = sky.T,
                wavelength_min = wavelength_min,
                wavelength_max = wavelength_max,
                Omega = optical_system.omega_px_sr,
                A = telescope.A_collecting_m2,
                eps = lambda wavelength_m : (1 - telescope.eps_spider_eff) * sky.eps(wavelength_m),
                eta = telescope.tau * detector.qe * cryostat.Tr_win)

        # Cryostat window
        I_window = etcutils.thermal_emission_intensity(
            T=cryostat.T,
            wavelength_min=wavelength_min,
            wavelength_max=wavelength_max,
            Omega=optical_system.omega_px_sr,
            A=telescope.A_collecting_m2,
            eps=cryostat.eps_win,
            eta=detector.
            qe  # No cryostat window or telescope throughput terms because the radiation from the walls doesn't pass through it
        )

        I_tel[key] = I_mirrors + I_spider + I_window

    if plotit:
        D = np.ones(1000) * detector.dark_current
        wavelengths = np.linspace(0.80, 2.5, 1000) * 1e-6

        # Plotting
        mu.newfigure(1, 1)
        plt.plot(wavelengths * 1e6, D, 'g--', label=r'Dark current')

        for key in I_tel:
            plt.errorbar(FILTER_BANDS_M[key][0] * 1e6,
                         I_tel[key],
                         0,
                         FILTER_BANDS_M[key][1] / 2 * 1e6,
                         fmt='o')
            plt.text(FILTER_BANDS_M[key][0] * 1e6, I_tel[key] * 5, key)

        plt.yscale('log')
        plt.axis('tight')
        plt.ylim(ymax=100 * I_tel['K'], ymin=1e-5)
        plt.legend(loc='lower right')
        plt.xlabel(r'$\lambda$ ($\mu$m)')
        plt.ylabel(r'Count ($e^{-}$ s$^{-1}$ pixel$^{-1}$)')
        plt.title(r'Estimated count from telescope thermal emission')
        mu.show_plot()

    return I_tel
Esempio n. 20
0
def airy_disc(
        wavelength_m,
        f_ratio,
        l_px_m,
        detector_size_px=None,
        trapz_oversampling=8,  # Oversampling used in the trapezoidal rule approximation.
        coords=None,
        P_0=1,
        plotit=False):
    """
		Returns the PSF of an optical system with a circular aperture given the f ratio, pixel and detector size at a given wavelength_m.

		If desired, an offset (measured from the top left corner of the detector) can be specified in vector coords = (x, y).

		The PSF is normalised such that the sum of every pixel in the PSF (extended to infinity) is equal to P_0 (unity by default), where P_0 is the total energy incident upon the telescope aperture. 

		P_0 represents the *ideal* total energy in the airy disc (that is, the total energy incident upon the telescope aperture), whilst P_sum measures the actual total energy in the image (i.e. the pixel values). 
	"""

    # Output image size
    detector_height_px, detector_width_px = detector_size_px[0:2]

    # Intensity map grid size
    # Oversampled image size
    oversampled_height_px = detector_height_px * trapz_oversampling
    oversampled_width_px = detector_width_px * trapz_oversampling
    # Coordinates of the centre of the Airy disc in the intensity map grid
    if coords == None:
        x_offset = oversampled_height_px / 2
        y_offset = oversampled_width_px / 2
    else:
        x_offset = coords[0] * trapz_oversampling
        y_offset = coords[1] * trapz_oversampling
    dx = oversampled_height_px / 2 - x_offset
    dy = oversampled_width_px / 2 - y_offset
    # Intensity map grid indices (in metres)
    x = np.arange(-oversampled_height_px // 2, +oversampled_height_px // 2 +
                  oversampled_height_px % 2 + 1, 1) + dx
    y = np.arange(-oversampled_width_px // 2, +oversampled_width_px // 2 +
                  oversampled_width_px % 2 + 1, 1) + dy
    x *= l_px_m / trapz_oversampling
    y *= l_px_m / trapz_oversampling
    Y, X = np.meshgrid(y, x)

    # Central intensity (W m^-2)
    I_0 = P_0 * np.pi / 4 / wavelength_m / wavelength_m / f_ratio / f_ratio

    # Calculating the Airy disc
    r = lambda x, y: np.pi / wavelength_m / f_ratio * np.sqrt(
        np.power(x, 2) + np.power(y, 2))
    I_fun = lambda x, y: np.power(
        (2 * scipy.special.jv(1, r(x, y)) / r(x, y)), 2) * I_0
    I = I_fun(X, Y)
    # I = np.swapaxes(I,0,1)
    nan_idx = np.where(np.isnan(I))
    if nan_idx[0].shape != (0, ):
        I[nan_idx[0][0], nan_idx[1]
          [0]] = I_0  # removing the NaN in the centre of the image if necessary
    """ Converting intensity values to count values in each pixel """
    # Approximation using top-hat intensity profile in each pixel
    count_approx = I * l_px_m**2 / trapz_oversampling**2
    count_approx = count_approx.astype(np.float64)

    # Approximation using trapezoidal rule
    count_cumtrapz = np.zeros((detector_height_px, detector_width_px))
    cumsum = 0
    for j in range(detector_width_px):
        for k in range(detector_height_px):
            px_grid = I[trapz_oversampling * k:trapz_oversampling * k +
                        trapz_oversampling + 1, trapz_oversampling *
                        j:trapz_oversampling * j + trapz_oversampling + 1]
            res1 = scipy.integrate.cumtrapz(px_grid,
                                            dx=l_px_m / trapz_oversampling,
                                            axis=0,
                                            initial=0)
            res2 = scipy.integrate.cumtrapz(res1[-1, :],
                                            dx=l_px_m / trapz_oversampling,
                                            initial=0)
            count_cumtrapz[k, j] = res2[-1]
    # Total energy in image
    P_sum = sum(count_cumtrapz.flatten())
    count_cumtrapz /= P_sum

    if plotit:
        mu.newfigure(1, 2)
        plt.subplot(1, 2, 1)
        plt.imshow(I, norm=LogNorm())
        mu.colorbar()
        plt.title('Intensity (oversampled by a factor of %d)' %
                  trapz_oversampling)
        plt.subplot(1, 2, 2)
        plt.imshow(count_cumtrapz, norm=LogNorm())
        mu.colorbar()
        plt.title('Count (via trapezoidal rule)')
        mu.show_plot()

    return count_cumtrapz, I, P_0, P_sum, I_0
Esempio n. 21
0
def get_diffraction_limited_image(
        image_truth,
        l_px_m,
        f_ratio,
        wavelength_m,
        f_ratio_in=None,
        wavelength_in_m=None,  # f-ratio and imaging wavelength of the input image (if it has N_os > 1)
        N_OS_psf=4,
        detector_size_px=None,
        plotit=False):
    """ Convolve the PSF of a given telescope at a given wavelength with image_truth to simulate diffraction-limited imaging. 
	It is assumed that the truth image has the appropriate plate scale of, but may be larger than, the detector. 
	If the detector size is not given, then it is assumed that the input image and detector have the same dimensions. 

	The flow should really be like this:
		1. Generate the PSF with N_OS = 4, say.
		2. Rescale the image to achieve the same plate scale.
		3. Convolve.
		4. Resample back down to the original plate scale.

	"""
    print("Diffraction-limiting truth image(s)...")
    image_truth, N, height, width = imutils.get_image_size(image_truth)

    # If the input image is already sampled by N_os > 1, then the PSF that we convolve with the image needs to add in quadrature with the PSF that has already been convolved with the image to get to the scaling we want.
    if f_ratio_in != None and wavelength_in_m != None:
        # Then we need to add the PSFs in quadrature.
        f_ratio_out = f_ratio
        wavelength_out_m = wavelength_m

        efl = 1
        D_in = efl / f_ratio_in
        D_out = efl / f_ratio_out
        FWHM_in = wavelength_in_m / D_in
        FWHM_out = wavelength_out_m / D_out
        FWHM_prime = np.sqrt(FWHM_out**2 - FWHM_in**2)

        wavelength_prime_m = wavelength_in_m
        D_prime = wavelength_prime_m / FWHM_prime
        f_ratio_prime = efl / D_prime

        f_ratio = f_ratio_prime
        wavelength_m = wavelength_prime_m

    # Because we specify the PSF in terms of Nyquist sampling, we need to express N_OS in terms of the f ratio and wavelength of the input image.
    N_OS_input = wavelength_m * f_ratio / 2 / l_px_m / (np.deg2rad(
        206265 / 3600))

    # Calculating the PSF
    psf = psf_airy_disk_kernel(wavelength_m=wavelength_m,
                               N_OS=N_OS_psf,
                               l_px_m=l_px_m)
    # TODO need to check that the PSF is not larger than image_truth_large

    # Convolving the PSF and the truth image to obtain the simulated diffraction-limited image
    # image_difflim = np.ndarray((N, height, width))
    for k in range(N):
        # Resample the image up to the appropriate plate scale.
        image_truth_large = resizeImagesToDetector(image_truth[k],
                                                   1 / N_OS_input,
                                                   1 / N_OS_psf)
        # Convolve with the PSF.
        image_difflim_large = fftwconvolve.fftconvolve(image_truth_large,
                                                       psf,
                                                       mode='same')
        # Resize the image to its original plate scale.
        if k == 0:
            im = resizeImagesToDetector(image_difflim_large, 1 / N_OS_psf,
                                        1 / N_OS_input)
            image_difflim = np.ndarray((N, im.shape[0], im.shape[1]))
            image_difflim[0] = im
        else:
            image_difflim[k] = resizeImagesToDetector(image_difflim_large,
                                                      1 / N_OS_psf,
                                                      1 / N_OS_input)

    if plotit:
        mu.newfigure(1, 3)
        plt.subplot(1, 3, 1)
        plt.imshow(psf)
        mu.colorbar()
        plt.title('Diffraction-limited PSF of telescope')
        plt.subplot(1, 3, 2)
        plt.imshow(image_truth[0])
        mu.colorbar()
        plt.title('Truth image')
        plt.subplot(1, 3, 3)
        plt.imshow(image_difflim[0])
        mu.colorbar()
        plt.title('Diffraction-limited image')
        plt.suptitle('Diffraction-limiting image')
        mu.show_plot()

    return np.squeeze(image_difflim)
Esempio n. 22
0
def lucky_frame(
        im,  # In electron counts/s.
        psf,  # Normalised.
        scale_factor,
        t_exp,
        final_sz,
        tt=np.array([0, 0]),
        im_star=None,  # In electron counts/s.					
        noise_frame_gain_multiplied=0,  # Noise injected into the system that is multiplied up by the detector gain after conversion to counts via a Poisson distribution, e.g. sky background, emission from telescope, etc. Must have shape final_sz. It is assumed that this noise frame has already been multiplied up by the detector gain!
        noise_frame_post_gain=0,  # Noise injected into the system after gain multiplication, e.g. read noise. Must have shape final_sz.
        gain=1,  # Detector gain.
        detector_saturation=np.inf,  # Detector saturation.
        plate_scale_as_px_conv=1,  # Only used for plotting.
        plate_scale_as_px=1,  # Only used for plotting.
        plotit=False):
    """ 
		This function can be used to generate a short-exposure 'lucky' image that can be input to the Lucky Imaging algorithms.
			Input: 	one 'raw' countrate image of a galaxy; one PSF with which to convolve it (at the same plate scale)
			Output: a 'Lucky' exposure. 			
			Process: convolve with PSF --> resize to detector --> add tip and tilt (from a premade vector of tip/tilt values) --> convert to counts --> add noise --> subtract the master sky/dark current. 
	"""
    # Convolve with PSF.
    im_raw = im
    im_convolved = obssim.convolve_psf(im_raw, psf)

    # Add a star to the field. We need to add the star at the convolution plate scale BEFORE we resize down because of the tip-tilt adding step!
    if is_numlike(im_star):
        if im_star.shape != im_convolved.shape:
            print(
                "ERROR: the input image of the star MUST have the same size and plate scale as the image of the galaxy after convolution!"
            )
            raise UserWarning
        im_convolved += im_star

    # Resize to detector (+ edge buffer).
    im_resized = imutils.fourier_resize(im=im_convolved,
                                        scale_factor=scale_factor,
                                        conserve_pixel_sum=True)

    # Add tip and tilt. To avoid edge effects, max(tt) should be less than or equal to the edge buffer.
    edge_buffer_px = (im.shape[0] - final_sz[0]) / 2
    if edge_buffer_px > 0 and max(tt) > edge_buffer_px:
        print(
            "WARNING: the edge buffer is less than the supplied tip and tilt by a margin of {:.2f} pixels! Shifted image will be clipped."
            .format(np.abs(edge_buffer_px - max(tt))))
    im_tt = obssim.add_tt(image=im_resized, tt_idxs=tt)[0]
    # Crop back down to the detector size.
    if edge_buffer_px > 0:
        im_tt = imutils.centre_crop(im_tt, final_sz)
    # Convert to counts. Note that we apply the gain AFTER we convert to integer
    # counts.
    im_counts = etcutils.expected_count_to_count(im_tt, t_exp=t_exp) * gain
    # Add the pre-gain noise. Here, we assume that the noise frame has already
    # been multiplied by the gain before being passed into this function.
    im_noisy = im_counts + noise_frame_gain_multiplied
    # Add the post-gain noise (i.e. read noise)
    im_noisy += noise_frame_post_gain
    # Account for detector saturation
    im_noisy = np.clip(im_noisy, a_min=0, a_max=detector_saturation)

    if plotit:
        plate_scale_as_px = plate_scale_as_px_conv * scale_factor
        # Plotting
        mu.newfigure(1, 3)
        plt.suptitle(
            'Convolving input image with PSF and resizing to detector')
        mu.astroimshow(im=im_raw,
                       title='Truth image (electrons/s)',
                       plate_scale_as_px=plate_scale_as_px_conv,
                       colorbar_on=True,
                       subplot=131)
        mu.astroimshow(im=psf,
                       title='Point spread function (normalised)',
                       plate_scale_as_px=plate_scale_as_px_conv,
                       colorbar_on=True,
                       subplot=132)
        # mu.astroimshow(im=im_convolved,
        # 	title='Star added, convolved with PSF (electrons/s)',
        # 	plate_scale_as_px = plate_scale_as_px_conv,
        # 	colorbar_on=True,
        # 	subplot=143)
        mu.astroimshow(im=im_resized,
                       title='Resized to detector plate scale (electrons/s)',
                       plate_scale_as_px=plate_scale_as_px,
                       colorbar_on=True,
                       subplot=133)

        # Zooming in on the galaxy
        # mu.newfigure(1,4)
        # plt.suptitle('Convolving input image with PSF and resizing to detector')
        # mu.astroimshow(im=imutils.centre_crop(im=im_raw, units='arcsec', plate_scale_as_px=plate_scale_as_px_conv, sz_final=(6, 6)), title='Raw input image (electrons/s)', plate_scale_as_px = plate_scale_as_px_conv, colorbar_on=True, subplot=141)
        # mu.astroimshow(im=psf, title='Point spread function (normalised)', plate_scale_as_px = plate_scale_as_px_conv, colorbar_on=True, subplot=142)
        # mu.astroimshow(im=imutils.centre_crop(im=im_convolved, units='arcsec', plate_scale_as_px=plate_scale_as_px_conv, sz_final=(6, 6)), title='Star added, convolved with PSF (electrons/s)', plate_scale_as_px = plate_scale_as_px_conv, colorbar_on=True, subplot=143)
        # mu.astroimshow(im=imutils.centre_crop(im=im_resized, units='arcsec', plate_scale_as_px=plate_scale_as_px, sz_final=(6, 6)), title='Resized to detector plate scale (electrons/s)', plate_scale_as_px=plate_scale_as_px, colorbar_on=True, subplot=144)

        mu.newfigure(1, 3)
        plt.suptitle(
            'Adding tip and tilt, converting to integer counts and adding noise'
        )
        mu.astroimshow(im=im_tt,
                       title='Atmospheric tip and tilt added (electrons/s)',
                       plate_scale_as_px=plate_scale_as_px,
                       colorbar_on=True,
                       subplot=131)
        mu.astroimshow(
            im=im_counts,
            title=
            r'Converted to integer counts and gain-multiplied by %d (electrons)'
            % gain,
            plate_scale_as_px=plate_scale_as_px,
            colorbar_on=True,
            subplot=132)
        mu.astroimshow(im=im_noisy,
                       title='Noise added (electrons)',
                       plate_scale_as_px=plate_scale_as_px,
                       colorbar_on=True,
                       subplot=133)

        # plt.subplot(1,4,4)
        plt.figure()
        x = np.linspace(-im_tt.shape[0] / 2, +im_tt.shape[0] / 2,
                        im_tt.shape[0]) * plate_scale_as_px
        plt.plot(x,
                 im_tt[:, im_tt.shape[1] / 2],
                 'g',
                 label='Electron count rate')
        plt.plot(x,
                 im_counts[:, im_tt.shape[1] / 2],
                 'b',
                 label='Converted to integer counts ($t_{exp} = %.2f$ s)' %
                 t_exp)
        plt.plot(x, im_noisy[:, im_tt.shape[1] / 2], 'r', label='Noise added')
        plt.xlabel('arcsec')
        plt.ylabel('Pixel value (electrons)')
        plt.title('Linear profiles')
        plt.axis('tight')
        plt.legend(loc='lower left')
        mu.show_plot()

    return im_noisy
Esempio n. 23
0
def get_seeing_limited_image(images,
                             seeing_diameter_as,
                             plate_scale_as=1,
                             padFactor=1,
                             plotit=False):
    """
		 Convolve a Gaussian PSF with an input image to simulate seeing with a FWHM of seeing_diameter_as. 
	"""
    print("Seeing-limiting image(s)", end="")

    images, N, height, width = get_image_size(images)

    # Padding the source image.
    pad_ud = height // padFactor // 2
    pad_lr = width // padFactor // 2

    # If the image dimensions are odd, need to ad an extra row/column of zeros.
    image_padded = np.pad(images[0], ((pad_ud, pad_ud + height % 2),
                                      (pad_lr, pad_lr + width % 2)),
                          mode='constant')
    # conv_height = image_padded.shape[0]
    # conv_width = image_padded.shape[1]
    conv_height = 2 * pad_ud + height + (height % 2)
    conv_width = 2 * pad_lr + width + (width % 2)

    # Generate a Gaussian kernel.
    kernel = np.zeros((conv_height, conv_width))
    y_as = np.arange(-conv_width // 2, +conv_width // 2 + conv_width % 2,
                     1) * plate_scale_as
    x_as = np.arange(-conv_height // 2, +conv_height // 2 + conv_height % 2,
                     1) * plate_scale_as
    X, Y = np.meshgrid(x_as, y_as)
    sigma = seeing_diameter_as / (2 * np.sqrt(2 * np.log(2)))
    kernel = np.exp(-(np.power(X, 2) + np.power(Y, 2)) /
                    (2 * np.power(sigma, 2)))
    kernel /= sum(kernel.flatten())
    kernel = np.pad(kernel, ((pad_ud, pad_ud + height % 2),
                             (pad_lr, pad_lr + width % 2)),
                    mode='constant')

    # Convolving the kernel with the image.
    image_seeing_limited = np.ndarray((N, conv_height, conv_width))
    image_seeing_limited_cropped = np.ndarray((N, height, width))

    for k in range(N):
        print('.', end="")
        image_padded = np.pad(images[k], ((pad_ud, pad_ud + height % 2),
                                          (pad_lr, pad_lr + width % 2)),
                              mode='constant')
        image_seeing_limited[k] = fftwconvolve.fftconvolve(image_padded,
                                                           kernel,
                                                           mode='same')
        image_seeing_limited_cropped[k] = image_seeing_limited[k,
                                                               pad_ud:height +
                                                               pad_ud,
                                                               pad_lr:width +
                                                               pad_lr]

    if plotit:
        mu.newfigure(2, 2)
        plt.suptitle('Seeing-limiting image')
        plt.subplot(2, 2, 1)
        plt.imshow(images[0])
        mu.colorbar()
        plt.title('Input image')
        plt.subplot(2, 2, 2)
        plt.imshow(kernel, extent=axes_kernel)
        mu.colorbar()
        plt.title('Kernel')
        plt.subplot(2, 2, 3)
        plt.imshow(image_seeing_limited[0])
        mu.colorbar()
        plt.title('Convolved image')
        plt.subplot(2, 2, 4)
        plt.imshow(image_seeing_limited_cropped[0])
        mu.colorbar()
        plt.title('Cropped, convolved image')
        mu.show_plot()

    return np.squeeze(image_seeing_limited_cropped)
Esempio n. 24
0
def simulate_sersic_galaxy(im_out_fname, # Output FITS file name
	height_px, 	# Height of output file
	width_px, 	# Width of output file
	mu_e,		# Surface brightness magnitude at effective radius
	R_e_px,		# Effective (half-light) radius (can be float)
	n, 			# Sersic index
	plate_scale_as_px, 	# Plate scale
	axis_ratio,	# Axis ratio (a/b)
	zeropoint = -AB_MAGNITUDE_ZEROPOINT, # Careful of the minus sign!
	pos_px = None, # Position of galaxy in frame 
	PA_deg = 0,	# Rotation angle
	object_type = 'sersic/2',
	galfit_input_fname = None,
	plotit = False,
	overwrite_existing = False
	):
	"""
		Return a simulated image of a galaxy (made using GALFIT) given the inputs.
	"""
	
	if galfit_input_fname == None:
		if not os.path.exists("galfit"):
			os.makedirs("galfit")
		galfit_input_fname = "galfit/galfit_input.txt"

	if not im_out_fname.endswith('.fits'):
		im_out_fname += '.fits'

	# Writing the parameters file.
	if not os.path.isfile(im_out_fname) or overwrite_existing:		
		galfit_input_fname, im_out_fname = write_GALFIT_params_file(
			galfit_input_fname, 
			im_out_fname, 
			height_px, 
			width_px, 
			mu_e, 
			R_e_px, 
			n, 
			plate_scale_as_px, 
			axis_ratio, 
			zeropoint, 
			pos_px, 
			PA_deg, 
			object_type)
		# Calling GALFIT.
		call_GALFIT(galfit_input_fname)
	else:
		print("WARNING: I found a GALFIT .fits file '{}' with the same name as the input filename, so I am using that instead of calling GALFIT again!".format(im_out_fname))

	# Editing the header to include the input parameters.
	hdulist = astropy.io.fits.open(im_out_fname, mode='update')
	if overwrite_existing:
		hdulist[0].header['R_E_PX'] = R_e_px
		hdulist[0].header['MU_E'] = mu_e
		hdulist[0].header['SER_IDX'] = n
	im_raw = hdulist[0].data
	hdulist.flush()
	hdulist.close()

	# Plotting.
	if plotit:
		mu.newfigure()
		plt.imshow(im_raw)
		plt.title("GALFIT-generated image")
		mu.colorbar()
		mu.show_plot()

	return im_raw