Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
All notable changes to this project will be documented in this file.

## [Unreleased]

### Changed
- When reading MWAX correlator files , `remove_coarse_band` now defaults to
`False` if DERIPPLE is on. Otherwise it defaults to `True`.
Expand All @@ -13,6 +14,8 @@ raised, and no pfb shape is removed.
fringe-stopped data.

### Fixed
- A backwards compatibiilty issue with feed arrays in old beamfits files which
could specify feeds as "e" or "n".
- Compatiblity with numpy>=2.3

## [3.2.4] - 2025-09-15
Expand Down
2 changes: 1 addition & 1 deletion src/pyuvdata/utils/pol.py
Original file line number Diff line number Diff line change
Expand Up @@ -1031,7 +1031,7 @@ def get_feeds_from_x_orientation(
if feeds is None and feed_array is None:
warnings.warn(
"Unknown polarization basis -- assuming linearly polarized (x/y) "
"feeds for Telescope.feed_array."
"feeds for feed_array."
)
feeds = ["x", "y"]

Expand Down
37 changes: 29 additions & 8 deletions src/pyuvdata/uvbeam/beamfits.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,16 +312,25 @@ def read_beamfits(
self.model_name = primary_header.pop("MODEL", None)
self.model_version = primary_header.pop("MODELVER", None)
self.mount_type = primary_header.pop("MNTSTA", mount_type)
x_orientation = primary_header.pop("XORIENT", "east")
x_orientation = primary_header.pop("XORIENT", None)
feedlist = primary_header.pop("FEEDLIST", None)
if feedlist is not None:
self.feed_array = np.array(feedlist[1:-1].split(", "))
feedlist = [feed.lower() for feed in feedlist[1:-1].split(", ")]
if "e" in feedlist and "n" in feedlist:
if x_orientation is None:
if feedlist.index("e") < feedlist.index("n"):
x_orientation = "east"
else:
x_orientation = "north"
feed_map = utils.pol.x_orientation_pol_map(
x_orientation=x_orientation
)
for key, value in feed_map.items():
feedlist[feedlist.index(value)] = key
self.feed_array = np.array(feedlist)

self.Nfeeds = len(self.feed_array)
feedang = primary_header.pop("FEEDANG", None)
if feedang is not None:
self.feed_angle = np.array(
[float(item) for item in feedang[1:-1].split(", ")]
)

self.history = str(primary_header.get("HISTORY", ""))
if not utils.history._check_history_version(
Expand Down Expand Up @@ -488,8 +497,20 @@ def read_beamfits(
# no bandpass information, set it to an array of ones
self.bandpass_array = np.ones(self.Nfreqs)

# Handle x-orientation keyword here
if self.feed_angle is None or self.feed_array is None:
# Handle missing feed orientation info here
if feedang is not None:
self.feed_angle = np.array(
[float(item) for item in feedang[1:-1].split(", ")]
)
else:
if x_orientation is None:
# no feed angles and no x_orientation info.
# Default to east x_orientation
warnings.warn(
"This beamfits file has no information about the orientation "
"of the feeds. Defaulting an East x_orientation."
)
x_orientation = "east"
self.set_feeds_from_x_orientation(x_orientation)

if run_check:
Expand Down
2 changes: 1 addition & 1 deletion src/pyuvdata/uvbeam/uvbeam.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,7 @@ def _fix_feeds(self):
warnings.warn(
"Feed information now required for power beams, will default to "
"linear feeds with x-orientation aligned to the east (populated based "
"on what is preesnt in UVBeam.polarization_array). This will become an "
"on what is present in UVBeam.polarization_array). This will become an "
"error in version 3.4.",
DeprecationWarning,
)
Expand Down
55 changes: 53 additions & 2 deletions tests/uvbeam/test_beamfits.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,23 @@ def cst_power_1freq_cut_healpix(cst_efield_1freq_cut_healpix_main):
def hera_beam_casa():
beam_in = UVBeam()
casa_file = fetch_data("hera_casa_beam")
beam_in.read_beamfits(casa_file, mount_type="fixed", run_check=False)
with check_warnings(
UserWarning,
match=[
"This beamfits file has no information about the orientation of "
"the feeds. Defaulting an East x_orientation.",
"Unknown polarization basis -- assuming linearly polarized (x/y) "
"feeds for feed_array.",
],
):
beam_in.read_beamfits(casa_file, mount_type="fixed", run_check=False)

# fill in missing parameters
beam_in.data_normalization = "peak"
beam_in.feed_name = "casa_ideal"
beam_in.feed_version = "v0"
beam_in.model_name = "casa_airy"
beam_in.model_version = "v0"
beam_in.mount_type = "fixed"

# this file is actually in an orthoslant projection RA/DEC at zenith at a
# particular time.
Expand Down Expand Up @@ -205,6 +213,49 @@ def test_read_cst_write_read_fits_change_freq_units(cst_power_1freq, tmp_path):
return


@pytest.mark.parametrize(
("feedlist", "feedang"),
[("[E, N]", [np.pi / 2, 0]), ("[E, N]", None), ("[N, E]", [0, np.pi / 2])],
)
def test_e_n_feeds(cst_efield_1freq_cut, feedlist, feedang, tmp_path):
# this test was inspired by an error reading in an old beamfits file that
# Ruby made for the LWA. It had a feed array of [E, N], which errored.
beam_in = cst_efield_1freq_cut
beam_out = UVBeam()

write_file = str(tmp_path / "outtest_beam.fits")
write_file2 = str(tmp_path / "outtest_beam2.fits")
beam_in.write_beamfits(write_file, clobber=True)

# now change frequency units
with fits.open(write_file) as fname:
data = fname[0].data
primary_hdr = fname[0].header
primary_hdr["FEEDLIST"] = feedlist
if feedang is None:
del primary_hdr["FEEDANG"]
else:
primary_hdr["FEEDANG"] = (
"[" + ", ".join(str(item) for item in feedang) + "]"
)
hdunames = fits_utils._indexhdus(fname)
bandpass_hdu = fname[hdunames["BANDPARM"]]
basisvec_hdu = fname[hdunames["BASISVEC"]]

prihdu = fits.PrimaryHDU(data=data, header=primary_hdr)
hdulist = fits.HDUList([prihdu, basisvec_hdu, bandpass_hdu])

hdulist.writeto(write_file2, overwrite=True)
hdulist.close()

beam_out.read_beamfits(write_file2)

if np.flip(beam_in.feed_angle).tolist() == feedang:
beam_in.feed_angle = np.flip(beam_in.feed_angle)

assert beam_in == beam_out


def test_writeread_healpix(cst_efield_1freq_cut_healpix, tmp_path):
beam_in = cst_efield_1freq_cut_healpix.copy()
beam_out = UVBeam()
Expand Down
3 changes: 3 additions & 0 deletions tests/uvbeam/test_uvbeam.py
Original file line number Diff line number Diff line change
Expand Up @@ -3023,6 +3023,7 @@ def test_generic_read_cst():


@pytest.mark.filterwarnings("ignore:Unknown polarization basis")
@pytest.mark.filterwarnings("ignore:This beamfits file has no information about")
@pytest.mark.filterwarnings("ignore:The mount_type keyword is set")
@pytest.mark.parametrize(
"dname", ["hera_fagnoni_dipole_yaml", "mwa_full_EE", "hera_casa_beam"]
Expand Down Expand Up @@ -3147,6 +3148,7 @@ def test_generic_read_all_bad_files(tmp_path):


@pytest.mark.filterwarnings("ignore:Unknown polarization basis")
@pytest.mark.filterwarnings("ignore:This beamfits file has no information about")
@pytest.mark.filterwarnings("ignore:The mount_type keyword is set")
@pytest.mark.parametrize(
"dname", ["hera_fagnoni_dipole_yaml", "mwa_full_EE", "hera_casa_beam"]
Expand Down Expand Up @@ -3184,6 +3186,7 @@ def test_from_file(dname):

@pytest.mark.filterwarnings("ignore:The mount_type keyword is set")
@pytest.mark.filterwarnings("ignore:Unknown polarization basis")
@pytest.mark.filterwarnings("ignore:This beamfits file has no information about")
@pytest.mark.parametrize(
["dname", "path_var", "relative_path", "file_list"],
[
Expand Down
Loading