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
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ mast

- Raise informative error if ``MastMissions`` query radius is too large. [#3447]

- Separate requests for moving target cutouts in ``Tesscut`` to one per sector. [#3467]

jplspec
^^^^^^^

Expand Down
77 changes: 77 additions & 0 deletions astroquery/mast/cutouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,32 @@ def _validate_product(self, product):
if product.upper() != "SPOC":
raise InvalidQueryError("Input product must be SPOC.")

def _get_moving_target_sectors(self, objectname, mt_type=None):
"""
Helper method to fetch unique sectors for a moving target

Parameters
----------
objectname : str
The name or ID of the moving target.
mt_type : str, optional
The moving target type (majorbody or smallbody).

Returns
-------
sectors : list or None
Sorted list of unique sector numbers, or None if no sectors are available.
"""
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=NoResultsWarning)
sector_table = self.get_sectors(objectname=objectname, moving_target=True, mt_type=mt_type)

if len(sector_table) == 0:
warnings.warn("Coordinates are not in any TESS sector.", NoResultsWarning)
return None

return sorted(set(sector_table["sector"]))

@deprecated_renamed_argument('product', None, since='0.4.11', message='Tesscut no longer supports operations on '
'TESS Image Calibrator (TICA) products. '
'The `product` argument is deprecated and will be removed in a future version.')
Expand Down Expand Up @@ -272,6 +298,9 @@ def download_cutouts(self, *, coordinates=None, size=5, sector=None, product='SP
Optional.
The TESS sector to return the cutout from. If not supplied, cutouts
from all available sectors on which the coordinate appears will be returned.

NOTE: For moving targets, if sector is not specified, the method will automatically
fetch all available sectors and make individual requests per sector.
product : str
Deprecated. Default is 'SPOC'.
The product that the cutouts will be made out of. The only valid value for this parameter is 'SPOC', for the
Expand Down Expand Up @@ -314,6 +343,34 @@ def download_cutouts(self, *, coordinates=None, size=5, sector=None, product='SP
"""
self._validate_product(product)
self._validate_target_input(coordinates, objectname, moving_target)

# For moving targets without a sector specified, fetch sectors first and make
# individual requests per sector to reduce memory pressure on the service
if moving_target and sector is None:
localpath_table = Table(names=["Local Path"], dtype=[str])
unique_sectors = self._get_moving_target_sectors(objectname, mt_type)

if unique_sectors is None:
return localpath_table

# Make individual requests per sector and combine results
all_paths = []
for sect in unique_sectors:
manifest = self.download_cutouts(
size=size,
sector=sect,
path=path,
inflate=inflate,
objectname=objectname,
moving_target=True,
mt_type=mt_type,
verbose=verbose,
)
all_paths.extend(manifest["Local Path"])

localpath_table["Local Path"] = all_paths
return localpath_table

params = _parse_cutout_size(size)

if sector:
Expand Down Expand Up @@ -395,6 +452,9 @@ def get_cutouts(self, *, coordinates=None, size=5, product='SPOC', sector=None,
Optional.
The TESS sector to return the cutout from. If not supplied, cutouts
from all available sectors on which the coordinate appears will be returned.

NOTE: For moving targets, if sector is not specified, the method will automatically
fetch all available sectors and make individual requests per sector.
objectname : str, optional
The target around which to search, by name (objectname="M104")
or TIC ID (objectname="TIC 141914082"). If moving_target is True, input must be the name or ID
Expand Down Expand Up @@ -425,6 +485,23 @@ def get_cutouts(self, *, coordinates=None, size=5, product='SPOC', sector=None,
self._validate_product(product)
self._validate_target_input(coordinates, objectname, moving_target)

# For moving targets without a sector specified, fetch sectors first and make
# individual requests per sector to reduce memory pressure on the service
if moving_target and sector is None:
unique_sectors = self._get_moving_target_sectors(objectname, mt_type)

if unique_sectors is None:
return []

# Make individual requests per sector and combine results
all_cutouts = []
for sect in unique_sectors:
cutouts = self.get_cutouts(
size=size, sector=sect, objectname=objectname, moving_target=True, mt_type=mt_type
)
all_cutouts.extend(cutouts)
return all_cutouts

params = _parse_cutout_size(size)
if sector:
params["sector"] = sector
Expand Down
63 changes: 63 additions & 0 deletions astroquery/mast/tests/test_mast.py
Original file line number Diff line number Diff line change
Expand Up @@ -1321,6 +1321,69 @@ def test_tesscut_get_cutouts(patch_post, tmpdir):
assert "Input product must be SPOC." in str(invalid_query.value)


def test_tesscut_get_cutouts_mt_no_sector(patch_post):
"""Test get_cutouts with moving target but no sector specified.

When sector is not specified for moving targets, the method should
automatically fetch available sectors and make individual requests per sector.
"""
# Moving target without specifying sector - should automatically fetch sectors
cutout_hdus_list = mast.Tesscut.get_cutouts(objectname="Eleonora", moving_target=True, mt_type="small_body", size=5)
assert isinstance(cutout_hdus_list, list)
# Mock returns 1 sector, so we expect 1 cutout
assert len(cutout_hdus_list) == 1
assert isinstance(cutout_hdus_list[0], fits.HDUList)


def test_tesscut_download_cutouts_mt_no_sector(patch_post, tmpdir):
"""Test download_cutouts with moving target but no sector specified.

When sector is not specified for moving targets, the method should
automatically fetch available sectors and make individual requests per sector.
"""
# Moving target without specifying sector - should automatically fetch sectors
manifest = mast.Tesscut.download_cutouts(
objectname="Eleonora", moving_target=True, mt_type="small_body", size=5, path=str(tmpdir)
)
assert isinstance(manifest, Table)
# Mock returns 1 sector, so we expect 1 file
assert len(manifest) == 1
assert manifest["Local Path"][0][-4:] == "fits"
assert os.path.isfile(manifest[0]["Local Path"])


def test_tesscut_get_cutouts_mt_no_sector_empty_results(patch_post, monkeypatch):
"""Test get_cutouts with moving target when no sectors are available.

When get_sectors returns an empty table, the method should warn and return an empty list.
"""
# Mock get_sectors to return an empty Table
empty_sector_table = Table(names=["sectorName", "sector", "camera", "ccd"], dtype=[str, int, int, int])
monkeypatch.setattr(mast.Tesscut, "get_sectors", lambda *args, **kwargs: empty_sector_table)

with pytest.warns(NoResultsWarning, match="Coordinates are not in any TESS sector"):
cutout_hdus_list = mast.Tesscut.get_cutouts(objectname="NonExistentObject", moving_target=True, size=5)
assert isinstance(cutout_hdus_list, list)
assert len(cutout_hdus_list) == 0


def test_tesscut_download_cutouts_mt_no_sector_empty_results(patch_post, tmpdir, monkeypatch):
"""Test download_cutouts with moving target when no sectors are available.

When get_sectors returns an empty table, the method should warn and return an empty Table.
"""
# Mock get_sectors to return an empty Table
empty_sector_table = Table(names=["sectorName", "sector", "camera", "ccd"], dtype=[str, int, int, int])
monkeypatch.setattr(mast.Tesscut, "get_sectors", lambda *args, **kwargs: empty_sector_table)

with pytest.warns(NoResultsWarning, match="Coordinates are not in any TESS sector"):
manifest = mast.Tesscut.download_cutouts(
objectname="NonExistentObject", moving_target=True, size=5, path=str(tmpdir)
)
assert isinstance(manifest, Table)
assert len(manifest) == 0


######################
# ZcutClass tests #
######################
Expand Down
30 changes: 30 additions & 0 deletions astroquery/mast/tests/test_mast_remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -1458,6 +1458,36 @@ def test_tesscut_get_cutouts_mt(self):
Tesscut.get_cutouts(objectname=moving_target_name)
assert error_nameresolve in str(error_msg.value)

def test_tesscut_get_cutouts_mt_no_sector(self):
"""Test get_cutouts with moving target but no sector specified.

When sector is not specified for moving targets, the method should
automatically fetch available sectors and make individual requests per sector.
"""
moving_target_name = "Eleonora"
# Moving target without specifying sector - should automatically fetch sectors
cutout_hdus_list = Tesscut.get_cutouts(objectname=moving_target_name, moving_target=True, size=1)
assert isinstance(cutout_hdus_list, list)
# Should return cutouts for all available sectors
assert len(cutout_hdus_list) >= 1
assert isinstance(cutout_hdus_list[0], fits.HDUList)

def test_tesscut_download_cutouts_mt_no_sector(self, tmpdir):
"""Test download_cutouts with moving target but no sector specified.

When sector is not specified for moving targets, the method should
automatically fetch available sectors and make individual requests per sector.
"""
moving_target_name = "Eleonora"
# Moving target without specifying sector - should automatically fetch sectors
manifest = Tesscut.download_cutouts(objectname=moving_target_name, moving_target=True, size=1, path=str(tmpdir))
assert isinstance(manifest, Table)
# Should return files for all available sectors
assert len(manifest) >= 1
assert manifest["Local Path"][0][-4:] == "fits"
for row in manifest:
assert os.path.isfile(row["Local Path"])

###################
# ZcutClass tests #
###################
Expand Down
Loading