import toolviper.utils.logger as logger
import toolviper.utils.parameter
from astrohack.utils.file import overwrite_file
from astrohack.core.panel import process_panel_chunk
from astrohack.utils.validation import custom_panel_checker
from astrohack.utils.text import get_default_file_name
from astrohack.utils.graph import create_and_execute_graph_from_dict
from astrohack.io.panel_mds import AstrohackPanelFile
from astrohack.io.dio import open_image
from typing import Union, List
@toolviper.utils.parameter.validate(custom_checker=custom_panel_checker)
[docs]def panel(
image_name: str,
panel_name: str = None,
clip_type: str = "sigma",
clip_level: Union[float, dict[dict[float]]] = 3.0,
use_detailed_mask: bool = True,
panel_model: str = "rigid",
panel_margins: float = 0.05,
polarization_state: str = "I",
ant: Union[str, List[str]] = "all",
ddi: Union[int, List[str]] = "all",
parallel: bool = False,
overwrite: bool = False,
):
"""Analyze holography images to derive panel adjustments
:param image_name: Input holography data file name. Accepted data formats are the output from \
``astrohack.holog.holog`` and AIPS holography data prepackaged using ``astrohack.panel.aips_holog_to_astrohack``.
:type image_name: str
:param panel_name: Name of output file; File name will be appended with suffix *.panel.zarr*. Defaults to \
*basename* of input file plus holography panel file suffix.
:type panel_name: str, optional
:param clip_type: Choose the amplitude clipping algorithm: none, absolute, relative, sigma or noise_threshold, \
default is sigma
:type clip_type: str, optional
:param clip_level: Choose level of clipping, can also be specified for specific antenna and DDI combinations by \
passing a dictionary, default is 3 (appropriate for sigma clipping)
:type clip_level: float, dict, optional
:param use_detailed_mask: Use a detailed aperture mask, i.e. include arm shadows for the VLA or include regions \
outside central circular aperture for the ngvla, default is True.
:type use_detailed_mask: bool, optional
:param panel_model: Model of surface fitting function used to fit panel surfaces, None will default to "rigid". \
Possible models are listed below.
:type panel_model: str, optional
:param panel_margins: Relative margin from the edge of the panel used to decide which points are margin points or \
internal points of each panel. Defaults to 0.05.
:type panel_margins: float, optional
:param polarization_state: Select the polarization state over which to run panel, only parallel hands or stokes I \
should be used, default is I.
:type polarization_state: str, optional
:param ant: List of antennas/antenna to be processed, defaults to "all" when None, ex. ea25
:type ant: list or str, optional
:param ddi: List of ddi to be processed, defaults to "all" when None, ex. 0
:type ddi: list or int, optional
:param parallel: Run in parallel. Defaults to False.
:type parallel: bool, optional
:param overwrite: Overwrite files on disk. Defaults to False.
:type overwrite: bool, optional
:return: Holography panel object.
:rtype: AstrohackPanelFile
.. _Description:
Each Stokes I aperture image in the input image file is processed in the following steps:
.. rubric:: Code Outline
- Phase image is converted to a physical surface deviation image.
- A mask of valid signals is created by using the relative cutoff on the amplitude image.
- From telescope panel and layout information, an image describing the panel assignment of each pixel is created
- Using panel image and mask, a list of pixels in each panel is created.
- Pixels in each panel are divided into two groups: margin pixels and internal pixels.
- For each panel:
* Internal pixels are fitted to a surface model.
* The fitted surface model is used to derive corrections for all pixels in the panel, internal and margins.
* The fitted surface model is used to derive corrections for the positions of the screws.
- A corrected deviation image is produced.
- RMS is computed for both the corrected and uncorrected deviation images.
- All images produced are stored in the output *.panel.zarr file*.
.. rubric:: Available panel surface models:
* AIPS fitting models:
- *mean*: The panel is corrected by the mean of its samples.
- *rigid*: The panel samples are fitted to a rigid surface (DEFAULT model).
* Corotated Paraboloids: (the two bending axes of the paraboloid are parallel and perpendicular to a radius \
of the antenna crossing the middle point of the panel):
- *corotated_scipy*: Paraboloid is fitted using scipy.optimize, robust but slow.
- *corotated_lst_sq*: Paraboloid is fitted using the linear algebra least squares method, fast but \
unreliable.
- *corotated_robust*: Tries corotated_lst_sq, if it diverges falls back to corotated_scipy, fast and robust.
* Experimental fitting models:
- *xy_paraboloid*: fitted using scipy.optimize, bending axes are parallel to the x and y axes.
- *rotated_paraboloid*: fitted using scipy.optimize, bending axes can be rotated by any arbitrary angle.
- *full_paraboloid_lst_sq*: Full 9 parameter paraboloid fitted using least_squares method, tends to \
heavily overfit surface irregularities.
.. rubric:: Amplitude clipping:
In order to produce results of good quality parts of the aperture with low signal (e.g. the shadow of the
secondary mirror support) a mask is defined based on the amplitude of the aperture. There are 5 methods
(clip_type parameter) available to define at which level (clip_level) the amplitude is clipped:
* none: In this method no amplitude clip is performed, i.e. the clipping value is set to -infinity.
* absolute: In this method the clipping value is taken directly from the clip_level parameter, e.g.: \
if the user calls `panel(..., clip_type='absolute', clip_level=3.5)` everything below 3.5 in \
amplitude will be clipped
* relative: In this method the clipping value is derived from the amplitude maximum, e.g.: if the user calls \
`panel(..., clip_type='relative', clip_level=0.2) everything below 20% of the maximum amplitude \
will be clipped
* sigma: In this method the clipping value is computed from the RMS noise in the amplitude outside the \
physical dish, e.g.: if the user calls `panel(clip_type='sigma', clip_level=3)` everything below 3 \
times the RMS noise in amplitude will be clipped.
* noise_threshold: In this model the cut is first set to the maximum amplitude outside the disk, a proxy for \
the noise maximum in amplitude, if this preserves a fraction of the aperture disk that is larger \
than the clip_level this is the chosen amplitude cutoff, if not, the cutoff is iteratively lowered \
by 10% until it preservers a fraction of the disk that is larger thatn clip_level. This heuristic \
was created with help from VLA operations.
The default clipping is set to 3 sigma.
.. rubric:: Passing a dictionary for amplitude clipping:
The dictionary used for specifying amplitude clippings specific for each antenna and DDI combination must
follow the following scheme:
.. parsed-literal::
amp_clip_dict = {
ant1_name: {
0: 0.3
1: 0.5
}
ant2_name: {
0: 0.45
1: 0.21
}
}
Where the antenna name is the usual antenna designation e.g. ea24 for the VLA or DV42 for ALMA, and the DDI
number must be given as an integer.
.. _Description:
**AstrohackPanelFile**
Panel object allows the user to access panel data via compound dictionary keys with values, in order of depth, \
`ant` -> `ddi`. The panel object also provides a `summary()` helper function to list available keys for each file.\
An outline of the panel object structure is show below:
.. parsed-literal::
panel_mds =
{
ant_0:{
ddi_0: panel_ds,
⋮
ddi_m: panel_ds
},
⋮
ant_n: …
}
**Example Usage**
.. parsed-literal::
from astrohack.panel import panel
# Fit the panels in the aperture image by using a rigid panel model and
# excluding the border 5% of each panel from the fitting.
panel_mds = panel(
"myholo.image.zarr",
panel_model='rigid',
panel_margin=0.05
)
# fit the panels in the aperture image by using a rigid panel model and
# excluding points in the aperture image which have an amplitude that is less than 20% of the peak amplitude.
panel_mds = panel(
"myholo.image.zarr",
clip_type='relative',
clip_level=0.2
)
"""
# Doing this here allows it to get captured by locals()
panel_name = get_default_file_name(image_name, ".panel.zarr", panel_name)
panel_params = locals()
image_mds = open_image(image_name)
overwrite_file(panel_name, overwrite)
panel_mds = AstrohackPanelFile.create_from_input_parameters(
panel_name, panel_params
)
executed_graph = create_and_execute_graph_from_dict(
looping_dict=image_mds,
chunk_function=process_panel_chunk,
param_dict=panel_params,
key_order=["ant", "ddi"],
output_mds=panel_mds,
)
if executed_graph:
panel_mds.write(mode="a")
return panel_mds
else:
return None