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 |
|
---|