[2] | 1 | from __future__ import absolute_import
|
---|
| 2 |
|
---|
| 3 | import collections
|
---|
| 4 | import numpy as np
|
---|
| 5 |
|
---|
| 6 |
|
---|
| 7 | class Axis(object):
|
---|
| 8 | """Class to hold axis information.
|
---|
| 9 | """
|
---|
| 10 |
|
---|
| 11 | def __init__(self, data, title='', units=''):
|
---|
| 12 | self.data = data
|
---|
| 13 | self.title = title
|
---|
| 14 | self.units = units
|
---|
| 15 |
|
---|
| 16 |
|
---|
| 17 | class Image(object):
|
---|
| 18 | """Class to hold 2d image, with axes, units, title, etc.
|
---|
| 19 | """
|
---|
| 20 | def __init__(self, data, axes=[], title='', units=''):
|
---|
| 21 | self.data = data
|
---|
| 22 | self.axes = axes
|
---|
| 23 | self.title = title
|
---|
| 24 | self.units = units
|
---|
| 25 |
|
---|
| 26 |
|
---|
| 27 | class BeamsGenerator(object):
|
---|
| 28 | """Class to generate the primary beam(s) of the simulated observation.
|
---|
| 29 | """
|
---|
| 30 |
|
---|
| 31 | def __init__(self, previous_results):
|
---|
| 32 | self.previous_results = previous_results
|
---|
| 33 | self.result = collections.OrderedDict()
|
---|
| 34 |
|
---|
| 35 | def run(self):
|
---|
| 36 | print 'BeamsGenerator.run'
|
---|
| 37 |
|
---|
| 38 | uvmapgen = self.previous_results['uvmapgenerator']
|
---|
| 39 | fts = self.previous_results['fts']
|
---|
| 40 | telescope = self.previous_results['loadparameters']['substages']\
|
---|
| 41 | ['Telescope']
|
---|
| 42 |
|
---|
| 43 | # number of mirror positions sampled by FTS
|
---|
| 44 | nsample = fts['ftsnsample']
|
---|
| 45 | fts_wn = fts['fts_wn']
|
---|
| 46 | fts_lambda = fts['fts_lambda']
|
---|
| 47 |
|
---|
| 48 | # max pixel size (radians) that will fully sample the image.
|
---|
| 49 | # Sampling freq is 2 * Nyquist freq. So sampling freq = 2 * b_max / lambda_min.
|
---|
| 50 | bmax = uvmapgen['bmax']
|
---|
| 51 | self.result['max baseline [m]'] = bmax
|
---|
| 52 | max_pixsize = np.min(fts_lambda) / (2.0 * bmax)
|
---|
| 53 | self.result['max pixsize [rad]'] = max_pixsize
|
---|
| 54 |
|
---|
| 55 | # Number of pixels per beam
|
---|
| 56 | # radius of first null of Airy disk is theta=1.22lambda/d. Number of
|
---|
| 57 | # pixels is beam diameter/max_pixsize. Calculate this for the
|
---|
| 58 | # longest wavelength, largest beam
|
---|
| 59 | max_beam_radius = 1.22 * np.max(fts_lambda) /\
|
---|
| 60 | telescope['Primary mirror diameter']
|
---|
| 61 | self.result['beam diam [rad]'] = 2.0 * max_beam_radius
|
---|
| 62 |
|
---|
| 63 | # go out to radius of first null
|
---|
| 64 | rpix = int(max_beam_radius / max_pixsize)
|
---|
| 65 | # rpix = 32
|
---|
| 66 | npix = 2 * rpix
|
---|
| 67 | self.result['npix'] = npix
|
---|
| 68 |
|
---|
| 69 | # spatial axes same for all wavelengths
|
---|
| 70 | axis = np.arange(-rpix, rpix, dtype=np.float)
|
---|
| 71 | axis *= max_pixsize
|
---|
| 72 | axis *= 206264.806247
|
---|
| 73 | self.result['spatial axis [arcsec]'] = axis
|
---|
| 74 |
|
---|
| 75 | # calculate beams for each point on fts_wn
|
---|
| 76 | # oversample uv grid by mult to minimise aliasing
|
---|
| 77 | mult = 5
|
---|
| 78 | mx,my = np.meshgrid(np.arange(-mult*rpix, mult*rpix),
|
---|
| 79 | np.arange(-mult*rpix, mult*rpix))
|
---|
| 80 | self.result['primary beam'] = collections.OrderedDict()
|
---|
| 81 | self.result['primary amplitude beam'] = collections.OrderedDict()
|
---|
| 82 | self.result['dirty beam'] = collections.OrderedDict()
|
---|
| 83 |
|
---|
| 84 | for wn in fts_wn:
|
---|
| 85 | # first calculate primary beam
|
---|
| 86 | #
|
---|
| 87 | # assume uniform illumination of primary. uv goes out to
|
---|
| 88 | # +- bmax / lambda_min, fill uv plane appropriate to primary
|
---|
| 89 | # size
|
---|
| 90 | mirror = np.ones([mult*npix,mult*npix])
|
---|
| 91 | lambda_min = 1.0 / (np.max(fts_wn) * 100.0)
|
---|
| 92 | lamb = 1.0 / (wn * 100.0)
|
---|
| 93 | mirror[(np.sqrt(mx*mx + my*my) / (mult*rpix)) *
|
---|
| 94 | (bmax / lambda_min) > \
|
---|
| 95 | (0.5 * telescope['Primary mirror diameter'] / lamb)] = 0.0
|
---|
| 96 |
|
---|
| 97 | # fftshift mirror to be centred at [0,0],
|
---|
| 98 | # do a 2d ftt of it,
|
---|
| 99 | # fftshift transform so that 0 freq is in centre of array
|
---|
| 100 | temp = np.fft.fftshift(mirror)
|
---|
| 101 | temp = np.fft.fft2(temp)
|
---|
| 102 | temp = np.fft.fftshift(temp)
|
---|
| 103 | # convert to real, should be real anyway as FT of symmetric function
|
---|
| 104 | # square to give intensity response
|
---|
| 105 | primary_amplitude_beam = np.real(temp)
|
---|
| 106 | primary_intensity_beam = np.power(np.abs(temp), 2)
|
---|
| 107 | # and normalise
|
---|
| 108 | primary_amplitude_beam /= np.max(primary_amplitude_beam)
|
---|
| 109 | primary_intensity_beam /= np.max(primary_intensity_beam)
|
---|
| 110 |
|
---|
| 111 | # truncate primary beam pattern
|
---|
| 112 | primary_amplitude_beam = primary_amplitude_beam[
|
---|
| 113 | (mult-1)*rpix:(mult+1)*rpix,
|
---|
| 114 | (mult-1)*rpix:(mult+1)*rpix]
|
---|
| 115 | primary_intensity_beam = primary_intensity_beam[
|
---|
| 116 | (mult-1)*rpix:(mult+1)*rpix,
|
---|
| 117 | (mult-1)*rpix:(mult+1)*rpix]
|
---|
| 118 |
|
---|
| 119 | # axes
|
---|
| 120 | axis1 = Axis(data=-axis, title='RA offset', units='arcsec')
|
---|
| 121 | axis2 = Axis(data=axis, title='Dec offset', units='arcsec')
|
---|
| 122 | image = Image(data=primary_intensity_beam, axes=[axis1, axis2],
|
---|
| 123 | title='Primary Beam %06.4g cm-1' % wn)
|
---|
| 124 | self.result['primary beam'][wn] = image
|
---|
| 125 |
|
---|
| 126 | image = Image(data=primary_amplitude_beam, axes=[axis1, axis2],
|
---|
| 127 | title='Amplitude Primary Beam %06.4g cm-1' % wn)
|
---|
| 128 | self.result['primary amplitude beam'][wn] = image
|
---|
| 129 |
|
---|
| 130 | # second calculate dirty beam
|
---|
| 131 | #
|
---|
| 132 | # 1. uv plane covered by discrete points so should use a
|
---|
| 133 | # slow dft rather than fft, which requires evenly spaced
|
---|
| 134 | # samples. Perhaps a bit slow though as calcs done in Python
|
---|
| 135 | # layer.
|
---|
| 136 | # 2. Could use shift theorem to give even-spaced equivalent
|
---|
| 137 | # of each discrete point, then use ffts. Equivalent to 1
|
---|
| 138 | # in speed.
|
---|
| 139 | # 3. Oversample uv plane and accept small error in point
|
---|
| 140 | # positions. Fastest but adequate?
|
---|
| 141 |
|
---|
| 142 | uv = np.zeros([mult*npix,mult*npix])
|
---|
| 143 | maxbx = bmax / np.min(fts_lambda)
|
---|
| 144 | for bxby in uvmapgen['bxby']:
|
---|
| 145 | ibx = int(round(((bxby[0] / lamb) / maxbx) * mult * rpix))
|
---|
| 146 | if ibx < 0:
|
---|
| 147 | ibx = mult*npix + ibx
|
---|
| 148 | iby = int(round(((bxby[1] / lamb) / maxbx) * mult * rpix))
|
---|
| 149 | if iby < 0:
|
---|
| 150 | iby = mult*npix + iby
|
---|
| 151 | uv[ibx,iby] = 1.0
|
---|
| 152 |
|
---|
| 153 | temp = np.fft.fftshift(uv)
|
---|
| 154 | temp = np.fft.fft2(temp)
|
---|
| 155 | temp = np.fft.fftshift(temp)
|
---|
| 156 | temp = np.power(temp, 2)
|
---|
| 157 | # convert to real, should be real anway as FT of symmetric function
|
---|
| 158 | dirty_beam = np.abs(temp)
|
---|
| 159 | # and normalise
|
---|
| 160 | dirty_beam /= np.max(dirty_beam)
|
---|
| 161 |
|
---|
| 162 | # truncate dirty beam pattern
|
---|
| 163 | dirty_beam = dirty_beam[
|
---|
| 164 | (mult-1)*rpix:(mult+1)*rpix,
|
---|
| 165 | (mult-1)*rpix:(mult+1)*rpix]
|
---|
| 166 |
|
---|
| 167 | # axes
|
---|
| 168 | axis = np.arange(-rpix, rpix, dtype=np.float)
|
---|
| 169 | axis *= (np.min(fts_lambda) / bmax)
|
---|
| 170 | axis *= 206264.806247
|
---|
| 171 | axis1 = Axis(data=-axis, title='RA offset', units='arcsec')
|
---|
| 172 | axis2 = Axis(data=axis, title='Dec offset', units='arcsec')
|
---|
| 173 |
|
---|
| 174 | image = Image(data=dirty_beam, axes=[axis1, axis2],
|
---|
| 175 | title='Dirty Beam %06.4g cm-1' % wn)
|
---|
| 176 |
|
---|
| 177 | self.result['dirty beam'][wn] = image
|
---|
| 178 |
|
---|
| 179 | return self.result
|
---|
| 180 |
|
---|
| 181 | def __repr__(self):
|
---|
| 182 | return 'PrimaryBeamGenerator'
|
---|
| 183 |
|
---|