Skip to content

Commit 0c0ccd8

Browse files
committed
Adding illum offset handling inside of MIR
1 parent 0052f7e commit 0c0ccd8

File tree

2 files changed

+154
-1
lines changed

2 files changed

+154
-1
lines changed

src/pyuvdata/uvdata/mir.py

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
__all__ = ["Mir", "generate_sma_antpos_dict"]
2323

24+
MAX_SMA_ANTENNA_NUMBER = 10
25+
2426

2527
def generate_sma_antpos_dict(filepath):
2628
"""
@@ -70,6 +72,102 @@ def generate_sma_antpos_dict(filepath):
7072
return {item["antenna"]: item["xyz_pos"] for item in mir_antpos}
7173

7274

75+
def calc_delta_uvw_from_offset_illum(
76+
*,
77+
illum_dict,
78+
ant_1_array,
79+
ant_2_array,
80+
app_ra,
81+
app_dec,
82+
frame_pa,
83+
lst_array,
84+
telescope_lat,
85+
):
86+
"""
87+
Calculate uvw offsets from illumination pattern offsets.
88+
89+
Parameters
90+
----------
91+
illum_dict : dict
92+
Dictionary which defines the illumination offset constants for each antenna.
93+
Keys are matched to antenna numbers, values are themselves dicts containing
94+
four key/value pairs - "x0" (constant horizontal offset on the primary/after the
95+
Nasmyth), "y0" (constant vertical offset), "x1" (horizontal offset before the
96+
Nasmyth), "y1" (vertical offset before the Nasmyth). Values are in units of
97+
meters, as realized on the primary.
98+
ant_1_array : ndarray of int
99+
Number of the first antenna in a given baseline pair. Shape (Nblts,).
100+
ant_2_array : ndarray of int
101+
Number of the second antenna in a given baseline pair. Shape (Nblts,).
102+
app_ra : ndarray of float
103+
Apparent right ascension for each baseline record. Shape (Nblts,), units of
104+
radians.
105+
app_dec : ndarray of float
106+
Apparent declination for each baseline record. Shape (Nblts,), units of radians.
107+
frame_pa : ndarray of float
108+
Frame position angle for each baseline record. Shape (Nblts,), units of radians.
109+
lst_array : ndarray of float
110+
LST value for each baseline-time record. Shape (Nblts,), units of radians.
111+
telescope_lat : float
112+
Telescope latitude. Units of radians.
113+
114+
"""
115+
import erfa
116+
117+
fix_x = np.zeros(MAX_SMA_ANTENNA_NUMBER + 1)
118+
fix_y = np.zeros(MAX_SMA_ANTENNA_NUMBER + 1)
119+
rot_x = np.zeros(MAX_SMA_ANTENNA_NUMBER + 1)
120+
rot_y = np.zeros(MAX_SMA_ANTENNA_NUMBER + 1)
121+
122+
if np.any(ant_1_array < 0) or np.any(ant_1_array > MAX_SMA_ANTENNA_NUMBER):
123+
raise ValueError(
124+
f"Values in ant_1_array out of range [0, {MAX_SMA_ANTENNA_NUMBER}])."
125+
)
126+
if np.any(ant_2_array < 0) or np.any(ant_2_array > MAX_SMA_ANTENNA_NUMBER):
127+
raise ValueError(
128+
f"Values in ant_2_array out of range [0, {MAX_SMA_ANTENNA_NUMBER}])."
129+
)
130+
131+
try:
132+
for ant, indv_dict in illum_dict.items():
133+
fix_x[ant] = indv_dict["x0"]
134+
fix_y[ant] = indv_dict["y0"]
135+
rot_x[ant] = indv_dict["x1"]
136+
rot_y[ant] = indv_dict["y1"]
137+
except KeyError as err:
138+
raise KeyError("Invalid keys in illum_dict.") from err
139+
140+
# Add the things we need to rotate first
141+
uvw_offsets = np.zeros((len(ant_1_array), 3, 1))
142+
uvw_offsets[:, 0] = rot_x[ant_1_array] - rot_x[ant_2_array]
143+
uvw_offsets[:, 1] = rot_y[ant_1_array] - rot_y[ant_2_array]
144+
145+
# Calculate the elevation angle, then start some rotations!
146+
_, el_arr = erfa.hd2ae(lst_array - app_ra, app_dec, telescope_lat)
147+
uvw_offsets = utils.phasing._rotate_one_axis(
148+
uvw_offsets[:, :, np.newaxis], rot_amount=el_arr, rot_axis=2
149+
)
150+
151+
# Add the static offsets - everything is now oriented in the frame of the antenna
152+
# Note we need the extra axis here since nvec=1 for _rotate_one_axis
153+
uvw_offsets[:, 0, 0] += fix_x[ant_1_array] - fix_x[ant_2_array]
154+
uvw_offsets[:, 1, 0] += fix_y[ant_1_array] - fix_y[ant_2_array]
155+
156+
# Calculate the position angle, accounting for the frame PA as well
157+
pa_array = frame_pa + utils.phasing.calc_parallactic_angle(
158+
app_ra=app_ra, app_dec=app_dec, lst_array=lst_array, telescope_lat=telescope_lat
159+
)
160+
161+
# Rotate by the PA so that we get the UVWs in the right orientation
162+
uvw_offsets = utils.phasing._rotate_one_axis(
163+
uvw_offsets, rot_amount=pa_array, rot_axis=2
164+
)
165+
166+
# This should be ready to straight-up add, striping out the extra axis that
167+
# the _rotate functions add (since nvec=1)
168+
return np.squeeze(uvw_offsets, axis=-1)
169+
170+
73171
class Mir(UVData):
74172
"""
75173
A class for Mir file objects.
@@ -98,6 +196,7 @@ def read_mir(
98196
apply_tsys=True,
99197
apply_flags=True,
100198
apply_dedoppler=False,
199+
illum_dict=None,
101200
pseudo_cont=False,
102201
rechunk=None,
103202
compass_soln=None,
@@ -181,6 +280,7 @@ def read_mir(
181280
apply_flags=apply_flags,
182281
apply_tsys=apply_tsys,
183282
apply_dedoppler=apply_dedoppler,
283+
illum_dict=illum_dict,
184284
metadata_only=metadata_only,
185285
)
186286

@@ -285,6 +385,7 @@ def _init_from_mir_parser(
285385
apply_flags=True,
286386
apply_dedoppler=False,
287387
metadata_only=False,
388+
illum_dict=None,
288389
):
289390
"""
290391
Convert a MirParser object into a UVData object.
@@ -299,6 +400,28 @@ def _init_from_mir_parser(
299400
"flexible polarization", which compresses the polarization-axis of various
300401
attributes to be of length 1, sets the `flex_spw_polarization_array`
301402
attribute to define the polarization per spectral window. Default is True.
403+
apply_flags : bool
404+
If set to True, apply "wideband" flags to the visibilities, which are
405+
recorded by the realtime system to denote when data are expected to be bad
406+
(e.g., antennas not on source, dewar warm). Default it true.
407+
apply_tsys : bool
408+
If set to False, data are returned as correlation coefficients (normalized
409+
by the auto-correlations). Default is True, which instead scales the raw
410+
visibilities and forward-gain of the antenna to produce values in Jy
411+
(uncalibrated).
412+
apply_dedoppler : bool
413+
If set to True, data will be corrected for any doppler-tracking performed
414+
during observations, and brought into the topocentric rest frame (default
415+
for UVData objects). Default is False.
416+
metadata_only : bool
417+
Read in only the metadata, ignore visibility data. Default is False.
418+
illum_dict : dict
419+
Dictionary which defines the illumination offset constants for each antenna.
420+
Keys are matched to antenna numbers, values are themselves dicts containing
421+
four key/value pairs - "x0" (constant horizontal offset on the primary/after
422+
the Nasmyth), "y0" (constant vertical offset), "x1" (horizontal offset
423+
before the Nasmyth), "y1" (vertical offset before the Nasmyth). Values are
424+
in units of meters, as realized on the primary.
302425
"""
303426
# Create a simple array/list for broadcasting values stored on a
304427
# per-blt basis into per-spw records, and per-time into per-blt records
@@ -745,7 +868,7 @@ def _init_from_mir_parser(
745868

746869
# Need to flip the sign convention here on uvw, since we use a1-a2 versus the
747870
# standard a2-a1 that uvdata expects
748-
self.uvw_array = (-1.0) * uvw_array
871+
self.uvw_array = -uvw_array
749872

750873
self.vis_units = "Jy"
751874
self.pol_convention = "avg"
@@ -991,6 +1114,18 @@ def _init_from_mir_parser(
9911114
# Plug in the new cat_id into the phase_center_id_array
9921115
self.phase_center_id_array[inhid_mask] = cat_id
9931116

1117+
if illum_dict is not None:
1118+
self.uvw_array += calc_delta_uvw_from_offset_illum(
1119+
illum_dict=illum_dict,
1120+
ant_1_array=self.ant_1_array,
1121+
ant_2_array=self.ant_2_array,
1122+
app_ra=self.phase_center_app_ra,
1123+
app_dec=self.phase_center_app_dec,
1124+
frame_pa=self.phase_center_frame_pa,
1125+
lst_array=self.lst_array,
1126+
telescope_lat=self.telescope.location.lat.rad,
1127+
)
1128+
9941129
def write_mir(self, filename):
9951130
"""
9961131
Write out the SMA MIR files.

src/pyuvdata/uvdata/uvdata.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9019,6 +9019,13 @@ def read_mir(self, filepath, **kwargs):
90199019
If set to True, data will be corrected for any doppler-tracking performed
90209020
during observations, and brought into the topocentric rest frame (default
90219021
for UVData objects). Default is False.
9022+
illum_dict : dict
9023+
Dictionary which defines the illumination offset constants for each antenna.
9024+
Keys are matched to antenna numbers, values are themselves dicts containing
9025+
four key/value pairs - "x0" (constant horizontal offset on the primary/after
9026+
the Nasmyth), "y0" (constant vertical offset), "x1" (horizontal offset
9027+
before the Nasmyth), "y1" (vertical offset before the Nasmyth). Values are
9028+
in units of meters, as realized on the primary.
90229029
pseudo_cont : boolean
90239030
Read in only pseudo-continuum values. Default is false.
90249031
rechunk : int
@@ -9922,6 +9929,7 @@ def read(
99229929
apply_tsys=True,
99239930
apply_flags=True,
99249931
apply_dedoppler=False,
9932+
illum_dict=None,
99259933
pseudo_cont=False,
99269934
rechunk=None,
99279935
compass_soln=None,
@@ -10331,6 +10339,13 @@ def read(
1033110339
If set to True, data will be corrected for any doppler-tracking performed
1033210340
during observations, and brought into the topocentric rest frame (default
1033310341
for UVData objects). Default is False.
10342+
illum_dict : dict
10343+
Dictionary which defines the illumination offset constants for each antenna.
10344+
Keys are matched to antenna numbers, values are themselves dicts containing
10345+
four key/value pairs - "x0" (constant horizontal offset on the primary/after
10346+
the Nasmyth), "y0" (constant vertical offset), "x1" (horizontal offset
10347+
before the Nasmyth), "y1" (vertical offset before the Nasmyth). Values are
10348+
in units of meters, as realized on the primary.
1033410349
allow_flex_pol : bool
1033510350
If only one polarization per spectral window is read (and the polarization
1033610351
differs from window to window), allow for the `UVData` object to use
@@ -10608,6 +10623,7 @@ def read(
1060810623
apply_tsys=apply_tsys,
1060910624
apply_flags=apply_flags,
1061010625
apply_dedoppler=apply_dedoppler,
10626+
illum_dict=illum_dict,
1061110627
pseudo_cont=pseudo_cont,
1061210628
rechunk=rechunk,
1061310629
compass_soln=compass_soln,
@@ -10746,6 +10762,7 @@ def read(
1074610762
apply_tsys=apply_tsys,
1074710763
apply_flags=apply_flags,
1074810764
apply_dedoppler=apply_dedoppler,
10765+
illum_dict=illum_dict,
1074910766
pseudo_cont=pseudo_cont,
1075010767
rechunk=rechunk,
1075110768
compass_soln=compass_soln,
@@ -11048,6 +11065,7 @@ def read(
1104811065
apply_dedoppler=apply_dedoppler,
1104911066
apply_tsys=apply_tsys,
1105011067
apply_flags=apply_flags,
11068+
illum_dict=illum_dict,
1105111069
pseudo_cont=pseudo_cont,
1105211070
rechunk=rechunk,
1105311071
compass_soln=compass_soln,

0 commit comments

Comments
 (0)