diff --git a/CHANGES.rst b/CHANGES.rst
index 536e238f0a..7c13d31d95 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -113,6 +113,11 @@ gaia
- Added ``get_query_payload`` kwarg to return the ADQL query string. [#3539]
+esa.euclid
+^^^^^^^^^^
+
+- New method get_sia to access the Simple Image Access Protocol (SIAP) v2.0 [#3569]
+
esa.hubble
^^^^^^^^^^
diff --git a/astroquery/esa/euclid/core.py b/astroquery/esa/euclid/core.py
index 449a83fb43..7d742cf233 100644
--- a/astroquery/esa/euclid/core.py
+++ b/astroquery/esa/euclid/core.py
@@ -50,7 +50,7 @@ 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
@@ -58,11 +58,13 @@ def __init__(self, *, environment='PDR', tap_plus_conn_handler=None, datalink_ha
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'
@@ -123,6 +125,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="tap-server",
+ upload_context="Upload",
+ table_edit_context="TableTool",
+ data_context="sia2/query",
+ datalink_context="datalink",
+ 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()
@@ -665,6 +681,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")
@@ -709,6 +727,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
@@ -744,6 +770,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:
@@ -1059,7 +1091,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',
@@ -1442,6 +1474,110 @@ 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, *, search_type='CIRCLE', ra, dec, radius, calibration=2, instrument='ALL', band=None,
+ collection='sedm', dsr_part1=None, dsr_part2=None, dsr_part3=None, output_file=None, verbose=False):
+ """
+ Access the Euclid Observation Images by VO SIAP v2.0. This service will return public images from Calibrated
+ and Stacked NISP and VIS images, MER Mosaics from VIS and NISP and Level 1 (RAW) images for NISP and VIS
+
+ Parameters
+ ----------
+ search_type : str, mandatory, default None
+ search region: CIRCLE or BOX
+ ra : float (degrees), str or astropy.coordinate, mandatory
+ right ascension
+ dec : float (degrees), str or astropy.coordinate, mandatory
+ declination
+ radius : float (degrees), str or astropy.coordinate, mandatory
+ search radius of the cutout to generate
+ calibration: int, optional, default 2
+ calibration level according to ObsCore VO standard: 0 (raw instrumental data), 1 (instrumental data in a
+ standard format), 2 (science ready data) or 3 (enhanced data products).
+ instrument: str, mandatory, default ALL
+ instrument name: ALL, VIS or NISP
+ band: str, optional, default None
+ filter name only valid if instrument is different from ALL: VIS for instrument VIS or NIR_H, NIR_J, NIR_Y
+ or NISP for instrument NISP
+ collection : str, mandatory, default sedm
+ the name of the data collection
+ dsr_part1: str, optional, default None
+ the data set release part 1: for OTF environment, the activity code; for REG and IDR, the target environment
+ dsr_part2: str, optional, default None
+ the data set release part 2: for OTF environment, the patch id (a positive integer); for REG and IDR,
+ the activity code
+ dsr_part3: str, optional, default None
+ the data set release part 3: for OTF, REG and IDR environment, the version (an integer greater than 1)
+ output_file : string, optional, default None
+ file where the results are saved.
+ verbose : bool, optional, default 'False'
+ flag to display information about the process
+
+ Returns
+ -------
+ A table object or votable file
+ """
+
+ 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}")
+
+ ra_deg = self.coordinates_degrees(ra)
+ dec_deg = self.coordinates_degrees(dec)
+ radius_deg = self.coordinates_degrees(radius)
+
+ params_dict = dict()
+ params_dict['TAPCLIENT'] = 'ASTROQUERY'
+ params_dict[
+ 'POS'] = f"{search_type},{ra_deg.to_value(u.deg)},{dec_deg.to_value(u.deg)},{radius_deg.to_value(u.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)
+
+ def coordinates_degrees(self, coord):
+
+ if not isinstance(coord, units.Quantity):
+ radius_quantity = Quantity(value=coord, unit=u.deg)
+ else:
+ radius_quantity = coord.to(u.deg)
+ return radius_quantity
+
@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):
diff --git a/astroquery/esa/euclid/tests/data/sia_test.vot b/astroquery/esa/euclid/tests/data/sia_test.vot
new file mode 100644
index 0000000000..fab4a4d902
--- /dev/null
+++ b/astroquery/esa/euclid/tests/data/sia_test.vot
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ EUC_MER_BGSUB-MOSAIC-VIS_TILE101007315-D84386_20230826T000856.482420Z_00.00.fits.gz |
+ NISP |
+ NIR_J |
+ {(1.56878970716861 , -1.15654637163971),(1.54566457897093 , -1.15654636650839),(1.54590379663453 , -1.14723910521964),(1.568550514417 , -1.14723911022404)} |
+ 89.2225775443200035 |
+ -66 |
+ null |
+ sedm |
+ mosaic |
+ DpdMerBksMosaic |
+ 3 |
+
+
+ |
+ |
+ EUC_NIR_W-CAL-IMAGE_H-65602-17_20230906T190444.924367Z.fits |
+ NISP |
+ NIR_H |
+ {(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)} |
+ 90.0032000000000068 |
+ -66.498500000000007 |
+ 65602 |
+ sedm |
+ image |
+ DpdNirCalibratedFrame |
+ 2 |
+
+
+
+
+
+
diff --git a/astroquery/esa/euclid/tests/test_euclidtap.py b/astroquery/esa/euclid/tests/test_euclidtap.py
index 05877adffb..5d6a13ae62 100644
--- a/astroquery/esa/euclid/tests/test_euclidtap.py
+++ b/astroquery/esa/euclid/tests/test_euclidtap.py
@@ -43,6 +43,8 @@
TABLE_FILE_NAME = get_pkg_data_filename(os.path.join("data", '1714556098855O-result.vot'), package=package)
TABLE_DATA = Path(TABLE_FILE_NAME).read_text()
+TABLE_SIA_FILE_NAME = get_pkg_data_filename(os.path.join("data", 'sia_test.vot'), package=package)
+
RADIUS = 1 * u.deg
SKYCOORD = SkyCoord(ra=19 * u.deg, dec=20 * u.deg, frame="icrs")
@@ -115,6 +117,8 @@ def mock_querier_async():
conn_handler = DummyConnHandler()
tapplus = TapPlus(url="http://test:1111/tap", connhandler=conn_handler)
cutout_handler = TapPlus(url="http://test:1111/tap", connhandler=conn_handler)
+ sia_handler = TapPlus(url="http://test:1111/tap", connhandler=conn_handler)
+
jobid = "12345"
launch_response = DummyResponse(303)
@@ -143,7 +147,7 @@ def mock_querier_async():
conn_handler.set_response("async/1479386030738O/results/result", results_response)
return EuclidClass(tap_plus_conn_handler=conn_handler, datalink_handler=tapplus, cutout_handler=cutout_handler,
- show_server_messages=False)
+ sia_handler=sia_handler, show_server_messages=False)
@pytest.fixture(scope="module")
@@ -182,7 +186,7 @@ def test_load_environments():
environment = 'WRONG'
try:
- tap = EuclidClass(environment='WRONG')
+ EuclidClass(environment='WRONG')
except Exception as e:
assert str(e).startswith(f"Invalid environment {environment}. Valid values: {list(conf.ENVIRONMENTS.keys())}")
@@ -1636,10 +1640,10 @@ def test_login(mock_login):
tapplus = TapPlus(url="https://test:1111/tap", connhandler=conn_handler)
tap = EuclidClass(tap_plus_conn_handler=conn_handler, datalink_handler=tapplus, show_server_messages=False)
tap.login(user="user", password="password")
- assert (mock_login.call_count == 3)
+ assert (mock_login.call_count == 4)
mock_login.side_effect = HTTPError("Login error")
tap.login(user="user", password="password")
- assert (mock_login.call_count == 4)
+ assert (mock_login.call_count == 5)
@patch.object(TapPlus, 'login_gui')
@@ -1649,7 +1653,7 @@ def test_login_gui(mock_login_gui, mock_login):
tapplus = TapPlus(url="http://test:1111/tap", connhandler=conn_handler)
tap = EuclidClass(tap_plus_conn_handler=conn_handler, datalink_handler=tapplus, show_server_messages=False)
tap.login_gui()
- assert (mock_login_gui.call_count == 2)
+ assert (mock_login_gui.call_count == 3)
mock_login_gui.side_effect = HTTPError("Login error")
tap.login(user="user", password="password")
assert (mock_login.call_count == 1)
@@ -1661,10 +1665,10 @@ def test_logout(mock_logout):
tapplus = TapPlus(url="http://test:1111/tap", connhandler=conn_handler)
tap = EuclidClass(tap_plus_conn_handler=conn_handler, datalink_handler=tapplus, show_server_messages=False)
tap.logout()
- assert (mock_logout.call_count == 3)
+ assert (mock_logout.call_count == 4)
mock_logout.side_effect = HTTPError("Login error")
tap.logout()
- assert (mock_logout.call_count == 4)
+ assert (mock_logout.call_count == 5)
def test_get_datalinks(monkeypatch):
@@ -1900,6 +1904,71 @@ def test_load_async_job(mock_querier_async):
assert job.jobid == jobid
+@pytest.mark.parametrize("verbose", [False, True])
+def test_get_sia(monkeypatch, verbose):
+ def load_data_monkeypatch(self, params_dict, output_file, http_method, verbose):
+ return Table.read(TABLE_SIA_FILE_NAME, format='votable')
+
+ monkeypatch.setattr(TapPlus, "load_data", load_data_monkeypatch)
+ euclid = EuclidClass(show_server_messages=False)
+
+ table = euclid.get_sia(ra=89.0, dec=-66.0, radius=1.0, verbose=verbose)
+ assert isinstance(table, Table)
+ fn = 'file_name'
+ assert table[fn][0] == 'EUC_MER_BGSUB-MOSAIC-VIS_TILE101007315-D84386_20230826T000856.482420Z_00.00.fits.gz'
+
+ table = euclid.get_sia(ra=89.0, dec=-66.0, radius=1.0, dsr_part1='CALBLOCK', dsr_part2='PV-023', dsr_part3=1,
+ verbose=verbose)
+ assert isinstance(table, Table)
+ assert table[fn][0] == 'EUC_MER_BGSUB-MOSAIC-VIS_TILE101007315-D84386_20230826T000856.482420Z_00.00.fits.gz'
+
+
+def test_get_sia_exceptions(monkeypatch):
+ def load_data_monkeypatch(self, params_dict, output_file, http_method, verbose):
+ return Table()
+
+ monkeypatch.setattr(TapPlus, "load_data", load_data_monkeypatch)
+ euclid = EuclidClass(show_server_messages=False)
+
+ error_message = "Invalid search tyype XX"
+ with pytest.raises(ValueError, match=error_message):
+ euclid.get_sia(search_type='XX', ra=89.0, dec=-66.0, radius=1.0, verbose=True)
+
+ error_message = "Invalid instrument XX"
+ with pytest.raises(ValueError, match=error_message):
+ euclid.get_sia(instrument='XX', ra=89.0, dec=-66.0, radius=1.0, verbose=True)
+
+ error_message = "For instrument ALL band must be None"
+ with pytest.raises(ValueError, match=error_message):
+ euclid.get_sia(instrument='ALL', band='XX', ra=89.0, dec=-66.0, radius=1.0, verbose=True)
+
+ error_message = "Invalid band NIR_H for instrument VIS"
+ with pytest.raises(ValueError, match=error_message):
+ euclid.get_sia(instrument='VIS', band='NIR_H', ra=89.0, dec=-66.0, radius=1.0, verbose=True)
+
+ error_message = "Invalid band VIS for instrument NISP"
+ with pytest.raises(ValueError, match=error_message):
+ euclid.get_sia(instrument='NISP', band='VIS', ra=89.0, dec=-66.0, radius=1.0, verbose=True)
+
+ error_message = "Invalid calibration 5"
+ with pytest.raises(ValueError, match=error_message):
+ euclid.get_sia(calibration=5, ra=89.0, dec=-66.0, radius=1.0, verbose=True)
+
+
+def test_coordinates_degrees():
+ euclid = EuclidClass(show_server_messages=False)
+
+ result = euclid.coordinates_degrees(125.0)
+
+ assert isinstance(result, Quantity)
+
+ q = Quantity(1.0, unit=u.arcmin)
+
+ result = euclid.coordinates_degrees(q)
+
+ assert isinstance(result, Quantity)
+
+
def remove_temp_dir():
dirs = glob.glob('./temp_*')
for dir_path in dirs:
diff --git a/astroquery/utils/tap/core.py b/astroquery/utils/tap/core.py
index 3f9fe6f933..56e0fb737e 100755
--- a/astroquery/utils/tap/core.py
+++ b/astroquery/utils/tap/core.py
@@ -853,7 +853,7 @@ def load_tables(self, *, only_names=False, include_shared_tables=False, verbose=
return self._Tap__load_tables(only_names=only_names, include_shared_tables=include_shared_tables, # noqa
verbose=verbose)
- def load_data(self, *, params_dict=None, output_file=None, verbose=False):
+ def load_data(self, *, params_dict=None, output_file=None, http_method='POST', verbose=False):
"""Loads the specified data
Parameters
@@ -863,6 +863,8 @@ def load_data(self, *, params_dict=None, output_file=None, verbose=False):
output_file : string, optional, default None
file where the results are saved.
If it is not provided, the http response contents are returned.
+ http_method: string, compulsory, default POST
+ HTTP Request Method: POST or GET
verbose : bool, optional, default 'False'
flag to display information about the process
@@ -879,12 +881,20 @@ def load_data(self, *, params_dict=None, output_file=None, verbose=False):
data = urlencode(params_dict)
if verbose:
print(f"Data request: {data}")
- response = connHandler.execute_datapost(data=data, verbose=verbose)
+
+ if http_method == 'POST':
+ response = connHandler.execute_datapost(data=data, verbose=verbose)
+ else:
+ response = connHandler.execute_dataget(query=data, verbose=verbose)
+
if verbose:
print(response.status, response.reason)
+
connHandler.check_launch_response_status(response, verbose, 200)
+
if verbose:
print("Reading...")
+
chunk = True
if output_file is not None:
with open(output_file, 'wb') as file:
@@ -903,9 +913,12 @@ def load_data(self, *, params_dict=None, output_file=None, verbose=False):
output_format = params_dict['FORMAT'].lower()
else:
output_format = "votable"
+
results = utils.read_http_response(response, output_format, use_names_over_ids=self.use_names_over_ids)
+
if verbose:
print("Done.")
+
return results
def load_groups(self, *, verbose=False):
diff --git a/docs/esa/euclid/euclid.rst b/docs/esa/euclid/euclid.rst
index 404327aaa0..00c6f2adbe 100644
--- a/docs/esa/euclid/euclid.rst
+++ b/docs/esa/euclid/euclid.rst
@@ -323,8 +323,8 @@ and their sky coverage (in its "fov" field) is queried using ADQL_. Please note:
* Given the size of the Euclid FITS images (~1.4 GB for the MER images and ~7 GB for calibrated VIS images) downloading individual files is time consuming (depending on the internet bandwith).
* This step can be skipped if using ESA Datalabs_ (as direct access to the products is possible).
-.. Skip testing as the example requires a lot of time to download a huge file
.. doctest-skip::
+.. Skip testing as the example requires a lot of time to download a huge file
>>> file_name = res['file_name'][0]
>>> print("Downloading file:", file_name)
@@ -356,6 +356,65 @@ and their sky coverage (in its "fov" field) is queried using ADQL_. Please note:
:height: 500px
:scale: 100%
+The Euclid archive provides the VO Simple Image Access Protocol (SIAP) v2.0 to access the Euclid Observation Images. This service returns public images from:
+
+1. calibrated and Stacked NISP and VIS images;
+2. MER Mosaics from VIS and NISP.
+
+This service returns the votable that contains all the necessary information to be used by the cutout function: the path to the fits files (file_name), the instrument, and id (obs_id). Note that the votable can also saved for a latter usage.
+
+ >>> from astroquery.esa.euclid import Euclid
+ >>> from astropy.coordinates import SkyCoord
+ >>> import astropy.units as u
+ >>> coords = SkyCoord(267.78, 65.53, frame='icrs', unit="deg") # NGC 6505
+ >>> radius = u.Quantity(0.01, u.deg)
+ >>> table = Euclid.get_sia(ra=coords.ra, dec=coords.dec, radius=radius, calibration=3)
+ >>> print(table.info)
+
+ name dtype
+ ------------------- ------
+ cutout_access_url object
+ facility_name object
+ file_name object
+ instrument_name object
+ filter object
+ s_region object
+ s_ra object
+ s_dec object
+ tile_index object
+ obs_collection object
+ dataproduct_type object
+ dataproduct_subtype object
+ calib_level object
+
+ >>> print(table)
+ cutout_access_url ...
+ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ...
+ https://eas.esac.esa.int/sas-cutout/cutout?filepath=/euclid/repository_idr/iqr1/Q1_R1/MER/102158889/GPC/EUC_MER_BGSUB-MOSAIC-PANSTARRS-I_TILE102158889-5BC663_20241024T203110.498476Z_00.00.fits&collection=GPC&tileindex=102158889&POS=CIRCLE,267.78,65.53,0.01 ...
+ https://eas.esac.esa.int/sas-cutout/cutout?filepath=/euclid/repository_idr/iqr1/Q1_R1/MER/102158889/HSC/EUC_MER_BGSUB-MOSAIC-WISHES-G_TILE102158889-3DC3C3_20241024T205647.635112Z_00.00.fits&collection=HSC&tileindex=102158889&POS=CIRCLE,267.78,65.53,0.01 ...
+ https://eas.esac.esa.int/sas-cutout/cutout?filepath=/euclid/repository_idr/iqr1/Q1_R1/MER/102158889/HSC/EUC_MER_BGSUB-MOSAIC-WISHES-Z_TILE102158889-60A7E6_20241024T204143.931276Z_00.00.fits&collection=HSC&tileindex=102158889&POS=CIRCLE,267.78,65.53,0.01 ...
+ https://eas.esac.esa.int/sas-cutout/cutout?filepath=/euclid/repository_idr/iqr1/Q1_R1/MER/102158889/MEGACAM/EUC_MER_BGSUB-MOSAIC-CFIS-R_TILE102158889-4366B7_20241024T203624.450577Z_00.00.fits&collection=MEGACAM&tileindex=102158889&POS=CIRCLE,267.78,65.53,0.01 ...
+ https://eas.esac.esa.int/sas-cutout/cutout?filepath=/euclid/repository_idr/iqr1/Q1_R1/MER/102158889/MEGACAM/EUC_MER_BGSUB-MOSAIC-CFIS-U_TILE102158889-9E97F_20241024T204431.839748Z_00.00.fits&collection=MEGACAM&tileindex=102158889&POS=CIRCLE,267.78,65.53,0.01 ...
+ https://eas.esac.esa.int/sas-cutout/cutout?filepath=/euclid/repository_idr/iqr1/Q1_R1/MER/102158889/NISP/EUC_MER_BGSUB-MOSAIC-NIR-H_TILE102158889-ED035A_20241024T212936.705156Z_00.00.fits&collection=NISP&tileindex=102158889&POS=CIRCLE,267.78,65.53,0.01 ...
+ https://eas.esac.esa.int/sas-cutout/cutout?filepath=/euclid/repository_idr/iqr1/Q1_R1/MER/102158889/NISP/EUC_MER_BGSUB-MOSAIC-NIR-J_TILE102158889-B8D44B_20241024T215040.579149Z_00.00.fits&collection=NISP&tileindex=102158889&POS=CIRCLE,267.78,65.53,0.01 ...
+ https://eas.esac.esa.int/sas-cutout/cutout?filepath=/euclid/repository_idr/iqr1/Q1_R1/MER/102158889/NISP/EUC_MER_BGSUB-MOSAIC-NIR-Y_TILE102158889-AC6585_20241024T225321.344048Z_00.00.fits&collection=NISP&tileindex=102158889&POS=CIRCLE,267.78,65.53,0.01 ...
+ https://eas.esac.esa.int/sas-cutout/cutout?filepath=/euclid/repository_idr/iqr1/Q1_R1/MER/102158889/VIS/EUC_MER_BGSUB-MOSAIC-VIS_TILE102158889-F95D3B_20241025T024806.508980Z_00.00.fits&collection=VIS&tileindex=102158889&POS=CIRCLE,267.78,65.53,0.01 ...
+
+The path of the fits file can be obtained by a simple regular expression.
+
+ >>> import re
+ >>> p = re.compile(r"filepath=(.*?)&")
+ >>> for i in [(p.search(t["cutout_access_url"]).group(1), t["instrument_name"]) for t in table]:
+ ... print(i)
+ ('/euclid/repository_idr/iqr1/Q1_R1/MER/102158889/GPC/EUC_MER_BGSUB-MOSAIC-PANSTARRS-I_TILE102158889-5BC663_20241024T203110.498476Z_00.00.fits', 'GPC')
+ ('/euclid/repository_idr/iqr1/Q1_R1/MER/102158889/HSC/EUC_MER_BGSUB-MOSAIC-WISHES-G_TILE102158889-3DC3C3_20241024T205647.635112Z_00.00.fits', 'HSC')
+ ('/euclid/repository_idr/iqr1/Q1_R1/MER/102158889/HSC/EUC_MER_BGSUB-MOSAIC-WISHES-Z_TILE102158889-60A7E6_20241024T204143.931276Z_00.00.fits', 'HSC')
+ ('/euclid/repository_idr/iqr1/Q1_R1/MER/102158889/MEGACAM/EUC_MER_BGSUB-MOSAIC-CFIS-R_TILE102158889-4366B7_20241024T203624.450577Z_00.00.fits', 'MEGACAM')
+ ('/euclid/repository_idr/iqr1/Q1_R1/MER/102158889/MEGACAM/EUC_MER_BGSUB-MOSAIC-CFIS-U_TILE102158889-9E97F_20241024T204431.839748Z_00.00.fits', 'MEGACAM')
+ ('/euclid/repository_idr/iqr1/Q1_R1/MER/102158889/NISP/EUC_MER_BGSUB-MOSAIC-NIR-H_TILE102158889-ED035A_20241024T212936.705156Z_00.00.fits', 'NISP')
+ ('/euclid/repository_idr/iqr1/Q1_R1/MER/102158889/NISP/EUC_MER_BGSUB-MOSAIC-NIR-J_TILE102158889-B8D44B_20241024T215040.579149Z_00.00.fits', 'NISP')
+ ('/euclid/repository_idr/iqr1/Q1_R1/MER/102158889/NISP/EUC_MER_BGSUB-MOSAIC-NIR-Y_TILE102158889-AC6585_20241024T225321.344048Z_00.00.fits', 'NISP')
+ ('/euclid/repository_idr/iqr1/Q1_R1/MER/102158889/VIS/EUC_MER_BGSUB-MOSAIC-VIS_TILE102158889-F95D3B_20241025T024806.508980Z_00.00.fits', 'VIS')
1.7. MER Cutouts
^^^^^^^^^^^^^^^^
@@ -371,8 +430,8 @@ It is also possible to download just small portions of the MER (background subtr
Download the cutout...
-.. Skip testing as the example requires a lot of time to download a huge file
.. doctest-skip::
+.. Skip testing as the example requires a lot of time to download a huge file
>>> file_path = f"{res['file_path'][0]}/{res['file_name'][0]}"
>>> cutout_out = Euclid.get_cutout(file_path=file_path, coordinate='NGC 6505', radius= 0.1 * u.arcmin, output_file='ngc6505_cutout_mer.fits')
@@ -502,8 +561,8 @@ package will also be available:
There are several ways to log in to the Euclid archive, as detailed below:
-.. Skip testing as the example require authentication
.. doctest-skip::
+.. Skip testing as the example require authentication
>>> from astroquery.esa.euclid import Euclid
>>> Euclid.login_gui() # Login via graphic interface (pop-up window)
@@ -518,8 +577,8 @@ There are several ways to log in to the Euclid archive, as detailed below:
All the asynchronous jobs launched by registered users are stored in the user area, which can store up to 10 GB of jobs. Therefore, it is recommended to remove unnecessary jobs to avoid filling up the user quota.
The example below shows how to delete all the jobs in the user area using the list_async_jobs and remove_jobs_ methods.
-.. Skip testing as the example require authentication
.. doctest-skip::
+.. Skip testing as the example require authentication
>>> Euclid.login()
>>> job_ids = [job.jobid for job in Euclid.list_async_jobs()]
@@ -530,8 +589,8 @@ It is also possible to take advantage of the job metadata to delete all the jobs
First, use the load_async_job_ method to download the metadata of the async jobs stored in the user space:
-.. Skip testing as the example require authentication
.. doctest-skip::
+.. Skip testing as the example require authentication
>>> job_obj = [Euclid.load_async_job(jobid=jobid) for jobid in job_ids]
>>> job_ids = [job.jobid for job in job_obj]
@@ -548,8 +607,8 @@ Second, create a dataframe that contains the jobid and date information:
Finally, extract the job id's included in a given time range (in the example below, all the jobs stored since 2024-10-01 at 7 hours UTC) and delete them:
-.. Skip testing as the example require authentication
.. doctest-skip::
+.. Skip testing as the example require authentication
>>> subset = df[(df['date'] == datetime.date(2024,10,1)) & (df['hour_UTC'].isin([7]))]
>>> jobs_to_delete = subset['job_id'].to_list()