.. _xst_reading_doc:
Cross-Correlation STatistics (XST)
==================================
`NenuFAR `_ produces
cross-correlation statistics data called `XST `_
that can be converted to Measurement Set format suited for radio
imaging softwares (this is the 'proto-imager' mode, in contrast
with the proper imager mode using data from the `NICKEL correlator `_).
When NenuFAR does not observe in XST mode, the cross-correlations
are saved with 10 sec integration time and 16 sub-bands of
195.3125 kHz in 5-min exposure time binary files.
They are immediately converted in images to be displayed as the
`NenuFAR-TV `_ and near-field images are also produced.
NenuFAR Cross-Correlation Statistics come in two flavours,
namely XST FITS files and NenuFAR-TV binary files.
They can be read and analyzed by :class:`~nenupy.io.xst.XST`
and :class:`~nenupy.io.xst.NenufarTV` respectively.
Both classes inherit from the base class :class:`~nenupy.io.xst.Crosslet`,
which contains the methods to extract, and perform basic imaging
and beamforming operations.
XST selection
-------------
Reading a NenuFAR XST file is straightforward with :class:`~nenupy.io.xst.XST`.
The general information may be displayed using :meth:`~nenupy.io.xst.XST.info`,
and some specific file type dependent properties may be accessed by
dedicated attributes, such as :attr:`~nenupy.io.xst.XST.mini_arrays`:
.. code-block:: python
>>> from nenupy.io.xst import XST
>>> xst = XST(".../nenupy/tests/test_data/XST.fits")
>>> xst.info()
file: '.../nenupy/tests/test_data/XST.fits'
frequency (1, 16): 68.5546875 MHz -- 79.296875 MHz
time (1,): 2020-02-19T18:00:03.000 -- 2020-02-19T18:00:03.000
data: (1, 16, 6105)
>>> xst.mini_arrays
array([ 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
52, 53, 54, 55], dtype='>i2')
Data selection can then be applied either using :meth:`~nenupy.io.xst.XST.get`
or :meth:`~nenupy.io.xst.XST.get_stokes` methods. The former accesses
the raw cross-correlations stored in the FITS files (namely the
polarizations "XX", "XY", "YX" and "YY"), the latter adds a Stokes
parameter computation layer.
.. code-block:: python
>>> from nenupy.io.xst import XST
>>> xst = XST(".../nenupy/tests/test_data/XST.fits")
>>> xx_data = xst.get(
polarization="XX",
miniarray_selection=None,
frequency_selection=">=20MHz",
time_selection=">=2020-02-19T18:00:00"
)
Both methods return an instance of the class :class:`~nenupy.io.xst.XST_Slice`
which contains the methods to further process the data, as well as plotting
tools such as :meth:`~nenupy.io.xst.XST_Slice.plot_correlaton_matrix`:
.. code-block:: python
>>> xx_data.plot_correlaton_matrix()
.. figure:: ../_images/io_images/correlation_matrix.png
:width: 450
:align: center
Cross-correlation matrix.
.. _xst_uv_coverage_doc:
UV coverage from XST
--------------------
UV coverage (:class:`~nenupy.astro.uvw.UV_Coverage`) corresponding with
the loded XST data can be easily displayed using its classmethod
:meth:`~nenupy.astro.uvw.UV_Coverage.from_xst`:
.. code-block:: python
>>> from nenupy.io.xst import XST
>>> from nenupy.astro.uvw import UV_Coverage
>>> xst = XST(".../nenupy/tests/test_data/XST.fits")
>>> uvw = UV_Coverage.from_xst(xst)
>>> uvw.plot()
.. figure:: ../_images/io_images/uv_coverage_nenucore.png
:width: 450
:align: center
:meth:`~nenupy.astro.uvw.UV_Coverage.from_xst` takes also in input an
:class:`~nenupy.io.xst.XST_Slice`, allowing for UV coverage display
of selected visibilities:
.. code-block:: python
>>> from nenupy.io.xst import XST
>>> from nenupy.astro.uvw import UV_Coverage
>>> xst = XST(".../nenupy/tests/test_data/XST.fits")
>>> xst_data = xst.get(miniarray_selection=np.array([0, 2, 10]))
>>> uvw = UV_Coverage.from_xst(xst_data)
>>> uvw.plot()
.. figure:: ../_images/io_images/uv_coverage_nenupartial.png
:width: 450
:align: center
.. _xst_beamforming_doc:
Beamforming from cross-correlations
-----------------------------------
XST contain recorded amplitude and phase data for each baseline
involved in the observation. Hence their ability to be converted
to beamformed statistics data (or BST) at will.
This can in particular be done for any subset of Mini-Arrays in
any pointing direction allowing for numerous potential array
configurations available at once with a single XST observation,
rather than performing a multi-pointing beam BST observation
(at the cost of Sub-band number).
To demonstrate this property, the following considers a single
NenuFAR observation data acquired simultaneously in BST and XST.
The files are loaded using :class:`~nenupy.io.xst.BST` and :class:`~nenupy.io.xst.XST`:
.. code-block:: python
>>> from nenupy.io.bst import BST
>>> from nenupy.io.xst import XST
>>> bst = BST("20191129_141900_BST.fits")
>>> xst = XST("20191129_141900_XST.fits")
>>> bst_data = bst.get(frequency_selection="==40.234375MHz", polarization="NW")
Beamforming the cross-correlation data is done using the
:meth:`~nenupy.io.xst.Crosslet.get_beamform` method.
The phasing direction must be provided.
Hence, to compare the BST and XST data, a :class:`~nenupy.astro.pointing.Pointing`
object is created directly from the metadata stored in the BST file
using :meth:`~nenupy.astro.pointing.Pointing.from_bst`.
Similarly, the same Mini-Arrays and polarization are selected.
Finally, the ``calibration`` table is specified ('default' value
enables the calibration table used during the BST observation).
.. code-block:: python
>>> from nenupy.astro.pointing import Pointing
>>> bf_cal = xst.get_beamform(
pointing=Pointing.from_bst(bst, beam=0, analog=False),
frequency_selection="==40.234375MHz",
mini_arrays=bst.mini_arrays,
polarization="NW",
calibration="default"
)
To compare the result, the selected BST data, the beamformed data
using the calibration table and without using it (setting
``calibration="none"``) are displayed together:
.. code-block:: python
>>> import matplotlib.pyplot as plt
>>> fig = plt.figure(figsize=(7, 4))
>>> plt.plot(bst_data.time.datetime, bst_data.value, label="BST data", linewidth=3)
>>> plt.plot(bf_uncal.time.datetime, bf_uncal.value, label="XST data uncalibrated", linewidth=1)
>>> plt.plot(bf_cal.time.datetime, bf_cal.value, label="XST data", linewidth=0.5, color="tab:red")
>>> plt.legend()
>>> plt.xlabel(f"Time (UTC from {bst_data.time[0].isot})")
>>> plt.ylabel("Amp")
.. figure:: ../_images/io_images/beamforming_from_xst.png
:width: 450
:align: center
BST data versus time, against re-constructed beamformed data from XST (uncalibrated or calibrated with the default table used to obtain the BST).
The blue (BST) and red (calibrated XST) curves are perfectly aligned as expected.
Image from XST
--------------
Once the XST data loaded (thanks to :class:`~nenupy.io.xst.XST` or :class:`~nenupy.io.xst.NenufarTV`),
their selection (using for instance :meth:`~nenupy.io.xst.Crosslet.get_stokes`)
outputs a :class:`~nenupy.io.xst.XST_Slice` instance.
The method :meth:`~nenupy.io.xst.XST_Slice.make_image` enables visibility
inversion to make a 'dirty image' from the dataset in the :class:`~nenupy.astro.sky.HpxSky` format.
The method :meth:`~nenupy.astro.sky.SkySliceBase.plot` is used to display the final image.
.. code-block:: python
>>> import astropy.units as u
>>> from astropy.coordinates import SkyCoord
>>> xst_data = xst.get_stokes(
stokes="I",
miniarray_selection=None,
frequency_selection=">=20MHz",
time_selection=">=2019-11-19T15:15:00"
)
>>> cyg_a = SkyCoord.from_name("Cyg A")
>>> im = xst_data.make_image(
resolution=1*u.deg,
fov_radius=20*u.deg,
phase_center=cyg_a,
stokes="I"
)
>>> im[0, 0, 0].plot(
center=cyg_a,
radius=17*u.deg,
colorbar_label="Stokes I (arb. units)",
figsize=(8, 8),
)
.. figure:: ../_images/io_images/xst_image.png
:width: 450
:align: center
Cygnus A image obtained from XST data.
Near-field imprint from XST
---------------------------
Computing the near-field is pretty similar.
Once the cross-correlations are selected using
:meth:`~nenupy.io.xst.Crosslet.get_stokes`,
:meth:`~nenupy.io.xst.XST_Slice.make_nearfield` can be called to compute it.
The returned values can be used to instanciate a :class:`~nenupy.io.xst.TV_Nearfield`
object. The :meth:`~nenupy.io.xst.TV_Nearfield.save_png` method is finally
used to display the map:
.. code-block:: python
:emphasize-lines: 13
>>> from nenupy.io.xst import TV_Nearfield
>>> import astropy.units as u
>>> xst_data = xst.get_stokes(
stokes="I",
miniarray_selection=None,
frequency_selection=">=20MHz",
time_selection="==2019-11-29T16:16:46.000"
)
>>> radius = 400*u.m
>>> npix = 64
>>> nf, source_imprint = xst_data.make_nearfield(
radius=radius,
npix=npix,
sources=["Cyg A", "Cas A", "Sun"]
)
>>> nearfield = TV_Nearfield(
nearfield=nf,
source_imprints=source_imprint,
npix=npix,
time=xst_data.time[0],
frequency=np.mean(xst_data.frequency),
radius=radius,
mini_arrays=xst_data.mini_arrays,
stokes="I"
)
>>> nearfield.save_png()
.. figure:: ../_images/io_images/xst_nearfield.png
:width: 450
:align: center
XST near-field.
NenuFAR TV
----------
The class :class:`~nenupy.io.xst.NenufarTV` is exclusively used to read and load
data corresponding with the `NenuFAR-TV `_.
Every 5 minutes, cross-correlations are taken along 16 sub-bands (for a total of
around 3 MHz bandwidth), with a 10-sec time resolution.
The data are stored in binary files that can be simply decoded by:
.. code-block:: python
>>> from nenupy.io.xst import NenufarTV
>>> tv = NenufarTV("/path/to/nenufarTV.dat")
TV Image
^^^^^^^^
Dedicated methods are attached to the :class:`~nenupy.io.xst.NenufarTV` class.
The image can be directly computed using :meth:`~nenupy.io.xst.NenufarTV.compute_nenufar_tv`:
.. code-block:: python
>>> from nenupy.io.xst import NenufarTV
>>> import astropy.units as u
>>> tv = NenufarTV("/path/to/nenufarTV.dat")
>>> tv_image = tv.compute_nenufar_tv(
analog_pointing_file="",
fov_radius=27 * u.deg,
resolution=0.5 * u.deg,
stokes="I"
)
>>> tv_image.save_png()
.. figure:: ../_images/io_images/nenufartv_im.png
:width: 450
:align: center
NenuFAR-TV image.
TV Near-field
^^^^^^^^^^^^^
The same is true for the near-field image, using the method
:meth:`~nenupy.io.xst.NenufarTV.compute_nearfield_tv`:
.. code-block:: python
>>> from nenupy.io.xst import NenufarTV
>>> import astropy.units as u
>>> tv = NenufarTV("/path/to/nenufarTV.dat")
>>> tv_nearfield = tv.compute_nearfield_tv(
sources=["Cyg A", "Cas A"],
stokes="I"
)
>>> tv_nearfield.save_png()
.. figure:: ../_images/io_images/nenufartv_nf.png
:width: 450
:align: center
NenuFAR-TV near-field.