Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
203ef74
took p out of following
Jun 12, 2026
9c5f1b6
fixed checkpoint desciptions
hosborne0501 Jun 16, 2026
c1e4840
added the word is
hosborne0501 Jun 16, 2026
49acb0e
fixed some of the grammar
hosborne0501 Jun 16, 2026
b687329
added a comma
hosborne0501 Jun 16, 2026
c9f2d81
rephrased to make more sense
hosborne0501 Jun 16, 2026
09ec7ee
added the words you can
hosborne0501 Jun 16, 2026
1ff9c1b
added the word the
hosborne0501 Jun 16, 2026
8d5531c
added a comma
hosborne0501 Jun 16, 2026
6e129ac
changed tile to file
hosborne0501 Jun 16, 2026
c01d153
More typos
hosborne0501 Jun 16, 2026
363f179
mostly grammar, some rephrasing
hosborne0501 Jun 18, 2026
4c94952
grammar
hosborne0501 Jun 18, 2026
f30f5ff
added link for gridding API documentation
hosborne0501 Jun 18, 2026
58a5f6d
updated beam info in the gridding section and updated deconvolution s…
hosborne0501 Jun 23, 2026
cb436e5
updated healpix section
hosborne0501 Jun 23, 2026
8b47c51
path no longer needed
tsnyder0 Jun 12, 2026
d68e87a
typo fixes and rewording to just before instrument
tsnyder0 Jun 17, 2026
03e8910
a few changes in calibration
tsnyder0 Jun 17, 2026
6653681
the rest of my typo and rewording changes
tsnyder0 Jun 18, 2026
a7e8ad0
Allow no model to be used if not calibrating
bhazelton Jun 9, 2026
ada2603
Don't set a default path to data or model files
bhazelton Jun 10, 2026
942ac37
Allow input path to be set in yaml, error if it is not set
bhazelton Jun 10, 2026
882dc03
allow for vis_model_arr to be None here (can't have subscript on None)
Jun 11, 2026
1dc8c69
set model_uv to None if vis_model_arr is None
Jun 11, 2026
ac7ce85
give model_uv option to be None
Jun 11, 2026
5346f08
set cal to None before option to calibrate and quickview function
Jun 11, 2026
b4dc88c
bitwise operator for array with multiple elements
tsnyder0 Jun 12, 2026
d742f25
change & back to and, add breakpoint
tsnyder0 Jun 12, 2026
0f447e7
add a.all() to != 0 piece, comment out breakpoint
tsnyder0 Jun 12, 2026
1e4faac
fixing the vis_model_arr[pol_i] line for gridding_dict
tsnyder0 Jun 23, 2026
2fe27cd
setting dirty image generate to only run if a model is provided
tsnyder0 Jun 23, 2026
fccfa8c
adding the if model_uv is not None for all other occurences of model …
tsnyder0 Jun 23, 2026
21b4554
when the model is None, make it possible to skip actions that require…
tsnyder0 Jun 23, 2026
56bc9ca
Adding info for running without a model into the usage documentation
tsnyder0 Jul 6, 2026
a15a47a
Adding allow_no_model info to the change log
tsnyder0 Jul 6, 2026
d8ef655
set cal as None so it has a value when called if calibrate visibiliti…
tsnyder0 Jul 15, 2026
ebe2364
set model_uv equal to None if not provided in the gridding checkpoint
tsnyder0 Jul 15, 2026
6114dbe
set vis_model_arr as None to begin with to avoid no value error (same…
tsnyder0 Jul 15, 2026
e295934
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 15, 2026
6d50da0
got rid of breakpoint
tsnyder0 Jul 30, 2026
6a5a325
use numpy meshgrid in phase_shift_uv_image
adampbeardsley Aug 10, 2026
6959344
remove unused import
adampbeardsley Aug 10, 2026
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
1 change: 1 addition & 0 deletions docs/source/changelog/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ in pyuvdata.UVBeam and just called directly.
* Analytic beams (subclassed from pyuvdata.AnalyticBeam) are now supported. Use
the new `analytic-beam-yaml` option to configure them.
* Added handling for `~` in paths in config yamls.
* Altered the necessary code to allow for a run to be done without a model.

### Bug Fixes
* Fixed a couple of bugs in beam and psf setup where the FFT direction or
Expand Down
11 changes: 5 additions & 6 deletions docs/source/installation/installation.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Installation

The dependencies on pyfhd to run with FHD have been removed, this makes installing
and `pyfhd` much easier than FHD. `pyfhd` is currently supported for Python 3.11+.
The dependencies on pyfhd to run with FHD have been removed. This makes installing `pyfhd` much easier than FHD. `pyfhd` is currently supported for Python 3.11+.

## Installing to do some development on pyfhd?

Expand All @@ -17,8 +16,8 @@ can't install (HPC environments). `uv` is best used for development as it suppor
a lot of features that Python developers of packages will appreciate (oh,
and by the way, _it's fast_, like _really fast_ at installing packages).
`conda` is best used where you need to use more than just Python, but perhaps a
combination of languages and tools, `conda` has the ability to manage any
compilers, CUDA versions, system tools etc.
combination of languages and tools. `conda` has the ability to manage many
compilers: CUDA versions, system tools etc.

### Make a venv environment
This assumes you have python installed.
Expand Down Expand Up @@ -54,7 +53,7 @@ conda env create --file environment.yml python=3.11 # You can make it 3.11 or 3.
conda activate pyfhd
```

You can deactivate the environment anytime by using `conda deactivate`
You can deactivate the environment at any time by using `conda deactivate`

### Installing pyfhd

Expand All @@ -70,7 +69,7 @@ You can Verify the installation using the version command
pyfhd -v
```

It should give you output that looks like this:
It should give you an output that looks like this:

```
________________________________________________________________________
Expand Down
184 changes: 82 additions & 102 deletions docs/source/tutorial/tutorial.rst

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/pyfhd/beam_setup/beam_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def beam_image(
group_id = (psf["id"][pol_i, 0, :]).astype(int)
# IDL psf structures often have "beam_gaussian_params" defined but set to zero
# have to test that it's present *and* nonzero
if "beam_gaussian_params" in psf and psf["beam_gaussian_params"] != 0:
if "beam_gaussian_params" in psf and (psf["beam_gaussian_params"] != 0).all():
beam_gaussian_params = psf["beam_gaussian_params"][:]
else:
beam_gaussian_params = None
Expand Down
51 changes: 30 additions & 21 deletions src/pyfhd/healpix/healpix_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from pyfhd.pyfhd_tools.pyfhd_utils import (
angle_difference,
histogram,
meshgrid,
region_grow,
)
from pyfhd.pyfhd_tools.unit_conv import radec_to_altaz, radec_to_pixel
Expand Down Expand Up @@ -451,8 +450,8 @@ def phase_shift_uv_image(obs: dict) -> NDArray[np.complex128]:
dx = (x - (obs["dimension"] / 2)) * (2 * np.pi / obs["dimension"])
dy = (y - (obs["elements"] / 2)) * (2 * np.pi / obs["dimension"])

xvals = meshgrid(obs["dimension"], obs["elements"], 1) - (obs["dimension"] / 2)
yvals = meshgrid(obs["dimension"], obs["elements"], 2) - (obs["elements"] / 2)
xvals, yvals = np.meshgrid(np.arange(obs["dimension"]) - (obs["dimension"] / 2),
np.arange(obs["elements"]) - (obs["elements"] / 2), indexing="ij")

phase = xvals * dx + yvals * dy
rephase_vals = np.cos(phase) + np.sin(phase) * 1j
Expand Down Expand Up @@ -567,6 +566,10 @@ def vis_model_freq_split(
]
if np.size(fi_use) == 0:
continue
if vis_model_arr is not None:
vis_model = vis_model_arr[polarization]
else:
vis_model = None
gridding_dict = visibility_grid(
vis_arr[polarization],
vis_weights[polarization],
Expand All @@ -576,7 +579,7 @@ def vis_model_freq_split(
polarization,
pyfhd_config,
logger,
model=vis_model_arr[polarization],
model=vis_model,
fi_use=fi_use,
bi_use=bi_use,
verbose_logging=False,
Expand All @@ -597,7 +600,10 @@ def vis_model_freq_split(
variance_uv_arr[fi] = (
gridding_dict["variance"] * rephase_use * gridding_dict["n_vis"]
)
model_uv_arr[fi] = gridding_dict["model_return"] * gridding_dict["n_vis"]
if vis_model is not None:
model_uv_arr[fi] = (
gridding_dict["model_return"] * gridding_dict["n_vis"]
)

if fft:
# No x_range and y_range hence no check for it here
Expand All @@ -619,13 +625,14 @@ def vis_model_freq_split(
degpix=obs["degpix"],
)
variance_arr[fi] *= gridding_dict["n_vis"]
model_arr[fi], _, _ = dirty_image_generate(
gridding_dict["model_return"],
pyfhd_config,
logger,
degpix=obs["degpix"],
)
model_arr[fi] *= gridding_dict["n_vis"]
if vis_model is not None:
model_arr[fi], _, _ = dirty_image_generate(
gridding_dict["model_return"],
pyfhd_config,
logger,
degpix=obs["degpix"],
)
model_arr[fi] *= gridding_dict["n_vis"]
else:
dirty_arr[fi] = gridding_dict["image_uv"] * gridding_dict["n_vis"]
weights_arr[fi] = (
Expand All @@ -634,7 +641,8 @@ def vis_model_freq_split(
variance_arr[fi] = (
gridding_dict["variance"] * rephase_use * gridding_dict["n_vis"]
)
model_arr[fi] = gridding_dict["model_return"] * gridding_dict["n_vis"]
if vis_model is not None:
model_arr[fi] = gridding_dict["model_return"] * gridding_dict["n_vis"]
obs["n_vis"] = n_vis_use

if save_uvf:
Expand Down Expand Up @@ -673,15 +681,16 @@ def vis_model_freq_split(
f"{pyfhd_config['obs_id']}_{uvf_name}_{obs['pol_names'][polarization]}_variance_uv_arr_gridded_uvf.h5",
logger=logger,
)
save(
Path(
uvf_dir,
if vis_model is not None:
save(
Path(
uvf_dir,
f"{pyfhd_config['obs_id']}_{uvf_name}_{obs['pol_names'][polarization]}_model_uv_arr_gridded_uvf.h5",
),
h5_save_dict,
f"{pyfhd_config['obs_id']}_{uvf_name}_{obs['pol_names'][polarization]}_model_uv_arr_gridded_uvf.h5",
),
h5_save_dict,
f"{pyfhd_config['obs_id']}_{uvf_name}_{obs['pol_names'][polarization]}_model_uv_arr_gridded_uvf.h5",
logger=logger,
)
logger=logger,
)

cube_split = {
"obs": obs,
Expand Down
93 changes: 49 additions & 44 deletions src/pyfhd/io/pyfhd_quickview.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,14 @@ def quickview(
obs: dict,
psf: dict,
params: dict,
cal: dict,
cal: dict | None,
vis_arr: NDArray[np.complex128],
vis_weights: NDArray[np.float64],
image_uv: NDArray[np.complex128],
weights_uv: NDArray[np.complex128],
variance_uv: NDArray[np.float64],
uniform_filter_uv: NDArray[np.float64],
model_uv: NDArray[np.complex128],
model_uv: NDArray[np.complex128] | None,
pyfhd_config: dict,
logger: Logger,
) -> None:
Expand Down Expand Up @@ -273,26 +273,28 @@ def quickview(
beam_ptr=beam_base_out[pol_i],
)
filter_arr[pol_i] = filter
instr_model_arr[pol_i], filter, _ = dirty_image_generate(
model_uv[pol_i],
pyfhd_config,
logger,
uniform_filter_uv=uniform_filter_uv,
degpix=obs_out["degpix"],
weights=weights_uv[pol_i],
pad_uv_image=pyfhd_config["pad_uv_image"],
filter=filter,
not_real=complex_flag,
beam_ptr=beam_base_out[pol_i],
)
if model_uv is not None:
instr_model_arr[pol_i], filter, _ = dirty_image_generate(
model_uv[pol_i],
pyfhd_config,
logger,
uniform_filter_uv=uniform_filter_uv,
degpix=obs_out["degpix"],
weights=weights_uv[pol_i],
pad_uv_image=pyfhd_config["pad_uv_image"],
filter=filter,
not_real=complex_flag,
beam_ptr=beam_base_out[pol_i],
)
renorm_factor = get_image_renormalization(
obs_out, weights_uv, beam_base_out, filter_arr, pyfhd_config, logger
)
# Reshape renorm factor to multiply per polarization without loop to [obs["n_pol"], 1, 1]
renorm_factor = np.expand_dims(renorm_factor.reshape([obs_out["n_pol"], 1]), -1)
instr_dirty_arr *= renorm_factor
instr_model_arr *= renorm_factor
instr_residual_arr = instr_dirty_arr - instr_model_arr
if model_uv is not None:
instr_model_arr *= renorm_factor
instr_residual_arr = instr_dirty_arr - instr_model_arr
# Get the pol_names
pol_names = obs["pol_names"]
# The cross-polarization XY and YX images are both complex, but are conjugate mirrors of each other
Expand Down Expand Up @@ -387,9 +389,10 @@ def quickview(
png_output.mkdir(exist_ok=True)
for pol_i in range(obs["n_pol"]):
logger.info(f"Saving the FITS files for polarization {pol_names[pol_i]}")
instr_residual = instr_residual_arr[pol_i] * beam_correction_out[pol_i]
instr_dirty = instr_dirty_arr[pol_i] * beam_correction_out[pol_i]
instr_model = instr_model_arr[pol_i] * beam_correction_out[pol_i]
if model_uv is not None:
instr_residual = instr_residual_arr[pol_i] * beam_correction_out[pol_i]
instr_model = instr_model_arr[pol_i] * beam_correction_out[pol_i]
beam_use = beam_base_out[pol_i]

# Write the fits files for the dirty images
Expand All @@ -400,20 +403,21 @@ def quickview(
fits_file_apparent.writeto(
Path(fits_output, f"{instr_dirty_name}.fits"), overwrite=True
)
fits_file_apparent.data = instr_model
instr_model_name = (
f"{pyfhd_config['obs_id']}_{filter_name}_model_{pol_names[pol_i]}"
)
fits_file_apparent.writeto(
Path(fits_output, f"{instr_model_name}.fits"), overwrite=True
)
fits_file_apparent.data = instr_residual
instr_residual_name = (
f"{pyfhd_config['obs_id']}_{filter_name}_residual_{pol_names[pol_i]}"
)
fits_file_apparent.writeto(
Path(fits_output, f"{instr_residual_name}.fits"), overwrite=True
)
if model_uv is not None:
fits_file_apparent.data = instr_model
instr_model_name = (
f"{pyfhd_config['obs_id']}_{filter_name}_model_{pol_names[pol_i]}"
)
fits_file_apparent.writeto(
Path(fits_output, f"{instr_model_name}.fits"), overwrite=True
)
fits_file_apparent.data = instr_residual
instr_residual_name = (
f"{pyfhd_config['obs_id']}_{filter_name}_residual_{pol_names[pol_i]}"
)
fits_file_apparent.writeto(
Path(fits_output, f"{instr_residual_name}.fits"), overwrite=True
)
fits_file.data = beam_use
beam_name = f"{pyfhd_config['obs_id']}_beam_{pol_names[pol_i]}"
fits_file.writeto(Path(fits_output, f"{beam_name}.fits"), overwrite=True)
Expand All @@ -431,18 +435,19 @@ def quickview(
title=f"Dirty Image {pol_names[pol_i]}",
logger=logger,
)
plot_fits_image(
Path(fits_output, f"{instr_model_name}.fits"),
Path(png_output, f"{instr_model_name}.png"),
title=f"Model Image {pol_names[pol_i]}",
logger=logger,
)
plot_fits_image(
Path(fits_output, f"{instr_residual_name}.fits"),
Path(png_output, f"{instr_residual_name}.png"),
title=f"Residual Image {pol_names[pol_i]}",
logger=logger,
)
if model_uv is not None:
plot_fits_image(
Path(fits_output, f"{instr_model_name}.fits"),
Path(png_output, f"{instr_model_name}.png"),
title=f"Model Image {pol_names[pol_i]}",
logger=logger,
)
plot_fits_image(
Path(fits_output, f"{instr_residual_name}.fits"),
Path(png_output, f"{instr_residual_name}.png"),
title=f"Residual Image {pol_names[pol_i]}",
logger=logger,
)
plot_fits_image(
Path(fits_output, f"{beam_name}.fits"),
Path(png_output, f"{beam_name}.png"),
Expand Down
37 changes: 26 additions & 11 deletions src/pyfhd/pyfhd.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,9 @@ def main():
f"Checkpoint Loaded: Uncalibrated visibility parameters, array and weights and the observation metadata dictionary loaded from {Path(pyfhd_config['output_dir'], 'obs_checkpoint.h5')}"
)

# Give cal and vis_model_arr a value to avoid no value error (Default = None, but can be overwritten below)
cal = None
vis_model_arr = None
# If the calibration checkpoint exists, load it now before loading in the beam
# to get the observation metadata and visibility parameters
if pyfhd_config["calibrate_checkpoint"]:
Expand Down Expand Up @@ -338,16 +341,20 @@ def main():
logger,
)

# Get the vis_model_arr from a UVFITS file or SAV files and flag any issues
vis_model_arr_start = time.time()
vis_model_arr = vis_model_transfer(pyfhd_config, obs, params, logger)
vis_model_arr_end = time.time()
_print_time_diff(
vis_model_arr_start,
vis_model_arr_end,
"Model Imported and Flagged From UVFITS",
logger,
)
if (
pyfhd_config["calibrate_visibilities"]
or pyfhd_config["model_file_path"] is not None
):
# Get the vis_model_arr from a UVFITS file or SAV files and flag any issues
vis_model_arr_start = time.time()
vis_model_arr = vis_model_transfer(pyfhd_config, obs, params, logger)
vis_model_arr_end = time.time()
_print_time_diff(
vis_model_arr_start,
vis_model_arr_end,
"Model Imported and Flagged From UVFITS",
logger,
)

# Skipped initializing the cal structure as it mostly just copies values from the obs, params, config and the skymodel from FHD
# However, there is resulting cal structure for logging and output purposes to store the resulting gain and any other associated
Expand Down Expand Up @@ -511,6 +518,8 @@ def main():
(obs["n_pol"], obs["elements"], obs["dimension"]),
dtype=np.complex128,
)
else:
model_uv = None
# Since it's done per polarization, we can do multi-processing if it's not fast enough
for pol_i in range(obs["n_pol"]):
logger.info(
Expand All @@ -524,6 +533,10 @@ def main():
no_conjugate = True
else:
no_conjugate = False
if vis_model_arr is not None:
vis_model = vis_model_arr[pol_i]
else:
vis_model = None
gridding_dict = visibility_grid(
vis_arr[pol_i],
vis_weights[pol_i],
Expand All @@ -535,7 +548,7 @@ def main():
logger,
uniform_flag=uniform_flag,
no_conjugate=no_conjugate,
model=vis_model_arr[pol_i],
model=vis_model,
)
if len(gridding_dict.keys()) != 0:
image_uv[pol_i] = gridding_dict["image_uv"]
Expand Down Expand Up @@ -605,6 +618,8 @@ def main():
uniform_filter_uv = grid_checkpoint["uniform_filter_uv"]
if "model_uv" in grid_checkpoint:
model_uv = grid_checkpoint["model_uv"]
else:
model_uv = None
del grid_checkpoint
logger.info(
f"Checkpoint Loaded: The Gridded UV Planes loaded from {Path(pyfhd_config['output_dir'], 'gridding_checkpoint.h5')}"
Expand Down
Loading
Loading