Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ gemini

- Add support for newer instruments (GHOST, IGRINS, IGRINS-2, MAROON-X, ALOPEKE, ZORRO) [#3638]

esa.euclid
^^^^^^^^^^

- New method get_sia to access the Simple Image Access Protocol (SIAP) v2.0 [#3569]

esa.hubble
^^^^^^^^^^

Expand Down
166 changes: 156 additions & 10 deletions astroquery/esa/euclid/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
European Space Agency (ESA)
"""
import binascii
from collections.abc import Iterable
from datetime import datetime
from datetime import timezone
from functools import cache
import os
import pprint
import re
import tarfile
import zipfile
from collections.abc import Iterable
from datetime import datetime
from datetime import timezone
from functools import cache

from astropy import units
from astropy import units as u
Expand All @@ -26,6 +26,7 @@
from requests.exceptions import HTTPError

from astroquery import log
import astroquery.esa.utils.utils as esautils
from astroquery.utils import commons
from astroquery.utils.tap import TapPlus
from astroquery.utils.tap import taputils
Expand All @@ -52,19 +53,21 @@ class EuclidClass(TapPlus):
__regex_designation = re.compile(r"\s*(\S+)\s(-?\d+)\s*", flags=re.MULTILINE | re.UNICODE)

def __init__(self, *, environment='PDR', tap_plus_conn_handler=None, datalink_handler=None, cutout_handler=None,
verbose=False, show_server_messages=True):
sia_handler=None, verbose=False, show_server_messages=True):
"""Constructor for EuclidClass.

Parameters
----------
environment : str, mandatory if no tap, data or cutout hosts is specified, default 'PDR'
The Euclid Science Archive environment: 'PDR', 'IDR', 'OTF' and 'REG'
tap_plus_conn_handler : tap connection handler object, optional, default None
HTTP(s) connection hander (creator). If no handler is provided, a new one is created.
datalink_handler : dataliink connection handler object, optional, default None
HTTP(s) connection hander (creator). If no handler is provided, a new one is created.
HTTP(s) connection handler (creator). If no handler is provided, a new one is created.
datalink_handler : datalink connection handler object, optional, default None
HTTP(s) connection handler (creator). If no handler is provided, a new one is created.
cutout_handler : cutout connection handler object, optional, default None
HTTP(s) connection hander (creator). If no handler is provided, a new one is created.
HTTP(s) connection handler (creator). If no handler is provided, a new one is created.
sia_handler : siap connection handler object, optional, default None
HTTP(s) connection handler (creator). If no handler is provided, a new one is created.
verbose : bool, optional, default 'True'
flag to display information about the process
show_server_messages : bool, optional, default 'True'
Expand Down Expand Up @@ -125,6 +128,20 @@ def __init__(self, *, environment='PDR', tap_plus_conn_handler=None, datalink_ha
else:
self.__euclidcutout = cutout_handler

if sia_handler is None:
self.__euclidsia = TapPlus(url=url_server,
server_context="sas-sia",
tap_context=None,
upload_context=None,
table_edit_context="TableTool",
data_context="sia2/query",
datalink_context=None,
verbose=verbose,
client_id='ASTROQUERY',
use_names_over_ids=conf.USE_NAMES_OVER_IDS)
else:
self.__euclidsia = sia_handler

if show_server_messages:
self.get_status_messages()

Expand Down Expand Up @@ -667,6 +684,8 @@ def login(self, *, user=None, password=None, credentials_file=None, verbose=Fals
self.__eucliddata.login(user=tap_user, password=tap_password, verbose=verbose)
log.info(f"Login to Euclid cutout service: {self.__euclidcutout._TapPlus__getconnhandler().get_host_url()}")
self.__euclidcutout.login(user=tap_user, password=tap_password, verbose=verbose)
log.info(f"Login to Euclid sia service: {self.__euclidsia._TapPlus__getconnhandler().get_host_url()}")
self.__euclidsia.login(user=tap_user, password=tap_password, verbose=verbose)
except HTTPError as err:
log.error('Error logging in data or cutout services: %s' % (str(err)))
log.error("Logging out from TAP server")
Expand Down Expand Up @@ -711,6 +730,14 @@ def login_gui(self, verbose=False):
log.error("Logging out from TAP server")
TapPlus.logout(self, verbose=verbose)

try:
log.info(f"Login to Euclid sia server: {self.__euclidsia._TapPlus__getconnhandler().get_host_url()}")
self.__euclidsia.login(user=tap_user, password=tap_password, verbose=verbose)
except HTTPError as err:
log.error('Error logging in sia server: %s' % (str(err)))
log.error("Logging out from TAP server")
TapPlus.logout(self, verbose=verbose)

def logout(self, verbose=False):
"""
Performs a logout
Expand Down Expand Up @@ -746,6 +773,12 @@ def logout(self, verbose=False):
except HTTPError as err:
log.error('Error logging out cutout server: %s' % (str(err)))

try:
self.__euclidsia.logout(verbose=verbose)
log.info("Euclid sia server logout OK")
except HTTPError as err:
log.error('Error logging out sia server: %s' % (str(err)))

@staticmethod
def __get_quantity_input(value, msg):
if value is None:
Expand Down Expand Up @@ -1087,7 +1120,7 @@ def get_product_list(self, *, observation_id=None, tile_index=None, product_type
observation id for observations. It is not compatible with parameter tile_index.

Searchable products by observation_id: 'dpdVisRawFrame', 'dpdNispRawFrame',
,'DpdVisCalibratedQuadFrame','DpdVisCalibratedFrameCatalog', 'DpdVisStackedFrame',
'DpdVisCalibratedQuadFrame','DpdVisCalibratedFrameCatalog', 'DpdVisStackedFrame',
'DpdVisStackedFrameCatalog',
'DpdNirCalibratedFrame', 'DpdNirCalibratedFrameCatalog', 'DpdNirStackedFrameCatalog', 'DpdNirStackedFrame',
'DpdMerSegmentationMap', 'dpdMerFinalCatalog',
Expand Down Expand Up @@ -1482,6 +1515,119 @@ def __is_multiple(self, value):

return not isinstance(value, int) and ((isinstance(value, (list, tuple)) and len(value) > 1) or ',' in value)

def get_sia(self, *, coordinates, radius=0.1, search_type='CIRCLE', calibration=2, instrument='ALL', band=None,
collection='sedm', dsr_part1=None, dsr_part2=None, dsr_part3=None, output_file=None, verbose=False):
"""
Query the Euclid Observation Images service using the IVOA Simple Image Access Protocol (SIAP) 2.0.

The service provides access to public calibrated and stacked VIS and NISP images, MER mosaics, and Level 1 (raw)
VIS and NISP observations.

Parameters
----------
coordinates: str or SkyCoord, mandatory
Center of the search region.
radius: float or quantity, optional, default value 0.1 degree
Radius of the search region. If a numeric value is provided, it is interpreted as degrees.
An astropy.units.Quantity may also be supplied. When search_type="BOX", this value specifies the box width.
search_type : str, optional, default CIRCLE
Shape of the search region. Supported values are "CIRCLE" and "BOX".
calibration: int, optional, default 2
Calibration level following the ObsCore data model:

- 1: raw instrumental data
- 2: instrumental data in a standard format
- 3: science-ready data
- 4: enhanced data products
instrument: str, optional, default ALL
Instrument to query. Supported values are "ALL", "VIS", and "NISP".
band: str, optional, default None
Instrument filter. This parameter is ignored when instrument="ALL". Valid values are:

- "VIS" for the VIS instrument
- "NIR_Y", "NIR_J", "NIR_H", or "NISP" for the NISP instrument
collection : str, optional, default sedm
Name of the data collection.
dsr_part1: str, optional, default None
First component of the dataset release identifier: for OTF environment, the activity code; for REG and IDR,
the target environment
dsr_part2: str, optional, default None
Second component of the dataset release identifier: for OTF environment, the patch id (a positive integer);
for REG and IDR, the activity code
dsr_part3: str, optional, default None
Third component of the dataset release identifier: for OTF, REG and IDR environment, the version
(an integer greater than 1)
output_file : string, optional, default None
Output file where the query results are written.
verbose : bool, optional, default False
Flag to display information about the process

Returns
-------
astropy.table.Table or str
Query results. If ``output_file`` is specified, the results are written to the given file in VOTable
format.
"""

valid_search_types = {'CIRCLE', 'BOX'}
valid_calibrations = {0: 'CALIB_ZERO', 1: 'CALIB_ONE', 2: 'CALIB_TWO', 3: 'CALIB_THREE'}
valid_instruments = {'ALL', 'VIS', 'NISP'}
valid_band_vis = {'VIS'}
valid_band_nisp = {'NIR_H', 'NIR_J', 'NIR_Y', 'NISP'}

if search_type not in valid_search_types:
raise ValueError(f"Invalid search tyype {search_type}")

if calibration is not None and calibration not in valid_calibrations:
raise ValueError(f"Invalid calibration {calibration}")

if instrument not in valid_instruments:
raise ValueError(f"Invalid instrument {instrument}")

if instrument == 'ALL' and band is not None:
raise ValueError(f"For instrument {instrument} band must be None")

if instrument == 'VIS' and band is not None and band not in valid_band_vis:
raise ValueError(f"Invalid band {band} for instrument {instrument}")

if instrument == 'NISP' and band is not None and band not in valid_band_nisp:
raise ValueError(f"Invalid band {band} for instrument {instrument}")

if coordinates is not None and radius is not None:
coord = commons.parse_coordinates(coordinates=coordinates)
ra_deg = coord.ra.degree
dec_deg = coord.dec.degree
radius_deg = esautils.get_degree_radius(radius)

if radius_deg <= 0.0:
raise ValueError(f"Search radius is zero or negative: {radius}")
else:
raise ValueError(f"Invalid coordinates or search radius: {coordinates}, {radius}")

params_dict = dict()
params_dict['TAPCLIENT'] = 'ASTROQUERY'
params_dict['POS'] = f"{search_type},{ra_deg},{dec_deg},{radius_deg}"
params_dict['INSTRUMENT'] = instrument
params_dict['COLLECTION'] = collection

if calibration is not None:
params_dict['CALIB'] = valid_calibrations[calibration]

if instrument != 'ALL' and band is not None:
params_dict['BAND'] = band

if dsr_part1 is not None:
params_dict['DSP1'] = dsr_part1

if dsr_part2 is not None:
params_dict['DSP2'] = dsr_part2

if dsr_part3 is not None:
params_dict['DSP3'] = dsr_part3

return self.__euclidsia.load_data(params_dict=params_dict, output_file=output_file, http_method='GET',
verbose=verbose)

@deprecated_renamed_argument(('instrument', 'id'), (None, None), since='0.4.12')
def get_cutout(self, *, file_path=None, coordinate, radius, output_file=None, verbose=False, instrument=None,
id=None):
Expand Down
56 changes: 56 additions & 0 deletions astroquery/esa/euclid/tests/data/sia_test.vot
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<VOTABLE version="1.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.ivoa.net/xml/VOTable/v1.4 http://www.ivoa.net/xml/VOTable/v1.4" xmlns="http://www.ivoa.net/xml/VOTable/v1.4">
<RESOURCE type="results">
<INFO name="QUERY_STATUS" value="OK" />
<INFO name="PROVIDER" value=""></INFO>
<TABLE>
<FIELD name="cutout_access_url" ucd="meta.ref.url" datatype="char" arraysize="*"/>
<FIELD name="facility_name" ucd="meta.id;instr.tel" datatype="char" arraysize="*"/>
<FIELD name="file_name" ucd="pos.eq.dec" datatype="char" arraysize="*"/>
<FIELD name="instrument_name" ucd="meta.id;instr" datatype="char" arraysize="*"/>
<FIELD name="filter" ucd="meta.id;band" datatype="char" arraysize="*"/>
<FIELD name="s_region" ucd="phys.angArea;obs" datatype="char" arraysize="*"/>
<FIELD name="s_ra" ucd="pos.eq.ra" datatype="char" arraysize="*"/>
<FIELD name="s_dec" ucd="pos.eq.dec" datatype="char" arraysize="*"/>
<FIELD name="obs_id" ucd="meta.id" datatype="char" arraysize="*"/>
<FIELD name="obs_collection" ucd="meta.id" datatype="char" arraysize="*"/>
<FIELD name="dataproduct_type" ucd="meta.id" datatype="char" arraysize="*"/>
<FIELD name="dataproduct_subtype" ucd="meta.id" datatype="char" arraysize="*"/>
<FIELD name="calib_level" ucd="meta.id" datatype="char" arraysize="*"/>
<DATA>
<TABLEDATA>
<TR>
<TD><![CDATA[https://easdev.esac.esa.int/sas-cutout/cutout?filepath=/data_03/repository_otf/MER/101007315/VIS/EUC_MER_BGSUB-MOSAIC-VIS_TILE101007315-D84386_20230826T000856.482420Z_00.00.fits.gz&collection=NISP&tileindex=101007315&POS=CIRCLE,89.0,-66.0,1.0]]></TD>
<TD></TD>
<TD>EUC_MER_BGSUB-MOSAIC-VIS_TILE101007315-D84386_20230826T000856.482420Z_00.00.fits.gz</TD>
<TD>NISP</TD>
<TD>NIR_J</TD>
<TD>{(1.56878970716861 , -1.15654637163971),(1.54566457897093 , -1.15654636650839),(1.54590379663453 , -1.14723910521964),(1.568550514417 , -1.14723911022404)}</TD>
<TD>89.2225775443200035</TD>
<TD>-66</TD>
<TD>null</TD>
<TD>sedm</TD>
<TD>mosaic</TD>
<TD>DpdMerBksMosaic</TD>
<TD>3</TD>
</TR>
<TR>
<TD><![CDATA[https://easdev.esac.esa.int/sas-cutout/cutout?filepath=/data_03/repository_otf/NIR/65602/EUC_NIR_W-CAL-IMAGE_H-65602-17_20230906T190444.924367Z.fits&collection=NISP&obsid=65602&POS=CIRCLE,89.0,-66.0,1.0]]></TD>
<TD></TD>
<TD>EUC_NIR_W-CAL-IMAGE_H-65602-17_20230906T190444.924367Z.fits</TD>
<TD>NISP</TD>
<TD>NIR_H</TD>
<TD>{(1.59364461496529 , -1.16117316244116),(1.5877646080787 , -1.16334176247736),(1.58179954392353 , -1.16549550886951),(1.5757606932609 , -1.16763184447562),(1.57013973108981 , -1.1695759346524),(1.5643381526667 , -1.16701623505239),(1.5587172508289 , -1.1645004743295),(1.55386389053321 , -1.16228607475105),(1.55304135179527 , -1.16190988595972),(1.54825944937331 , -1.15969489664624),(1.57153509266763 , -1.15142605710669)}</TD>
<TD>90.0032000000000068</TD>
<TD>-66.498500000000007</TD>
<TD>65602</TD>
<TD>sedm</TD>
<TD>image</TD>
<TD>DpdNirCalibratedFrame</TD>
<TD>2</TD>
</TR>
</TABLEDATA>
</DATA>
</TABLE>
</RESOURCE>
</VOTABLE>
21 changes: 21 additions & 0 deletions astroquery/esa/euclid/tests/test_euclid_remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,24 @@ def test_get_tables():

table = euclid.load_table("catalogue.mer_catalogue")
assert len(table.columns) == 471


@pytest.mark.remote_data
def test_get_sia():
coords = SkyCoord(267.78, 65.53, frame='icrs', unit="deg")
radius = u.Quantity(0.01, u.deg)

euclid = EuclidClass()

table = euclid.get_sia(coordinates=coords, radius=radius, calibration=3)
assert len(table.colnames) == 13
assert len(table['cutout_access_url']) == 9

assert 'POS=CIRCLE,267.78,65.53,0.01' in table['cutout_access_url'][0]

# Test coordinates as a string

table = euclid.get_sia(coordinates='267.7825, 65.5325', radius=radius, calibration=3)
assert len(table.colnames) == 13
assert 'POS=CIRCLE,267.7825,65.53252,0.01' in table['cutout_access_url'][0]
assert len(table['cutout_access_url']) == 9
Loading
Loading