
Beam cut analysis tutorial¶
Beam cuts are a commonly used measurement to infer properties of the telescope beam without having to resort to a much more expensive full holography. With that in mind astrohack.beamcut was created with an intent to aid VLA operations and in the near future the commissioning of the ngVLA prototype antenna. In this tutorial we go through the astrohack reduction of a calibrated ms.
[1]:
import os
try:
import astrohack
print("AstroHACK version", astrohack.__version__, "already installed.")
except ImportError as e:
print(e)
print("Installing AstroHACK")
os.system("pip install astrohack")
import astrohack
print("astrohack version", astrohack.__version__, " installed.")
AstroHACK version 1.0.1 already installed.
Download tutorial data¶
[2]:
# Download data.
import toolviper
basename = "kband_beamcut_small"
ms_name = f"data/{basename}.ms"
toolviper.utils.data.update()
toolviper.utils.data.download(file=f"{basename}.ms", folder="data")
[2026-03-19 15:38:59,537] INFO astrohack: Updating file metadata information ...
[2026-03-19 15:38:59,751] INFO astrohack: Module path: /export/home/arya/miniforge3/envs/casadev/lib/python3.12/site-packages/toolviper
[2026-03-19 15:38:59,755] INFO astrohack: Downloading from [cloudflare] ....
Download List ──────────────────────── kband_beamcut_small.ms
Starting local Dask client¶
[3]:
from toolviper.dask.client import local_client
parallel = True
if parallel:
client = local_client(cores=4, memory_limit="4GB")
print(client)
else:
client = None
[2026-03-19 15:39:00,448] INFO astrohack: Module path: /export/home/arya/miniforge3/envs/casadev/lib/python3.12/site-packages/toolviper
[2026-03-19 15:39:00,452] WARNING astrohack: It is recommended that the local cache directory be set using the dask_local_dir parameter.
[2026-03-19 15:39:01,384] INFO astrohack: Client <MenrvaClient: 'tcp://127.0.0.1:39903' processes=4 threads=4, memory=14.90 GiB>
<MenrvaClient: 'tcp://127.0.0.1:39903' processes=4 threads=4, memory=14.90 GiB>
Extract beam cut data to Astrohack formats¶
In this step we use astrohack.extract_pointing and astrohack.extract_holog to create an astrohack holog.zarr holography file from the ms. The usage of these functions originally created for holography purposes is needed to simplify the process of creating the beamcuts as astrohack.extract_pointing determines which antennas were moving and astrohack.extract_holog matches visibilities to pointing data in a convenient way.
[4]:
from astrohack import extract_pointing, extract_holog
point_name = f"data/{basename}.point.zarr"
holog_name = f"data/{basename}.holog.zarr"
# Extract pointing data to an astrohack file format
point_mds = extract_pointing(
ms_name,
point_name,
overwrite=True,
parallel=parallel
)
# Extract visibilities to an astrohack file format
holog_mds = extract_holog(
ms_name,
point_name,
holog_name,
data_column="DATA", # This applies to this dataset only as it has been split
overwrite=True,
parallel=parallel,
)
[2026-03-19 15:39:01,389] INFO astrohack: Module path: /export/home/arya/work/Holography-1022/astrohack/src/astrohack
[2026-03-19 15:39:01,391] INFO astrohack: Creating output file name: data/kband_beamcut_small.point.zarr
[2026-03-19 15:39:02,436] INFO astrohack: Consolidating data/kband_beamcut_small.point.zarr...
[2026-03-19 15:39:02,538] INFO astrohack: Module path: /export/home/arya/work/Holography-1022/astrohack/src/astrohack
[2026-03-19 15:39:02,541] INFO astrohack: Creating output file name: data/kband_beamcut_small.holog.zarr
[2026-03-19 15:39:02,564] INFO astrohack: Pre-processing ddi: 0, scans: [8 ... 13]
[2026-03-19 15:39:02,564] INFO astrohack: Pre-processing ddi: 1, scans: [8 ... 13]
[2026-03-19 15:39:03,854] INFO astrohack: EA15: DDI 0: Suggested cell size 1.64 amin, FOV: (19.72 amin, 19.76 amin)
[2026-03-19 15:39:03,856] INFO astrohack: EA17: DDI 0: Suggested cell size 1.64 amin, FOV: (19.73 amin, 19.76 amin)
[2026-03-19 15:39:04,844] INFO astrohack: Finished extracting holography chunk for DDI 0, map_0.
[2026-03-19 15:39:05,154] INFO astrohack: EA15: DDI 1: Suggested cell size 1.63 amin, FOV: (19.72 amin, 19.76 amin)
[2026-03-19 15:39:05,155] INFO astrohack: EA17: DDI 1: Suggested cell size 1.63 amin, FOV: (19.73 amin, 19.76 amin)
[2026-03-19 15:39:05,212] INFO astrohack: Finished extracting holography chunk for DDI 1, map_0.
[2026-03-19 15:39:05,213] INFO astrohack: Consolidating data/kband_beamcut_small.holog.zarr...
Running beamcut¶
astrohack.beamcut executes the following steps for each antenna & DDI combination: 1. Break up visibility data onto different cuts based on scans 2. Determine the cut direction based on the L and M distributions in each cut 3. Flatten the cut onto a distance from pointing center axis 4. Fit multiple gaussians to the multiple lobes present in the beam 5. Identify the primary beam and the first sidelobes 6. Measure primary beam offset and the first sidelobe ratio 7. If a destination is given,
plots of the beam cut in the sky, the beam in amplitude and the beam in attenuation are produced along with a table with the properties of all beam lobes as measured with the gaussian fit.
The measurements in step 6 are indeed the crux of the matter, as the offset of the primary beam is an important measure of antenna optical alignment and the first sidelobe ratio is linked to focus offsets.
[5]:
from astrohack import beamcut
beamcut_name = f"data/{basename}.beamcut.zarr"
beamcut_mds = beamcut(
holog_name,
beamcut_name,
destination=None, # This parameter is to be filled with a destination directory for beamcut products.
overwrite=True,
parallel=parallel,
)
[2026-03-19 15:39:05,266] INFO astrohack: Module path: /export/home/arya/work/Holography-1022/astrohack/src/astrohack
[2026-03-19 15:39:05,270] INFO astrohack: Creating output file name: data/kband_beamcut_small.beamcut.zarr
[2026-03-19 15:39:06,861] INFO worker_2: processing EA15: DDI 1
[2026-03-19 15:39:07,323] INFO worker_0: processing EA15: DDI 0
[2026-03-19 15:39:07,324] INFO worker_1: processing EA17: DDI 0
[2026-03-19 15:39:07,324] INFO worker_3: processing EA17: DDI 1
[2026-03-19 15:39:07,863] INFO astrohack: Consolidating data/kband_beamcut_small.beamcut.zarr...
Interact with a beamcut object¶
The beamcut object is the return of astrohack.beamcut and can also be obtained by opening a beamcut.zarr file on disk by using astrohack.open_beamcut. This object allows the user to directly manipulate the data contained in the related file on disk, as well as requesting new plots and reports to be generated from the data on disk.
[6]:
from astrohack import open_beamcut
beamcut_mds = open_beamcut(beamcut_name)
beamcut_mds.summary()
################################################################################
### Summary for: ###
### data/kband_beamcut_small.beamcut.zarr ###
################################################################################
Data origin:
creation_time: 2026-03-19 15:39:05 MDT
creator_function: beamcut
origin: astrohack
version: 1.0.1
Input Parameters:
+--------------+---------------------------------------+
| Parameter | Value |
+--------------+---------------------------------------+
| ant | all |
| azel_unit | deg |
| beamcut_name | data/kband_beamcut_small.beamcut.zarr |
| ddi | all |
| destination | None |
| display | False |
| dpi | 300 |
| holog_name | data/kband_beamcut_small.holog.zarr |
| lm_unit | amin |
| overwrite | True |
| parallel | True |
| phase_scale | None |
| phase_unit | deg |
| y_scale | None |
+--------------+---------------------------------------+
Available methods:
+------------------------------+-----------------------------------------------+
| Methods | Description |
+------------------------------+-----------------------------------------------+
| add_node | Add a node to the data tree file structure, |
| | however this node is not yet consolidated |
| | into the data tree structure, |
| | consolidate must be called to integrate all |
| | nodes writen by add_node onto the tree |
| | structure. |
| consolidate | Traverse own file structure on disk |
| | consolidating metadata to create a unified |
| | data tree entity. |
| create_beam_fit_report | Create reports on the parameters of the |
| | gaussians fitted to the beamcut. |
| create_from_input_parameters | Create an AstrohackBaseFile object from a |
| | filename and initializes xdtree root |
| | attributes. |
| is_close_to | Tests if self and other_mds are close to each |
| | other. |
| items | Get children items |
| keys | Get children keys |
| observation_summary | Create a Summary of observation information |
| open | Open Base file. |
| plot_beam_cuts_over_sky | Plot beamcuts contained in the beamcut_mds |
| | over the sky |
| plot_beamcut_in_amplitude | Plot beamcuts contained in the beamcut_mds in |
| | amplitude |
| plot_beamcut_in_attenuation | Plot beamcuts contained in the beamcut_mds in |
| | attenuation |
| plot_beamcut_in_phase | Plot beamcuts contained in the beamcut_mds in |
| | phase |
| summary | Prints summary of this Astrohack File object, |
| | with available data, attributes and methods |
| values | Get children values |
| write | Write mds to disk by saving the data tree to |
| | a file |
+------------------------------+-----------------------------------------------+
Data Contents:
+----------+-------+--------------------+
| Antenna | DDI | Cut |
+----------+-------+--------------------+
| ant_ea15 | ddi_0 | ['cut_0', 'cut_1'] |
| ant_ea15 | ddi_1 | ['cut_0', 'cut_1'] |
| ant_ea17 | ddi_0 | ['cut_0', 'cut_1'] |
| ant_ea17 | ddi_1 | ['cut_0', 'cut_1'] |
+----------+-------+--------------------+
Observation summary¶
The observation summary contains relevant observation information for each antenna and DDI combination as well as cut timing and direction.
[7]:
beamcut_mds.observation_summary("data/beamcut_summary.txt")
[2026-03-19 15:39:08,006] INFO astrohack: Module path: /export/home/arya/work/Holography-1022/astrohack/src/astrohack
############################################################
### ant_ea15, ddi_0 ###
############################################################
General:
Telescope name => EVLA
Antenna name => ea15
Station => N04
Reference antennas => ['ea23 @ N08']
Source => HOLORASTER
Phase center => 16h42m58.810s +39°48m36.990s [FK5]
Az el info => @ l,m = (0,0), Az, El = (294.3, 45.4) [deg]
Start time => 25 Nov 2025, 23:10:56 (UTC)
Stop time => 25 Nov 2025, 23:20:42 (UTC)
Duration => 9 min, 46.05 sec
Spectral:
Channel width => 128.000 MHz
Frequency range => 21.315 GHz to 21.443 GHz
Number of channels => 1
Rep. frequency => 21.380 GHz
Rep. wavelength => 1.402 cm
Beam:
Cell size => 1.64 amin
Grid size => 13 by 13 pixels
L extent => From -9.87 amin to 9.83 amin
M extent => From -9.80 amin to 9.89 amin
cut_0:
El. cut (S -> N) at 2025-11-25 23:16 UTC
cut_1:
Az. cut (W -> E) at 2025-11-25 23:19 UTC
############################################################
### ant_ea15, ddi_1 ###
############################################################
General:
Telescope name => EVLA
Antenna name => ea15
Station => N04
Reference antennas => ['ea23 @ N08']
Source => HOLORASTER
Phase center => 16h42m58.810s +39°48m36.990s [FK5]
Az el info => @ l,m = (0,0), Az, El = (294.3, 45.4) [deg]
Start time => 25 Nov 2025, 23:10:56 (UTC)
Stop time => 25 Nov 2025, 23:20:42 (UTC)
Duration => 9 min, 46.05 sec
Spectral:
Channel width => 128.000 MHz
Frequency range => 21.443 GHz to 21.571 GHz
Number of channels => 1
Rep. frequency => 21.508 GHz
Rep. wavelength => 1.394 cm
Beam:
Cell size => 1.63 amin
Grid size => 13 by 13 pixels
L extent => From -9.87 amin to 9.83 amin
M extent => From -9.80 amin to 9.89 amin
cut_0:
El. cut (S -> N) at 2025-11-25 23:16 UTC
cut_1:
Az. cut (W -> E) at 2025-11-25 23:19 UTC
############################################################
### ant_ea17, ddi_0 ###
############################################################
General:
Telescope name => EVLA
Antenna name => ea17
Station => W04
Reference antennas => ['ea11 @ W08']
Source => HOLORASTER
Phase center => 16h42m58.810s +39°48m36.990s [FK5]
Az el info => @ l,m = (0,0), Az, El = (294.2, 45.4) [deg]
Start time => 25 Nov 2025, 23:10:56 (UTC)
Stop time => 25 Nov 2025, 23:20:42 (UTC)
Duration => 9 min, 46.05 sec
Spectral:
Channel width => 128.000 MHz
Frequency range => 21.315 GHz to 21.443 GHz
Number of channels => 1
Rep. frequency => 21.380 GHz
Rep. wavelength => 1.402 cm
Beam:
Cell size => 1.64 amin
Grid size => 13 by 13 pixels
L extent => From -9.88 amin to 9.85 amin
M extent => From -9.88 amin to 9.87 amin
cut_0:
El. cut (S -> N) at 2025-11-25 23:16 UTC
cut_1:
Az. cut (W -> E) at 2025-11-25 23:19 UTC
############################################################
### ant_ea17, ddi_1 ###
############################################################
General:
Telescope name => EVLA
Antenna name => ea17
Station => W04
Reference antennas => ['ea11 @ W08']
Source => HOLORASTER
Phase center => 16h42m58.810s +39°48m36.990s [FK5]
Az el info => @ l,m = (0,0), Az, El = (294.2, 45.4) [deg]
Start time => 25 Nov 2025, 23:10:56 (UTC)
Stop time => 25 Nov 2025, 23:20:42 (UTC)
Duration => 9 min, 46.05 sec
Spectral:
Channel width => 128.000 MHz
Frequency range => 21.443 GHz to 21.571 GHz
Number of channels => 1
Rep. frequency => 21.508 GHz
Rep. wavelength => 1.394 cm
Beam:
Cell size => 1.63 amin
Grid size => 13 by 13 pixels
L extent => From -9.88 amin to 9.85 amin
M extent => From -9.88 amin to 9.87 amin
cut_0:
El. cut (S -> N) at 2025-11-25 23:16 UTC
cut_1:
Az. cut (W -> E) at 2025-11-25 23:19 UTC
Interacting with datatree¶
Below its is shown how to interact directly with the data tree contained in the beamcut_mds by simply especifying the keys of interest to arrive at the desired xarray dataset.
[8]:
ea17_ddi_0 = beamcut_mds["ant_ea17"]["ddi_0"]["cut_0"]
ea17_ddi_0
[8]:
<xarray.DataTree 'cut_0'>
Group: /ant_ea17/ddi_0/cut_0
Dimensions: (lm_dist: 487, time: 487, lm: 2)
Coordinates:
* lm_dist (lm_dist) float64 4kB -0.002873 -0.002863 ... 0.002871
* time (time) float64 4kB 5.271e+09 5.271e+09 ... 5.271e+09 5.271e+09
Dimensions without coordinates: lm
Data variables:
LL_amp_fit (lm_dist) float64 4kB dask.array<chunksize=(487,), meta=np.ndarray>
LL_amplitude (lm_dist) float64 4kB dask.array<chunksize=(487,), meta=np.ndarray>
LL_phase (lm_dist) float64 4kB dask.array<chunksize=(487,), meta=np.ndarray>
LL_weight (lm_dist) float64 4kB dask.array<chunksize=(487,), meta=np.ndarray>
RR_amp_fit (lm_dist) float64 4kB dask.array<chunksize=(487,), meta=np.ndarray>
RR_amplitude (lm_dist) float64 4kB dask.array<chunksize=(487,), meta=np.ndarray>
RR_phase (lm_dist) float64 4kB dask.array<chunksize=(487,), meta=np.ndarray>
RR_weight (lm_dist) float64 4kB dask.array<chunksize=(487,), meta=np.ndarray>
lm_offsets (time, lm) float64 8kB dask.array<chunksize=(487, 2), meta=np.ndarray>
Attributes: (12/19)
LL_amp_fit_pars: [-0.0020771884159861693, 0.16536998921100482, ...
LL_first_side_lobe_ratio: 0.9506975351868078
LL_fit_succeeded: True
LL_n_peaks: 5
LL_pb_center: 2.0066879975923337e-05
LL_pb_fwhm: 0.0007089940968873998
... ...
available_corrs: ['RR', 'LL']
direction: El. cut (S -> N)
lm_angle: 8.043802534404685e-06
scan_number: 8
time_string: 2025-11-25 23:16
xlabel: Elevation offsetCreating beam cut plots¶
Creating plots from the data in a beamcut_mds can be done by using the plotting methods.
Beamcut Sky coverage¶
The first of these methods is plot_beam_cuts_over_sky which creates a plot of all pointings for each antenna and DDI combination, colored by cut (scan).
[9]:
beamcut_exports = "beamcut_exports"
beamcut_mds.plot_beam_cuts_over_sky(
beamcut_exports,
ant="ea17",
ddi=0,
display=True,
parallel=False,
)
[2026-03-19 15:39:08,155] INFO astrohack: Module path: /export/home/arya/work/Holography-1022/astrohack/src/astrohack
Amplitude plots¶
The next plotting method is plot_beamcut_in_amplitude which plots the beamcut data in amplitude for each correlation and cut while identifying the lobes marked in the report. Key properties are also shown, the primary beam offset, FWHM and the first side lobe ratio (FSLR).
[10]:
beamcut_mds.plot_beamcut_in_amplitude(
beamcut_exports,
ant="ea17",
ddi=0,
display=True,
parallel=False,
)
[2026-03-19 15:39:08,668] INFO astrohack: Module path: /export/home/arya/work/Holography-1022/astrohack/src/astrohack
Phase plots¶
Another way of visualizing a beam cut is through the plot_beamcut_in_phase method. Here the phase accross the beam cut is plotted which can give important hints to disentangle between focal plane misalignments and pointing errors. Also present in this plot is the multi-gaussian fit to the primary beam in amplitude, which help identify when phase regimes change, usually at at null.
[11]:
beamcut_mds.plot_beamcut_in_phase(
beamcut_exports,
ant="ea17",
ddi=0,
display=True,
parallel=False,
)
[2026-03-19 15:39:10,543] INFO astrohack: Module path: /export/home/arya/work/Holography-1022/astrohack/src/astrohack
Attenuation plots¶
The remaining plotting method is plot_beamcut_in_attenuation which plots each cut in attenuation in a way that let us compare the parallel hands directly. Also present in this plot are the primary beam offset, FWHM and the first side lobe ratio (FSLR), however the numbers shown here are the average between the 2 parallel hands.
[12]:
beamcut_mds.plot_beamcut_in_attenuation(
beamcut_exports,
ant="ea17",
ddi=0,
display=True,
parallel=False,
)
[2026-03-19 15:39:12,440] INFO astrohack: Module path: /export/home/arya/work/Holography-1022/astrohack/src/astrohack
Create report on beam cut gaussian fit¶
Lastly, create_beam_fit_report produces a report with the fitted parameters to each of the lobes present in the beam.
[13]:
beamcut_mds.create_beam_fit_report(beamcut_exports, ant="ea17", ddi=0, parallel=False)
with open("beamcut_exports/beamcut_report_ant_ea17_ddi_0.txt", "r") as infile:
for line in infile:
print(line[:-1])
[2026-03-19 15:39:13,304] INFO astrohack: Module path: /export/home/arya/work/Holography-1022/astrohack/src/astrohack
- Beam cut for EA17, DDI 0, $\nu$ = 21.380 GHz, Az ~ 294.3 deg, El ~ 45.5 deg
- RR El. cut (S -> N), 2025-11-25 23:16 UTC, Beam fit results:
+----+---------------+---------------+-------------+------------------+
| Id | Center [amin] | Amplitude [ ] | FWHM [amin] | Attenuation [dB] |
+----+---------------+---------------+-------------+------------------+
| 1) | -7.328 | 0.092 | 3.118 | -10.275 |
| 2) | -3.378 | 0.173 | 1.229 | -7.546 |
| 3) | -0.003 | 0.999 | 2.417 | 0.066 |
| 4) | 3.401 | 0.234 | 1.353 | -6.239 |
| 5) | 7.366 | 0.098 | 2.800 | -10.011 |
+----+---------------+---------------+-------------+------------------+
- LL El. cut (S -> N), 2025-11-25 23:16 UTC, Beam fit results:
+----+---------------+---------------+-------------+------------------+
| Id | Center [amin] | Amplitude [ ] | FWHM [amin] | Attenuation [dB] |
+----+---------------+---------------+-------------+------------------+
| 1) | -7.141 | 0.165 | 2.850 | -9.265 |
| 2) | -3.314 | 0.279 | 1.253 | -6.997 |
| 3) | 0.069 | 1.406 | 2.437 | 0.031 |
| 4) | 3.487 | 0.293 | 1.317 | -6.778 |
| 5) | 7.388 | 0.104 | 2.898 | -11.275 |
+----+---------------+---------------+-------------+------------------+
- RR Az. cut (W -> E), 2025-11-25 23:19 UTC, Beam fit results:
+----+---------------+---------------+-------------+------------------+
| Id | Center [amin] | Amplitude [ ] | FWHM [amin] | Attenuation [dB] |
+----+---------------+---------------+-------------+------------------+
| 1) | -7.481 | 0.078 | 2.799 | -11.231 |
| 2) | -3.509 | 0.227 | 1.297 | -6.606 |
| 3) | -0.055 | 1.047 | 2.460 | 0.030 |
| 4) | 3.314 | 0.241 | 1.375 | -6.343 |
| 5) | 7.251 | 0.100 | 2.646 | -10.191 |
+----+---------------+---------------+-------------+------------------+
- LL Az. cut (W -> E), 2025-11-25 23:19 UTC, Beam fit results:
+----+---------------+---------------+-------------+------------------+
| Id | Center [amin] | Amplitude [ ] | FWHM [amin] | Attenuation [dB] |
+----+---------------+---------------+-------------+------------------+
| 1) | -7.382 | 0.070 | 3.511 | -11.667 |
| 2) | -3.413 | 0.219 | 1.317 | -6.734 |
| 3) | 0.025 | 1.044 | 2.448 | 0.055 |
| 4) | 3.396 | 0.233 | 1.371 | -6.459 |
| 5) | 7.221 | 0.105 | 2.515 | -9.916 |
+----+---------------+---------------+-------------+------------------+
[14]:
if parallel:
client.close()