Commits
1 - | from typing import Optional, Tuple, Union |
1 + | from typing import Dict, Optional, Tuple, Union |
2 2 | |
3 3 | import numpy as np |
4 4 | from .mstools import write_history |
5 5 | from casatools import table, ms, mstransformer |
6 6 | from casatools import measures as me |
7 7 | from casatasks import casalog |
8 8 | from .parallel.parallel_data_helper import ParallelDataHelper |
9 9 | |
10 10 | |
11 11 | def phaseshift( |
133 133 | try: |
134 134 | tblocal.open(vis) |
135 135 | colnames = tblocal.colnames() |
136 136 | finally: |
137 137 | tblocal.done() |
138 138 | return colnames |
139 139 | |
140 140 | |
141 141 | def _update_field_subtable(outputvis: str, field: str, phasecenter: Union[str, dict]): |
142 142 | """Update MS/FIELD subtable with shifted center(s).""" |
143 + | |
143 144 | try: |
144 145 | tblocal = table() |
145 146 | # modify FIELD table |
146 147 | tblocal.open(outputvis + "/FIELD", nomodify=False) |
147 148 | pcol = tblocal.getcol("PHASE_DIR") |
148 149 | |
150 + | field_frames = _find_field_ref_frames(tblocal) |
149 151 | if isinstance(phasecenter, str): |
150 - | thenewra_rad, thenewdec_rad = _convert_to_ra_dec_j2000(phasecenter) |
151 152 | if field: |
152 153 | try: |
153 154 | field_id = int(field) |
154 155 | except ValueError as _exc: |
155 156 | fnames = tblocal.getcol("NAME") |
156 157 | field_id = np.where(fnames == field)[0][0] |
158 + | thenewra_rad, thenewdec_rad = _convert_to_ref_frame(phasecenter, field_frames[field_id]) |
157 159 | pcol[0][0][field_id] = thenewra_rad |
158 160 | pcol[1][0][field_id] = thenewdec_rad |
159 161 | else: |
160 162 | for row in range(0, tblocal.nrows()): |
163 + | thenewra_rad, thenewdec_rad = _convert_to_ref_frame(phasecenter, field_frames[row]) |
161 164 | pcol[0][0][row] = thenewra_rad |
162 165 | pcol[1][0][row] = thenewdec_rad |
163 166 | |
164 167 | elif isinstance(phasecenter, dict): |
165 168 | for field_id, field_center in phasecenter.items(): |
166 - | thenewra_rad, thenewdec_rad = _convert_to_ra_dec_j2000(field_center) |
167 169 | field_iidx = int(field_id) |
170 + | thenewra_rad, thenewdec_rad = _convert_to_ref_frame(field_center, field_frames[field_iidx]) |
168 171 | pcol[0][0][field_iidx] = thenewra_rad |
169 172 | pcol[1][0][field_iidx] = thenewdec_rad |
170 173 | |
171 174 | tblocal.putcol("PHASE_DIR", pcol) |
172 175 | |
173 176 | except Exception as instance: |
174 177 | casalog.post(f"*** Error '%s' updating FIELD subtable {instance}", "WARN") |
175 178 | raise RuntimeError(str(instance)) |
176 179 | finally: |
177 180 | tblocal.done() |
178 181 | |
179 182 | |
180 - | def _convert_to_ra_dec_j2000(phasecenter: str) -> Tuple[float, float]: |
181 - | """Parse phase center string to obtain ra/dec (in rad)""" |
183 + | def _find_field_ref_frames(tblocal: table) -> Dict[int, str]: |
184 + | """ |
185 + | Given an open FIELD subtable, returns a dict of {field: reference_frame} for the PHASE_DIR |
186 + | column, where field is of type int, and reference_frame is of type str. |
187 + | |
188 + | This handles: |
189 + | - simple metadata where the reference frame is in the keywords of the PHASE_DIR column (same |
190 + | ref frame for all fields) |
191 + | - variable (per-field) reference frame metadata, usually in a PhaseDir_Ref additional column |
192 + | """ |
193 + | |
194 + | dir_col = "PHASE_DIR" |
195 + | metainfo = tblocal.getcolkeyword(dir_col, "MEASINFO") |
196 + | nrows = tblocal.nrows() |
197 + | if "Ref" in metainfo: |
198 + | ref_frame = metainfo["Ref"] |
199 + | field_ref_frames = {field_id: ref_frame for field_id in range(0, nrows)} |
200 + | elif "Ref" not in metainfo and ("VarRefCol" in metainfo and "TabRefTypes" in metainfo and |
201 + | "TabRefCodes" in metainfo): |
202 + | col = metainfo["VarRefCol"] # presumably PhaseDir_Ref |
203 + | ref_frame_codes = tblocal.getcol(col) |
204 + | ref_codes_to_frame_idx = {code: idx for idx, code in enumerate(metainfo["TabRefCodes"])} |
205 + | ref_frame_names = [metainfo["TabRefTypes"][ref_codes_to_frame_idx[code]] for code in |
206 + | ref_frame_codes] |
207 + | field_ref_frames = {field_id: ref_frame_names[field_id] for field_id in np.arange(0, nrows)} |
208 + | else: |
209 + | raise RuntimeError("Error when retrieving reference frames from the metadata of column " |
210 + | f"{dir_col}. The field 'Ref' is not present but could not find the " |
211 + | "fields 'VarRefCol', 'TabRefTypes', and 'TabRefCodes'") |
212 + | |
213 + | return field_ref_frames |
214 + | |
215 + | |
216 + | def _convert_to_ref_frame(phasecenter: str, output_ref_frame: str) -> Tuple[float, float]: |
217 + | """ |
218 + | Converts one phasecenter (presumably given as input to this task) to another reference |
219 + | frame (presumably the frame used in the (output) MS for the relevant field). |
220 + | |
221 + | When applying phaseshift, the output MS has the same reference frames as the input MS, as |
222 + | propagated by mstransform. |
223 + | |
224 + | Returns the v0,v1 (RA,Dec) values in units of radians and using the requested frame, ready to be |
225 + | written to a FIELD subtable. |
226 + | """ |
182 227 | def parse_phasecenter(center: str) -> Tuple[str, str, str]: |
183 228 | """ |
184 - | Splits the: |
229 + | Parse phase center string to obtain ra/dec (in rad). Splits the: |
185 230 | - (optional) frame, |
186 231 | - v0 (typically RA), |
187 232 | - v1 (typically Dec) |
188 233 | in a phasecenter string. |
189 234 | """ |
190 235 | dirstr = center.split(" ") |
191 236 | if 3 == len(dirstr): |
192 237 | dir_frame, dir_v0, dir_v1 = dirstr[0], dirstr[1], dirstr[2] |
193 238 | elif 2 == len(dirstr): |
194 239 | dir_frame = "" # J2000 will be default in me.direction, etc. |
195 240 | dir_v0, dir_v1 = dirstr[0], dirstr[1] |
196 241 | else: |
197 242 | raise AttributeError(f"Wrong phasecenter string: '{center}'. It must have 3 or 2 " |
198 243 | "items separated by space(s): 'reference_frame RA Dec' or 'RA Dec'") |
199 244 | |
200 245 | return dir_frame, dir_v0, dir_v1 |
201 246 | |
202 247 | try: |
203 248 | melocal = me() |
204 249 | dir_frame, dir_v0, dir_v1 = parse_phasecenter(phasecenter) |
250 + | |
251 + | # Note: even if the input frame is the same as output_ref_frame, we need to ensure units of |
252 + | # radians for the FIELD subtable |
205 253 | if dir_frame: |
206 254 | thedir = melocal.direction(dir_frame, dir_v0, dir_v1) |
207 255 | else: |
208 256 | thedir = melocal.direction(v0=dir_v0, v1=dir_v1) |
209 257 | if not thedir: |
210 258 | raise RuntimeError( |
211 259 | f"measures.direction() failed for phasecenter string: {phasecenter}" |
212 260 | ) |
213 - | if dir_frame != "J2000": |
214 - | # Convert to J2000 |
215 - | thedir = melocal.measure(thedir, "J2000") |
261 + | |
262 + | if dir_frame != output_ref_frame: |
263 + | thedir = melocal.measure(thedir, output_ref_frame) |
216 264 | thenewra_rad = thedir["m0"]["value"] |
217 265 | thenewdec_rad = thedir["m1"]["value"] |
218 266 | except Exception as instance: |
219 267 | casalog.post( |
220 268 | f"*** Error {instance} when interpreting parameter 'phasecenter': ", |
221 269 | "SEVERE", |
222 270 | ) |
223 271 | raise RuntimeError(str(instance)) |
224 272 | finally: |
225 273 | melocal.done() |