Open In Colab

astrohack

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 offset

Creating 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
../_images/tutorials_beamcut_tutorial_19_1.png

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
../_images/tutorials_beamcut_tutorial_21_1.png

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
../_images/tutorials_beamcut_tutorial_23_1.png

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
../_images/tutorials_beamcut_tutorial_25_1.png

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()