diff --git a/CHANGELOG.md b/CHANGELOG.md index c9addfc6c..410203c08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] + ### Changed +- Added a `UVData.flip_conjugation` method and more detail to the uvw check warning +message to inform users when a conjugation flip might be needed. - When reading MWAX correlator files , `remove_coarse_band` now defaults to `False` if DERIPPLE is on. Otherwise it defaults to `True`. - If the user has set `remove_coarse_band=True` when DERIPPLE is on, a warning is diff --git a/src/pyuvdata/uvdata/uvdata.py b/src/pyuvdata/uvdata/uvdata.py index 9a2384173..b55c1e1ce 100644 --- a/src/pyuvdata/uvdata/uvdata.py +++ b/src/pyuvdata/uvdata/uvdata.py @@ -2247,6 +2247,17 @@ def _fix_autos(self): # Finally, plug the modified values back into data_array self.data_array[auto_screen] = auto_data + def flip_conjugation(self): + """ + Flip the conjugation of the visibilities (and multiply the UVWs by -1). + + Useful in the event that the data come in with the opposite conjugation + convention from pyuvdata's convention. + """ + self.uvw_array *= -1 + self.data_array = np.conj(self.data_array) + logger.info("Flipped visibility conjugation") + def check( self, *, @@ -2462,6 +2473,7 @@ def check( # make a metadata only copy of this object to properly calculate uvws if not np.allclose(temp_obj.uvw_array, self.uvw_array, atol=1): max_diff = np.max(np.abs(temp_obj.uvw_array - self.uvw_array)) + max_flip_diff = np.max(np.abs((-temp_obj.uvw_array) - self.uvw_array)) if allow_flip_conj and np.allclose( -temp_obj.uvw_array, self.uvw_array, atol=1 ): @@ -2469,23 +2481,30 @@ def check( "UVW orientation appears to be flipped, attempting to " "fix by changing conjugation of baselines." ) - self.uvw_array *= -1 - self.data_array = np.conj(self.data_array) - logger.info("Flipped Array") - elif not strict_uvw_antpos_check: - warnings.warn( - "The uvw_array does not match the expected values given " - "the antenna positions. The largest discrepancy is " - f"{max_diff} meters. This is a fairly common situation " - "but might indicate an error in the antenna positions, " - "the uvws or the phasing." - ) + self.flip_conjugation() else: - raise ValueError( + msg = ( "The uvw_array does not match the expected values given " "the antenna positions. The largest discrepancy is " f"{max_diff} meters." ) + flip_msg = "" + if max_flip_diff < max_diff: + flip_msg = ( + " It is possible that the conjugation convention does " + "not match pyuvdata's convention. Flipping the " + "conjugation with the `flip_conjugation` method would " + f"result in a maximum uvw discrepancy of {max_flip_diff} " + "meters." + ) + if not strict_uvw_antpos_check: + warnings.warn( + msg + " This is a fairly common situation " + "but might indicate an error in the antenna positions, " + "the uvws or the phasing." + flip_msg + ) + else: + raise ValueError(msg + flip_msg) # check auto and cross-corrs have sensible uvws logger.debug("Checking autos...") diff --git a/tests/conftest.py b/tests/conftest.py index 100b17a18..062345215 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -57,6 +57,7 @@ def casa_uvfits_main(): """Read in CASA tutorial uvfits file.""" fname = fetch_data("vla_casa_tutorial_uvfits") uv_in = UVData() + # TODO: why are these uvws so far off? ~2000m! with check_warnings( UserWarning, "The uvw_array does not match the expected values" ): diff --git a/tests/uvdata/test_ms.py b/tests/uvdata/test_ms.py index 1dbef1c75..c3121f4e8 100644 --- a/tests/uvdata/test_ms.py +++ b/tests/uvdata/test_ms.py @@ -1006,18 +1006,50 @@ def test_ms_bad_history(sma_mir, tmp_path): @pytest.mark.filterwarnings("ignore:The uvw_array does not match the expected values") -def test_flip_conj(nrao_ms, tmp_path): +@pytest.mark.parametrize( + ("add_error", "msg"), + [ + ( + False, + "UVW orientation appears to be flipped, attempting to fix by " + "changing conjugation of baselines.", + ), + ( + True, + "The uvw_array does not match the expected values given the antenna " + "positions. The largest discrepancy is max_diff meters. " + "This is a fairly common situation but might indicate an error in the " + "antenna positions, the uvws or the phasing. It is possible that the " + "conjugation convention does not match pyuvdata's convention. Flipping " + "the conjugation with the `flip_conjugation` method would result in " + "a maximum uvw discrepancy of max_flip_diff meters.", + ), + ], +) +def test_flip_conj(nrao_ms, tmp_path, add_error, msg): filename = os.path.join(tmp_path, "flip_conj.ms") nrao_ms.set_uvws_from_antenna_positions() + + orig_uvws = nrao_ms.uvw_array.copy() + if add_error: + nrao_ms.uvw_array += 1.1 + max_flip_diff = np.max(np.abs(nrao_ms.uvw_array - orig_uvws)) + nrao_ms.uvw_array *= -1 nrao_ms.data_array = np.conj(nrao_ms.data_array) + max_diff = np.max(np.abs(nrao_ms.uvw_array - orig_uvws)) + + if add_error: + msg = msg.replace("max_diff", str(max_diff)) + msg = msg.replace("max_flip_diff", str(max_flip_diff)) + with check_warnings( UserWarning, match="Writing in the MS file that the units of the data are unca" ): nrao_ms.write_ms(filename, flip_conj=True, run_check=False, clobber=True) - with check_warnings(UserWarning, match=["UVW orientation appears to be flip"] * 2): + with check_warnings(UserWarning, match=[msg] * 2): uv = UVData.from_file(filename) nrao_ms.check(allow_flip_conj=True) diff --git a/tests/uvdata/test_uvdata.py b/tests/uvdata/test_uvdata.py index ed0cf0343..abf7fe291 100644 --- a/tests/uvdata/test_uvdata.py +++ b/tests/uvdata/test_uvdata.py @@ -12033,7 +12033,10 @@ def test_init_like_hera_cal(hera_uvh5, tmp_path, projected, check_before_write): hera_uvh5.phase_center_catalog[0]["cat_epoch"] = 2000.0 hera_uvh5._set_app_coords_helper() warn_type = UserWarning - msg = "The uvw_array does not match the expected values" + msg = ( + "The uvw_array does not match the expected values given " + "the antenna positions. The largest discrepancy is " + ) else: warn_type = None msg = None