diff --git a/CHANGES.rst b/CHANGES.rst index 1a33980a49..9854bb0dca 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -147,6 +147,10 @@ mast - Resolved issue making PANSTARRS catalog queries when columns and sorting is specified. [#2727] +- Added warning message for ``Tesscut`` requests when input cutout size reaches a + limit of 30 pixels in either dimension, and enables users to modify the request + timeout upper limit from the default 600 seconds. [#2693] + nist ^^^^ diff --git a/astroquery/mast/cutouts.py b/astroquery/mast/cutouts.py index 8ca35cb3da..06d3d272d9 100644 --- a/astroquery/mast/cutouts.py +++ b/astroquery/mast/cutouts.py @@ -25,7 +25,9 @@ from astropy.table import Table from astropy.io import fits -from ..exceptions import InputWarning, NoResultsWarning, InvalidQueryError +from . import conf +from .. import log +from ..exceptions import InputWarning, LargeQueryWarning, NoResultsWarning, InvalidQueryError from .utils import parse_input_location from .core import MastQueryWithLogin @@ -34,7 +36,7 @@ __all__ = ["TesscutClass", "Tesscut", "ZcutClass", "Zcut"] -def _parse_cutout_size(size): +def _parse_cutout_size(size, timeout=None, mission=None): """ Take a user input cutout size and parse it into the regular format [ny,nx] where nx/ny are quantities with units either pixels or degrees. @@ -48,6 +50,17 @@ def _parse_cutout_size(size): ``(ny, nx)`` order. Scalar numbers in ``size`` are assumed to be in units of pixels. `~astropy.units.Quantity` objects must be in pixel or angular units. + mission : str, optional + The mission for which the size parsing is being done. This parameter + is mainly meant to trigger a cutout size warning specifically for TESSCut + requests. Default is None. + timeout : int or float, optional + The modified request timeout limit. + The request processing time by default is 600 seconds, meaning an attempt at communicating + with the API will take 600 seconds before timing out. In the context of this function, this + parameter is meant to keep track of whether or not the timeout limit has been modified, which + will affect whether or not a warning message about the cutout size gets triggered. + Default is None. Returns ------- @@ -56,35 +69,67 @@ def _parse_cutout_size(size): either pixels or degrees. """ + # This local variable will change to True if input cutout size exceeds recommended limits for TESS + limit_reached = False + + # Checking 2d size inputs for the recommended cutout size + if (mission == 'TESS') & (not isinstance(size, (int, float, u.Quantity))): + if len(size) == 2: + if np.isscalar(size[0]): + size = [size[0] * u.pixel, size[1] * u.pixel] + + with u.set_enabled_equivalencies(u.pixel_scale(21 * u.arcsec / u.pixel)): + limit_reached = (size * size[0].unit > 30 * u.pixel).any() + # Making size into an array [ny, nx] if np.isscalar(size): size = np.repeat(size, 2) + if mission == 'TESS': + limit_reached = (size > 30).any() + if isinstance(size, u.Quantity): size = np.atleast_1d(size) + if len(size) == 1: size = np.repeat(size, 2) + # Based on the literature, TESS resolution is approx. 21 arcseconds per pixel. + # We will convert the recommended upper limit for a dimension from pixels + # to the unit being passed. + if mission == 'TESS': + with u.set_enabled_equivalencies(u.pixel_scale(21 * u.arcsec / u.pixel)): + limit_reached = (size > 30 * u.pixel).any() + if len(size) > 2: warnings.warn("Too many dimensions in cutout size, only the first two will be used.", InputWarning) # Getting x and y out of the size + if np.isscalar(size[0]): x = size[1] y = size[0] units = "px" + elif size[0].unit == u.pixel: x = size[1].value y = size[0].value units = "px" + elif size[0].unit.physical_type == 'angle': x = size[1].to(u.deg).value y = size[0].to(u.deg).value units = "d" + else: raise InvalidQueryError("Cutout size must be in pixels or angular quantity.") + if (limit_reached) & (not timeout): + warnings.warn("You have selected a large cutout size that may result in a timeout error. We suggest limiting" + " the size of your requested cutout, or changing the request timeout limit from its" + " default 600 seconds to something higher, using the timeout argument.", LargeQueryWarning) + return {"x": x, "y": y, "units": units} @@ -108,6 +153,7 @@ def __init__(self): def get_sectors(self, *, coordinates=None, radius=0*u.deg, product='SPOC', objectname=None, moving_target=False, mt_type=None): + """ Get a list of the TESS data sectors whose footprints intersect with the given search area. @@ -223,7 +269,8 @@ def get_sectors(self, *, coordinates=None, radius=0*u.deg, product='SPOC', objec return Table(sector_dict) def download_cutouts(self, *, coordinates=None, size=5, sector=None, product='SPOC', path=".", - inflate=True, objectname=None, moving_target=False, mt_type=None, verbose=False): + inflate=True, objectname=None, moving_target=False, mt_type=None, verbose=False, + timeout=None): """ Download cutout target pixel file(s) around the given coordinates with indicated size. @@ -280,12 +327,24 @@ def download_cutouts(self, *, coordinates=None, size=5, sector=None, product='SP first majorbody is tried and then smallbody if a matching majorbody is not found. NOTE: If moving_target is supplied, this argument is ignored. + timeout : int or float, optional + The modified request timeout limit. + The request processing time by default is 600 seconds, meaning an attempt at communicating + with the API will take 600 seconds before timing out. The timeout upper limit can be modified + using this argument for large cutout requests via TESSCut. Default is None. Returns ------- response : `~astropy.table.Table` """ + # Modify TIMEOUT attribute if necessary (usually this is modified for large requests) + if timeout: + default_timeout = conf.timeout + self._service_api_connection.TIMEOUT = timeout + log.info(f"Request timeout upper limit is being changed to {self._service_api_connection.TIMEOUT}" + " seconds.") + if moving_target: # The Moving Targets service is currently only available for SPOC @@ -315,7 +374,7 @@ def download_cutouts(self, *, coordinates=None, size=5, sector=None, product='SP astrocut_request = f"astrocut?ra={coordinates.ra.deg}&dec={coordinates.dec.deg}" # Adding the arguments that are common between moving/still astrocut requests - size_dict = _parse_cutout_size(size) + size_dict = _parse_cutout_size(size, timeout=timeout, mission='TESS') astrocut_request += f"&y={size_dict['y']}&x={size_dict['x']}&units={size_dict['units']}" # Making sure input product is either SPOC or TICA, @@ -356,10 +415,14 @@ def download_cutouts(self, *, coordinates=None, size=5, sector=None, product='SP os.remove(zipfile_path) localpath_table['Local Path'] = [path+x for x in cutout_files] + + if timeout: + self._service_api_connection.TIMEOUT = default_timeout + return localpath_table def get_cutouts(self, *, coordinates=None, size=5, product='SPOC', sector=None, - objectname=None, moving_target=False, mt_type=None): + objectname=None, moving_target=False, mt_type=None, timeout=None): """ Get cutout target pixel file(s) around the given coordinates with indicated size, and return them as a list of `~astropy.io.fits.HDUList` objects. @@ -408,14 +471,26 @@ def get_cutouts(self, *, coordinates=None, size=5, product='SPOC', sector=None, first majorbody is tried and then smallbody if a matching majorbody is not found. NOTE: If moving_target is supplied, this argument is ignored. + timeout : int or float, optional + The modified request timeout limit. + The request processing time by default is 600 seconds, meaning an attempt at communicating + with the API will take 600 seconds before timing out. The timeout upper limit can be modified + using this argument for large cutout requests via TESSCut. Default is None. Returns ------- response : A list of `~astropy.io.fits.HDUList` objects. """ + # Modify TIMEOUT attribute if necessary (usually this is modified for large requests) + if timeout: + default_timeout = conf.timeout + self._service_api_connection.TIMEOUT = timeout + log.info(f"Request timeout upper limit is being changed to {self._service_api_connection.TIMEOUT}" + " seconds.") + # Setting up the cutout size - param_dict = _parse_cutout_size(size) + param_dict = _parse_cutout_size(size, timeout=timeout, mission='TESS') # Add sector if present if sector: @@ -485,6 +560,9 @@ def get_cutouts(self, *, coordinates=None, size=5, product='SPOC', sector=None, # preserve the original filename in the fits object cutout_hdus_list[-1].filename = name + if timeout: + self._service_api_connection.TIMEOUT = default_timeout + return cutout_hdus_list @@ -595,6 +673,7 @@ def download_cutouts(self, coordinates, *, size=5, survey=None, cutout_format="f response : `~astropy.table.Table` Cutout file(s) for given coordinates """ + # Get Skycoord object for coordinates/object coordinates = parse_input_location(coordinates) size_dict = _parse_cutout_size(size) diff --git a/astroquery/mast/tests/test_mast_remote.py b/astroquery/mast/tests/test_mast_remote.py index f70f6dd236..62dd829a4c 100644 --- a/astroquery/mast/tests/test_mast_remote.py +++ b/astroquery/mast/tests/test_mast_remote.py @@ -15,8 +15,8 @@ from astroquery import mast from ..utils import ResolverError -from ...exceptions import (InputWarning, InvalidQueryError, MaxResultsWarning, - NoResultsWarning) +from ...exceptions import (InputWarning, InvalidQueryError, LargeQueryWarning, + MaxResultsWarning, NoResultsWarning) OBSID = '1647157' @@ -949,7 +949,7 @@ def test_tesscut_download_cutouts_mt(self, tmpdir): assert error_tica_mt in str(error_msg.value) @pytest.mark.parametrize("product", ["tica", "spoc"]) - def test_tesscut_get_cutouts(self, product): + def test_tesscut_get_cutouts(self, product, caplog): coord = SkyCoord(107.18696, -70.50919, unit="deg") @@ -975,6 +975,14 @@ def test_tesscut_get_cutouts(self, product): assert len(cutout_hdus_list) >= 1 assert isinstance(cutout_hdus_list[0], fits.HDUList) + # Check that an INFO message is returned when timeout is adjusted + mast.Tesscut.get_cutouts(product=product, coordinates=coord, size=5, timeout=1000) + with caplog.at_level("INFO", logger="astroquery"): + assert "timeout upper limit is being changed" in caplog.text + + # Ensure that timeout returns to default (600 seconds) after adjusted in previous call + assert mast.Tesscut._service_api_connection.TIMEOUT == 600 + def test_tesscut_get_cutouts_mt(self): # Moving target functionality testing @@ -1027,6 +1035,16 @@ def test_tesscut_get_cutouts_mt(self): moving_target=True) assert error_tica_mt in str(error_msg.value) + @pytest.mark.xfail(raises=LargeQueryWarning) + @pytest.mark.parametrize("product", ["tica", "spoc"]) + @pytest.mark.parametrize("size", [31, [5, 60], 0.2 * u.deg, [0.1 * u.deg, 0.2 * u.deg], + 5000 * u.arcsec, 20 * u.arcmin]) + def test_tesscut_timeout_param(self, product, size): + + # Check that a warning comes up when cutout size too big + coordinates = '60 60' + mast.Tesscut.get_cutouts(product=product, coordinates=coordinates, size=size) + ################### # ZcutClass tests # ################### diff --git a/docs/mast/mast.rst b/docs/mast/mast.rst index 322fcd7115..cb971d2a0a 100644 --- a/docs/mast/mast.rst +++ b/docs/mast/mast.rst @@ -1065,6 +1065,29 @@ and because the TICA products are not available for sectors 1-26, we request cut ---------------------------------------------------------- ./tica-s0027-4-2_107.186960_-70.509190_21x14_astrocut.fits +It is important to be mindful of the requested cutout size when using either `~astroquery.mast.TesscutClass.download_cutouts` or `~astroquery.mast.TesscutClass.get_cutouts`, +as it will affect the time it takes to retrieve your cutouts. By default, any request that ``astroquery.mast`` makes to an +API is capped at 600 seconds. Queries that take longer than this will yield a timeout error. +The recommended cutout size for TESSCut is no larger than 30 pixels in either the X or Y +direction, so a user will be met with a warning message if the input cutout size exceeds +those limits. Below is an example of a request using `~astroquery.mast.TesscutClass.get_cutouts` for cutouts of size 0.2 x 0.2 degrees-squared, which is +around 34 x 34 pixels-squared. + +.. doctest-skip:: + + >>> import astropy.units as u + >>> from astroquery.mast import Tesscut + >>> from astropy.coordinates import SkyCoord + ... + >>> cutout_coord = SkyCoord(107.18696, -70.50919, unit="deg") + >>> hdulist = Tesscut.get_cutouts(coordinates=cutout_coord, size=0.2*u.deg) + WARNING: LargeQueryWarning: You have selected a large cutout size that may result in a timeout error. + We suggest limiting the size of your requested cutout, or changing the request timeout limit from + its default 600 seconds to something higher, using the timeout argument. [astroquery.mast.cutouts] + +At this point, users may choose to decrease their cutout size or extend the request timeout limit from +600 seconds to something longer. + Sector information ------------------