Source code for pyvo.dal.sia2

# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
A module for searching for images in a remote archive.

A Simple Image Access v2 (SIA2) service allows a client to search for
images based on a number of criteria/parameters. The results are
represented in `pyvo.dam.obscore.ObsCoreMetadata` format.

The ``SIA2Service`` class can represent a specific service available at a URL
endpoint.
"""

import warnings
import numpy as np

from astropy import units as u
from astropy.time import Time
from astropy.utils.decorators import deprecated
from astropy.utils.exceptions import AstropyDeprecationWarning

from .query import DALResults, DALQuery, DALService, Record
from .adhoc import DatalinkResultsMixin, AxisParamMixin, SodaRecordMixin, DatalinkRecordMixin
from .params import IntervalQueryParam, StrQueryParam, EnumQueryParam
from .vosi import AvailabilityMixin, CapabilityMixin
from ..dam import ObsCoreMetadata, CALIBRATION_LEVELS


__all__ = ["search", "SIA2Service", "SIA2Query", "SIA2Results", "ObsCoreRecord"]

SIA2_STANDARD_ID = 'ivo://ivoa.net/std/SIA#query-2.0'


SIA2_PARAMETERS_DESC = """
pos : single or list of tuples
    angle units (default: deg)
    the positional region(s) to be searched for data. Each region can
    be expressed as a tuple representing a CIRCLE, RANGE or POLYGON as
    follows:
    (ra, dec, radius) - for CIRCLE. (angle units - defaults to)
    (long1, long2, lat1, lat2) - for RANGE (angle units required)
    (ra, dec, ra, dec, ra, dec ... ) ra/dec points for POLYGON all
    in angle units
band : scalar, tuple(interval) or list of tuples
    (spectral units (default: meter)
    the energy interval(s) to be searched for data.
time : single or list of `~astropy.time.Time` or compatible strings
    the time interval(s) to be searched for data.
pol : single or list of str from `pyvo.dam.obscore.POLARIZATION_STATES`
    the polarization state(s) to be searched for data.
field_of_view : single or list of tuples
    angle units (default arcsec)
    the range(s) of field of view (size) to be searched for data
spatial_resolution : single or list of tuples
    angle units required
    the range(s) of spatial resolution to be searched for data
spectral_resolving_power : single or list of tuples
    the range(s) of spectral resolving power to be searched for data
exptime : single or list of tuples
    time units (default: second)
    the range(s) of exposure times to be searched for data
timeres : single of list of tuples
    time units (default: second)
    the range(s) of temporal resolution to be searched for data
publisher_did : single or list of str
    specifies the unique identifier of dataset(s). It is global because
    it must include information regarding the publisher
    (obs_publisher_did in ObsCore)
collection : single or list of str
    name of the collection that the data belongs to
facility : single or list of str
    specifies the name of the facility (usually telescope) where
    the data was acquired.
instrument : single or list of str
    specifies the name of the instrument with which the data was
    acquired.
data_type : 'image'|'cube'
    specifies the type of the data
calib_level : single or list from enum
    `pyvo.dam.obscore.CALIBRATION_LEVELS`
    specifies the calibration level of the data. Can be a single value
    or a list of values
target_name : single or list of str
    specifies the name of the target (e.g. the intention of the
    original science program or observation)
res_format : single or list of strings
    specifies response format(s).
max_records : int
    allows the client to limit the number or records in the response
**kwargs : custom query parameters
    single or a list of values (or tuples for intervals) custom query
    parameters that a specific service accepts. The values of the
    parameters need to follow the SIA2 format and represent the
    appropriate quantities (where applicable).
"""


def __getattr__(name):
    if name == 'SIA_PARAMETERS_DESC':
        warnings.warn("The name SIA_PARAMETERS_DESC is deprecated in v1.5 for SIA v2 services, "
                      "use SIA2_PARAMETERS_DESC instead.", AstropyDeprecationWarning)
        return SIA2_PARAMETERS_DESC
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


def search(url, pos=None, *, band=None, time=None, pol=None,
           field_of_view=None, spatial_resolution=None,
           spectral_resolving_power=None, exptime=None,
           timeres=None, publisher_did=None, facility=None, collection=None,
           instrument=None, data_type=None, calib_level=None,
           target_name=None, res_format=None, maxrec=None, session=None,
           **kwargs):
    """
    submit a simple SIA query to a SIA2 compatible service

    Parameters
    ----------

    url : str
       url of the SIA service (base or endpoint)
    _SIA2_PARAMETERS

    """
    service = SIA2Service(url, session=session)
    return service.search(pos=pos, band=band, time=time, pol=pol,
                          field_of_view=field_of_view,
                          spatial_resolution=spatial_resolution,
                          spectral_resolving_power=spectral_resolving_power,
                          exptime=exptime, timeres=timeres,
                          publisher_did=publisher_did,
                          facility=facility, collection=collection,
                          instrument=instrument, data_type=data_type,
                          calib_level=calib_level, target_name=target_name,
                          res_format=res_format, maxrec=maxrec, **kwargs)


search.__doc__ = search.__doc__.replace('_SIA2_PARAMETERS',
                                        SIA2_PARAMETERS_DESC)


def _tolist(value):
    # return value as a list - is there something in Python to do that?
    if not value:
        return []
    if isinstance(value, list):
        return value
    return [value]


[docs]class SIA2Service(DALService, AvailabilityMixin, CapabilityMixin): """ a representation of an SIA2 service Note that within pyVO, SIA2 services are associated with the (non-existing) standard id ivo://ivoa.net/std/sia2 rather than ivo://ivoa.net/std/sia#query-2.0 as in the standard; users should generally not notice that, though. """ def __init__(self, baseurl, *, capability_description=None, session=None, check_baseurl=True): """ instantiate an SIA2 service Parameters ---------- url : str url - URL of the SIA2service (base or query endpoint) session : object optional session to use for network requests check_baseurl : bool True - use the capabilities end point of the service to get the query end point, False - baseurl is the query end point """ super().__init__(baseurl, capability_description=capability_description, session=session) # Check if the session has an update_from_capabilities attribute. # This means that the session is aware of IVOA capabilities, # and can use this information in processing network requests. # One such usecase for this is auth. if hasattr(self._session, 'update_from_capabilities'): self._session.update_from_capabilities(self.capabilities) self.query_ep = baseurl.strip('&') # default service end point if check_baseurl: for cap in self.capabilities: # assumes that the access URL is the same regardless of the # authentication method except BasicAA which is not supported # in pyvo. So pick any access url as long as it's not if cap.standardid.lower() == SIA2_STANDARD_ID.lower(): for interface in cap.interfaces: if interface.accessurls and \ not (len(interface.securitymethods) == 1 and interface.securitymethods[0].standardid == 'ivo://ivoa.net/sso#BasicAA'): self.query_ep = interface.accessurls[0].content break else: continue break
[docs] def search(self, pos=None, *, band=None, time=None, pol=None, field_of_view=None, spatial_resolution=None, spectral_resolving_power=None, exptime=None, timeres=None, publisher_did=None, facility=None, collection=None, instrument=None, data_type=None, calib_level=None, target_name=None, res_format=None, maxrec=None, **kwargs): """ Performs a SIA2 search against a SIA2 service See Also -------- pyvo.dal.sia2.SIA2Query """ return SIA2Query(self.query_ep, pos=pos, band=band, time=time, pol=pol, field_of_view=field_of_view, spatial_resolution=spatial_resolution, spectral_resolving_power=spectral_resolving_power, exptime=exptime, timeres=timeres, publisher_did=publisher_did, facility=facility, collection=collection, instrument=instrument, data_type=data_type, calib_level=calib_level, target_name=target_name, res_format=res_format, maxrec=maxrec, session=self._session, **kwargs).execute()
[docs]class SIA2Query(DALQuery, AxisParamMixin): """ a class very similar to :py:attr:`~pyvo.dal.query.SIAQuery` class but used to interact with SIA2 services. """ def __init__(self, url, pos=None, *, band=None, time=None, pol=None, field_of_view=None, spatial_resolution=None, spectral_resolving_power=None, exptime=None, timeres=None, publisher_did=None, facility=None, collection=None, instrument=None, data_type=None, calib_level=None, target_name=None, res_format=None, maxrec=None, session=None, **kwargs): """ initialize the query object with a url and the given parameters Note: The majority of the attributes represent constraints used to query the SIA2 service and are represented through lists. Multiple value attributes are OR-ed in the query, however the values of different attributes are AND-ed. Intervals are represented with tuples and open-ended intervals should be expressed with float("-inf") or float("inf"). Eg. For all values less than or equal to 600 use (float(-inf), 600) Additional attribute constraints can be specified (or removed) after this object has been created using the *.add and *_del methods. Parameters ---------- url : url where to send the query request to _SIA2_PARAMETERS session : object optional session to use for network requests Returns ------- SIA2Results a container holding a table of matching image records. Records are represented in IVOA ObsCore format Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError if the service responds with an error, including a query syntax error. See Also -------- SIA2Results pyvo.dal.query.DALServiceError pyvo.dal.query.DALQueryError """ super().__init__(url, session=session) for pp in _tolist(pos): self.pos.add(pp) for bb in _tolist(band): self.band.add(bb) for tt in _tolist(time): self.time.add(tt) for pp in _tolist(pol): self.pol.add(pp) for ff in _tolist(field_of_view): self.field_of_view.add(ff) for sp in _tolist(spatial_resolution): self.spatial_resolution.add(sp) for sr in _tolist(spectral_resolving_power): self.spectral_resolving_power.add(sr) for et in _tolist(exptime): self.exptime.add(et) for tr in _tolist(timeres): self.timeres.add(tr) for ii in _tolist(publisher_did): self.publisher_did.add(ii) for ff in _tolist(facility): self.facility.add(ff) for col in _tolist(collection): self.collection.add(col) for inst in _tolist(instrument): self.instrument.add(inst) for dt in _tolist(data_type): self.data_type.add(dt) for cal in _tolist(calib_level): self.calib_level.add(cal) for tt in _tolist(target_name): self.target_name.add(tt) for rf in _tolist(res_format): self.res_format.add(rf) for name, value in kwargs.items(): custom_arg = [] for kw in _tolist(value): if isinstance(kw, tuple): val = '{} {}'.format(kw[0], kw[1]) else: val = str(kw) custom_arg.append(val) self[name] = custom_arg self.maxrec = maxrec __init__.__doc__ = \ __init__.__doc__.replace('_SIA2_PARAMETERS', SIA2_PARAMETERS_DESC) @property def field_of_view(self): if not hasattr(self, '_fov'): self._fov = IntervalQueryParam(u.deg) self['FOV'] = self._fov.dal return self._fov @property def spatial_resolution(self): if not hasattr(self, '_spatres'): self._spatres = IntervalQueryParam(u.arcsec) self['SPATRES'] = self._spatres.dal return self._spatres @property def spectral_resolving_power(self): if not hasattr(self, '_specrp'): self._specrp = IntervalQueryParam() self['SPECRP'] = self._specrp.dal return self._specrp @property def exptime(self): if not hasattr(self, '_exptime'): self._exptime = IntervalQueryParam(u.second) self['EXPTIME'] = self._exptime.dal return self._exptime @property def timeres(self): if not hasattr(self, '_timeres'): self._timeres = IntervalQueryParam(u.second) self['TIMERES'] = self._timeres.dal return self._timeres @property def publisher_did(self): if not hasattr(self, '_publisher_did'): self._publisher_did = StrQueryParam() self['ID'] = self._publisher_did.dal return self._publisher_did @property def facility(self): if not hasattr(self, '_facility'): self._facility = StrQueryParam() self['FACILITY'] = self._facility.dal return self._facility @property def collection(self): if not hasattr(self, '_collection'): self._collection = StrQueryParam() self['COLLECTION'] = self._collection.dal return self._collection @property def instrument(self): if not hasattr(self, '_instrument'): self._instrument = StrQueryParam() self['INSTRUMENT'] = self._instrument.dal return self._instrument @property def data_type(self): if not hasattr(self, '_data_type'): self._data_type = StrQueryParam() self['DPTYPE'] = self._data_type.dal return self._data_type @property def calib_level(self): if not hasattr(self, '_cal'): self._cal = EnumQueryParam(CALIBRATION_LEVELS) self['CALIB'] = self._cal.dal return self._cal @property def target_name(self): if not hasattr(self, '_target'): self._target_name = StrQueryParam() self['TARGET'] = self._target_name.dal return self._target_name @property def res_format(self): if not hasattr(self, '_res_format'): self._res_format = StrQueryParam() self['FORMAT'] = self._res_format.dal return self._res_format @property def maxrec(self): return self._maxrec @maxrec.setter def maxrec(self, val): if not val: return if not isinstance(val, int) and val > 0: raise ValueError('maxrec {} must be positive int'.format(val)) self._maxrec = val self['MAXREC'] = str(val)
[docs] def execute(self): """ submit the query and return the results as a SIA2Results instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ return SIA2Results(self.execute_votable(), url=self.queryurl, session=self._session)
[docs]class SIA2Results(DatalinkResultsMixin, DALResults): """ The list of matching images resulting from an image (SIA2) query. Each record contains a set of metadata that describes an available image matching the query constraints. The number of records in the results is available by passing it to the Python built-in ``len()`` function. This class supports iterable semantics; thus, individual records (in the form of :py:class:`~pyvo.dal.sia2.ObsCoreRecord` instances) are typically accessed by iterating over an ``SIA2Results`` instance. Alternatively, records can be accessed randomly via :py:meth:`getrecord` or through a Python Database API (v2) Cursor (via :py:meth:`~pyvo.dal.query.DALResults.cursor`). Column-based data access is possible via the :py:meth:`~pyvo.dal.query.DALResults.getcolumn` method. ``SIA2Results`` is essentially a wrapper around an Astropy :py:mod:`~astropy.io.votable` :py:class:`~astropy.io.votable.tree.TableElement` instance where the columns contain the various metadata describing the images. One can access that VOTable directly via the :py:attr:`~pyvo.dal.query.DALResults.votable` attribute. Thus, when one retrieves a whole column via :py:meth:`~pyvo.dal.query.DALResults.getcolumn`, the result is a Numpy array. Alternatively, one can manipulate the results as an Astropy :py:class:`~astropy.table.table.Table` via the following conversion: ``table = results.votable.to_table()`` ``SIA2Results`` supports the array item operator ``[...]`` in a read-only context. When the argument is numerical, the result is an :py:class:`~pyvo.dal.sia2.ObsCoreRecord` instance, representing the record at the position given by the numerical index. If the argument is a string, it is interpreted as the name of a column, and the data from the column matching that name is returned as a Numpy array. """
[docs] def getrecord(self, index): """ return a representation of a SIA2 result record that follows dictionary semantics. The keys of the dictionary are those returned by this instance's fieldnames attribute. The returned record has additional image-specific properties Parameters ---------- index : int the integer index of the desired record where 0 returns the first record Returns ------- ObsCoreMetadataRecord a dictionary-like wrapper containing the result record metadata. Raises ------ IndexError if index is negative or equal or larger than the number of rows in the result table. See Also -------- Record """ return ObsCoreRecord(self, index, session=self._session)
class ObsCoreRecord(SodaRecordMixin, DatalinkRecordMixin, Record, ObsCoreMetadata): """ a dictionary-like container for data in a record from the results of an image (SIA2) search, describing an available image in ObsCore format. The commonly accessed metadata which are stadardized by the SIA2 protocol are available as attributes. If the metadatum accessible via an attribute is not available, the value of that attribute will be None. All metadata, including non-standard metadata, are also acessible via the ``get(`` *key* ``)`` function (or the [*key*] operator) where *key* is table column name. """ # OBSERVATION INFO @property def dataproduct_type(self): """ Data product (file content) primary type. This is coded as a string that conveys a general idea of the content and organization of a dataset. """ return self.get('dataproduct_type', decode=True) @property def dataproduct_subtype(self): """ Data product more specific type """ return self.get('dataproduct_subtype', decode=True, default=None) @property def calib_level(self): """ Calibration level of the observation: in {0, 1, 2, 3, 4} """ return self.get('calib_level') # TARGET INFO @property def target_name(self): """ The target_name attribute contains the name of the target of the observation, if any. This is typically the name of an astronomical object, but could be the name of a survey field. The target name is most useful for output, to identify the target of an observation to the user. In queries it is generally better to refer to astronomical objects by position, using a name resolver to convert the target name into a coordinate (when possible). """ return self.get('target_name', decode=True) @property def target_class(self): """ This field indicates the type of object that was pointed for this observation. It is a string with possible values defined in a special vocabulary set to be defined: list of object classes (or types) used by the SIMBAD database, NED or defined in another IVOA vocabulary. """ return self.get('target_class', decode=True, default=None) # DATA DESCRIPTION @property def obs_id(self): """ Collection specific internal ID given by the ObsTAP service """ return self.get('obs_id', decode=True) @property def obs_title(self): """ Brief description of dataset in free format """ return self.get('obs_title', decode=True, default=None) @property def obs_collection(self): """ The name of the collection (DataID.Collection) identifies the data collection to which the data product belongs. A data collection can be any collection of datasets which are alike in some fashion. Typical data collections might be all the data from a particular telescope, instrument, or survey. The value is either the registered shortname for the data collection, the full registered IVOA identifier for the collection, or a data provider defined short name for the collection. Examples: HST/WFPC2, VLT/FORS2, CHANDRA/ACIS-S, etc. """ return self.get('obs_collection', decode=True) @property def obs_create_date(self): """ Date when the dataset was created """ cd = self.get('obs_create_date', default=None) return cd if not cd else Time(cd) @property def obs_creator_name(self): """ The name of the institution or entity which created the dataset. """ return self.get('obs_creator_name', decode=True, default=None) @property def obs_creator_did(self): """ IVOA dataset identifier given by its creator. """ return self.get('obs_creator_did', decode=True, default=None) # CURATION INFORMATION @property def obs_release_date(self): """ Observation release date """ rt = self.get('obs_release_date', default=None, decode=True) return rt if not rt else Time(rt) @property def obs_publisher_did(self): """ ID for the Dataset assigned by the publisher. Note that data from a source (creator_did) can be published by multiple publishers and have assigned multiple publisher data IDs. """ return self.get('obs_publisher_did', decode=True) @property def publisher_id(self): """ IVOA-ID for the Publisher. It will also be globally unique since each publisher has a unique registered publisher ID """ return self.get('publisher_id', decode=True, default=None) @property def bib_reference(self): """ URL or bibcode for documentation. This is a forward link to major publications which reference the dataset. """ return self.get('bib_reference', decode=True, default=None) @property def data_rights(self): """ This parameter allows mentioning the availability of a dataset. Possible values are: public, secure, or proprietary. """ return self.get('data_rights', decode=True, default=None) # ACCESS INFORMATION @property def access_url(self): """ The access_url column contains a URL that can be used to download the data product (as a file of some sort). Access URLs are not guaranteed to remain valid and unchanged indefinitely. To access a specific data product after a period of time (e.g., days or weeks) a query should be performed to obtain a fresh access URL. """ return self.get('access_url', decode=True) @property def access_format(self): """ Content format of the dataset. The value of access_format should be a MIME type, either a standard MIME type, an extended MIME type from the above table, or a new custom MIME type defined by the data provider. """ return self.get('access_format', decode=True) @property def access_estsize(self): """ The approximate size (in kilobytes) of the file available via the access_url. This is used only to gain some idea of the size of a data product before downloading it, hence only an approximate value is required. Provision of dataset size estimates is important whenever it is possible that datasets can be very large. """ return self.get('access_estsize') * 1000 * u.byte # SPATIAL CHARACTERISATION @property def s_ra(self): """ ICRS Right Ascension of the center of the observation """ return self.get('s_ra') * u.deg @property def s_dec(self): """ CRS Declination of the center of the observation """ return self.get('s_dec') * u.deg @property def s_fov(self): """ Approximate size of the covered region as the diameter of a containing circle. For most data products the value given should be large enough to include the entire area of the observation; coverage within the bounded region need not be complete, for example if the specified radius encompasses a rotated rectangular region. For observations which do not have a well-defined boundary, e.g. radio or high energy observations, a characteristic value should be given. The radius attribute provides a simple way to characterize and use (e.g. for discovery computations) the approximate spatial coverage of a data product. The spatial coverage of a data product can be more precisely specified using the region attribute. """ return self.get('s_fov') * u.deg @property def s_region(self): """ Sky region covered by the data product (expressed in ICRS frame). It can be used to precisely specify the covered spatial region of a data product. It is often an exact, or almost exact, representation of the illumination region of a given observation defined in a standard way by the concept of Support in the Characterisation data model. """ return self.get('s_region', decode=True) @property def s_resolution(self): """ Spatial resolution of data specifies a reference value chosen by the data provider for the estimated spatial resolution of the data product in arcseconds. This refers to the smallest spatial feature in the observed signal that can be resolved. In cases where the spatial resolution varies across the field the best spatial resolution (smallest resolvable spatial feature) should be specified. In cases where the spatial frequency sampling of an observation is complex (e.g., interferometry) a typical value for spatial resolution estimate should be given; additional characterisation may be necessary to fully specify the spatial characteristics of the data. """ return self.get('s_resolution') * u.arcsec @property def s_xel1(self): """ Number of elements along the first spatial axis """ return self.get('s_xel1') @property def s_xel2(self): """ Number of elements along the second spatial axis """ return self.get('s_xel2') @property def s_ucd(self): """ UCD for the nature of the spatial axis (pos or u,v data) """ return self.get('s_ucd', decode=True, default=None) @property def s_unit(self): """ Unit used for spatial axis """ return self.get('s_unit', decode=True, default=None) @property def s_resolution_min(self): """ Resolution min value on spatial axis (FHWM of PSF) """ rmin = self.get('s_resolution_min', default=None) return rmin if not rmin else rmin * u.arcsec @property def s_resolution_max(self): """ Resolution max value on spatial axis (FHWM of PSF) """ rmax = self.get('s_resolution_max', default=None) return rmax if not rmax else rmax * u.arcsec @property def s_calib_status(self): """ A string to encode the calibration status along the spatial axis (astrometry). Possible values could be {uncalibrated, raw, calibrated} """ return self.get('s_calib_status', decode=True, default=None) @property def s_stat_error(self): """ This parameter gives an estimate of the astrometric statistical error after the astrometric calibration phase. """ return self.get('s_stat_error', decode=True, default=None) @property def s_pixel_scale(self): """ This corresponds to the sampling precision of the data along the spatial axis. It is stored as a real number corresponding to the spatial sampling period, i.e., the distance in world coordinates system units between two pixel centers. It may contain two values if the pixels are rectangular. """ return self.get('s_pixel_scale', decode=True, default=None) # TIME CHARACTERISATION @property def t_xel(self): """ Number of elements along the time axis """ return self.get('t_xel') @property def t_ref_pos(self): """ Time Axis Reference Position as defined in STC REC, Section 4.4.1.1.1 """ return self.get('t_ref_pos', decode=True, default=None) @property def t_min(self): """ The start time of the observation specified in MJD as an `~astropy.time.Time` instance. In case of data products result of the combination of multiple frames, min time must be the minimum of the start times. ``None`` is used for NaN response values. """ t_min = self.get('t_min') if np.isfinite(t_min): return Time(t_min, format='mjd') else: return None @property def t_max(self): """ The stop time of the observation specified in MJD as an `~astropy.time.Time` instance. In case of data products result of the combination of multiple frames, t_max must be the maximum of the stop times. ``None`` is used for NaN response values. """ t_max = self.get('t_max') if np.isfinite(t_max): return Time(t_max, format='mjd') else: return None @property def t_exptime(self): """ Total exposure time. For simple exposures, this is just the time_bounds size expressed in seconds. For data where the detector is not active at all times (e.g. data products made by combining exposures taken at different times), the t_exptime will be smaller than the time_bounds interval. For data where the xptime is not constant over the entire data product, the median exposure time per pixel is a good way to characterize the typical value. In some cases, exptime is generally used as an indicator of the relative sensitivity (depth) within a single data collection (e.g. obs_collection); data providers should supply a suitable relative value when it is not feasible to define or compute the true exposure time. In case of targeted observations, on the contrary the exposure time is often adjusted to achieve similar signal to noise ratio for different targets. """ return self.get('t_exptime') * u.second @property def t_resolution(self): """ Estimated or average value of the temporal resolution. """ return self.get('t_resolution') * u.second @property def t_calib_status(self): """ Type of time coordinate calibration. Possible values are principally {uncalibrated, calibrated, raw, relative}. This may be extended for specific time domain collections. """ return self.get('t_calib_status', decode=True, default=None) @property def t_stat_error(self): """ Time coord statistical error on the time measurements in seconds """ ter = self.get('t_stat_error', default=None) return ter if not ter else ter * u.second # SPECTRAL CHARACTERISATION @property def em_xel(self): """ Number of elements along the spectral axis """ return self.get('em_xel') @property def em_ucd(self): """ Nature of the spectral axis """ return self.get('em_ucd', decode=True, default=None) @property def em_unit(self): """ Units along the spectral axis """ return self.get('em_unit', decode=True, default=None) @property def em_calib_status(self): """ This attribute of the spectral axis indicates the status of the data in terms of spectral calibration. Possible values are defined in the Characterisation Data Model and belong to {uncalibrated , calibrated, relative, absolute}. """ return self.get('em_calib_status', decode=True, default=None) @property def em_min(self): """ Minimum of the spectral interval covered by the observation """ return self.get('em_min') * u.meter @property def em_max(self): """ Maximum of the spectral interval covered by the observation """ return self.get('em_max') * u.meter @property def em_res_power(self): """ Average estimation for the spectral resolution power stored as a double value, with no unit. """ return self.get("em_res_power") @property def em_res_power_min(self): """ Resolving power min value on spectral axis """ return self.get('em_res_power_min', None) @property def em_res_power_max(self): """ Resolving power max value on spectral axis """ return self.get('em_res_power_max', None) @property def em_resolution(self): """ A mean estimate of the resolution, e.g. Full Width at Half Maximum (FWHM) of the Line Spread Function (or LSF). This can be used for narrow range spectra whereas in the majority of cases, the resolution power is preferable due to the LSF variation along the spectral axis. """ if 'em_resolution' in self.keys(): return self.get('em_resolution') * u.meter return None @property def em_stat_error(self): """ Spectral coord statistical error (accuracy along the spectral axis) """ if 'em_stat_error' in self.keys(): return self.get('em_stat_error') * u.meter return None # OBSERVABLE AXIS @property def o_ucd(self): """ Nature of the observable axis within the data product """ return self.get('o_ucd', decode=True) @property def o_unit(self): """ Units along the observable axis """ return self.get('o_unit', decode=True, default=None) @property def o_calib_status(self): """ Type of calibration applied on the Flux observed (or other observable quantity). """ return self.get('o_calib_status', decode=True, default=None) @property def o_stat_error(self): """ Statistical error on the Observable axis. Note: the return value has the units defined in unit """ return self.get('o_stat_error', decode=True, default=None) # POLARIZATION CHARACTERISATION @property def pol_xel(self): """ Number of different polarization states present in the data. The default value is 0, indicating that polarization was not explicitly observed. Corresponding values are stored in the `pol` property """ return self.get('pol_xel') @property def pol_states(self): """ List of polarization states present in the data file. Possible values are: {I Q U V RR LL RL LR XX YY XY YX POLI POLA}. Values in the set are separated by the '/' character. A leading / character must start the list and a trailing / character must end it. It should be ordered following the above list, compatible with the FITS list table for polarization definition. """ return self.get('pol_states', decode=True, default=None) # PROVENANCE @property def instrument_name(self): """ The name of the instrument used for the acquisition of the data """ return self.get('instrument_name', decode=True) @property def facility_name(self): """ Name of the facility or observatory used to collect the data """ return self.get('facility_name', decode=True, default=None) @property def proposal_id(self): """ Identifier of proposal to which observation belongs """ return self.get('proposal_id', default=None, decode=True) @deprecated("1.5", alternative="SIA2Service") class SIAService(SIA2Service): pass @deprecated("1.5", alternative="SIA2Query") class SIAQuery(SIA2Query): pass @deprecated("1.5", alternative="SIA2Results") class SIAResults(SIA2Results): pass