From 0328ba97c07227055a8ad91efee9bc8c7d79170f Mon Sep 17 00:00:00 2001 From: Sam Bianco Date: Mon, 22 Sep 2025 17:23:58 -0400 Subject: [PATCH 1/3] case insensitive filters --- astroquery/mast/missions.py | 7 +----- astroquery/mast/observations.py | 7 +----- astroquery/mast/tests/test_mast.py | 8 ++++--- astroquery/mast/utils.py | 36 +++++++++++++++++++++++++++++- 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/astroquery/mast/missions.py b/astroquery/mast/missions.py index 82ed5c3218..ba87307d30 100644 --- a/astroquery/mast/missions.py +++ b/astroquery/mast/missions.py @@ -517,12 +517,7 @@ def filter_products(self, products, *, extension=None, **filters): # Filter by file extension, if provided if extension: - extensions = [extension] if isinstance(extension, str) else extension - ext_mask = np.array( - [not isinstance(x, np.ma.core.MaskedConstant) and any(x.endswith(ext) for ext in extensions) - for x in products["filename"]], - dtype=bool - ) + ext_mask = utils.apply_extension_filter(products, extension, 'filename') filter_mask &= ext_mask # Apply column-based filters diff --git a/astroquery/mast/observations.py b/astroquery/mast/observations.py index 0c73a5c124..320af9ed78 100644 --- a/astroquery/mast/observations.py +++ b/astroquery/mast/observations.py @@ -594,12 +594,7 @@ def filter_products(self, products, *, mrp_only=False, extension=None, **filters # Filter by file extension, if provided if extension: - extensions = [extension] if isinstance(extension, str) else extension - ext_mask = np.array( - [not isinstance(x, np.ma.core.MaskedConstant) and any(x.endswith(ext) for ext in extensions) - for x in products["productFilename"]], - dtype=bool - ) + ext_mask = utils.apply_extension_filter(products, extension, 'productFilename') filter_mask &= ext_mask # Apply column-based filters diff --git a/astroquery/mast/tests/test_mast.py b/astroquery/mast/tests/test_mast.py index e57ada5e28..9d90da325e 100644 --- a/astroquery/mast/tests/test_mast.py +++ b/astroquery/mast/tests/test_mast.py @@ -428,7 +428,7 @@ def test_missions_filter_products(patch_post): assert all(~((filtered['size'] >= 14400) & (filtered['size'] <= 17280))) # Negate a string match - filtered = mast.MastMissions.filter_products(products, category='!CALIBRATED') + filtered = mast.MastMissions.filter_products(products, category='!calibrated') assert all(filtered['category'] != 'CALIBRATED') # Negate one string in a list @@ -802,7 +802,7 @@ def test_observations_get_product_list(patch_post): def test_observations_filter_products(patch_post): products = mast.Observations.get_product_list('2003738726') filtered = mast.Observations.filter_products(products, - productType=["SCIENCE"], + productType=["sCiEnCE"], mrp_only=False) assert isinstance(filtered, Table) assert len(filtered) == 7 @@ -812,8 +812,10 @@ def test_observations_filter_products(patch_post): assert all(filtered['productGroupDescription'] == 'Minimum Recommended Products') # Filter by extension - filtered = mast.Observations.filter_products(products, extension='fits') + filtered = mast.Observations.filter_products(products, extension='FITS') assert len(filtered) > 0 + filtered = mast.Observations.filter_products(products, extension=['png']) + assert len(filtered) == 0 # Numeric filtering filtered = mast.Observations.filter_products(products, size='<50000') diff --git a/astroquery/mast/utils.py b/astroquery/mast/utils.py index 5a18adce46..0b4d666192 100644 --- a/astroquery/mast/utils.py +++ b/astroquery/mast/utils.py @@ -456,6 +456,38 @@ def remove_duplicate_products(data_products, uri_key): return unique_products +def apply_extension_filter(products, extension, filename_key): + """ + Applies an extension filter to a product table. + + Parameters + ---------- + products : `~astropy.table.Table` + The product table to filter. + extension : str + The extension to filter by (e.g., 'fits', 'csv'). + filename_key : str + The column name representing the filename of a product. + + Returns + ------- + ext_mask : `numpy.ndarray` + A boolean mask indicating which rows of the product table have the specified extension. + """ + # Normalize extensions to lowercase + extensions = [extension] if isinstance(extension, str) else extension + extensions = tuple(ext.lower() for ext in extensions) + + # Build mask + ext_mask = np.array( + [not isinstance(x, np.ma.core.MaskedConstant) + and str(x).lower().endswith(extensions) + for x in products[filename_key]], + dtype=bool + ) + return ext_mask + + def _combine_positive_negative_masks(mask_funcs): """ Combines a list of mask functions into a single mask according to: @@ -582,7 +614,9 @@ def apply_column_filters(products, filters): v = val[1:] if is_negated else val def func(col, v=v): - return np.isin(col, [v]) + # Normalize both column values and filter to lowercase strings for case-insensitive comparison + col_lower = np.char.lower(col.astype(str)) + return np.isin(col_lower, [v.lower()]) mask_funcs.append((func, is_negated)) this_mask = _combine_positive_negative_masks(mask_funcs)(col_data) From 5248feea1ba7de3d5a0c6e816c6095cf36ecac30 Mon Sep 17 00:00:00 2001 From: Sam Bianco Date: Tue, 23 Sep 2025 13:05:54 -0400 Subject: [PATCH 2/3] Changelog fix docs build --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 5385d32b6e..6b7a418315 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -25,6 +25,8 @@ mast - Raise an error if non-string values are passed to ``utils.resolve_object``. [#3435] +- Filtering by file extension or by a string column is now case-insensitive in ``MastMissions.filter_products`` + and ``Observations.filter_products``. [#3427] Infrastructure, Utility and Other Changes and Additions From b4ccef0134ccee7d8caab8ccd4edd74ed8db2bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brigitta=20Sip=C5=91cz?= Date: Fri, 17 Oct 2025 16:49:25 -0700 Subject: [PATCH 3/3] DOC: fixing failing doctest --- docs/mast/mast_cut.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/mast/mast_cut.rst b/docs/mast/mast_cut.rst index 27d085664a..eb26ac1bd2 100644 --- a/docs/mast/mast_cut.rst +++ b/docs/mast/mast_cut.rst @@ -12,9 +12,9 @@ Processing Operation's Center (`SPOC `__) full frame images -was discontinued. Individual TICA full frame images remain available from the +As of August 2025, the option to create cutouts using TESS Image CAlibration +(`TICA `__) full frame images +was discontinued. Individual TICA full frame images remain available from the `MAST TICA homepage `__. Cutouts using SPOC data remain available through TESSCut. **Note:** TESScut limits each user to no more than 10 simultaneous calls to the service. @@ -97,7 +97,7 @@ simply with either the objectname or coordinates. 2 APERTURE 1 ImageHDU 97 (2136, 2078) int32 -The `~astroquery.mast.TesscutClass.download_cutouts` function takes a coordinate, cutout size +The `~astroquery.mast.TesscutClass.download_cutouts` function takes a coordinate, cutout size (in pixels or an angular quantity), or object name (e.g. "M104" or "TIC 32449963") and moving target (True or False). It uses these parameters to download the cutout target pixel file(s). @@ -170,6 +170,7 @@ The following example requests SPOC cutouts for a moving target. tess-s0043-3-3 43 3 3 tess-s0044-2-4 44 2 4 tess-s0092-4-3 92 4 3 + tess-s0097-1-4 97 1 4 Zcut @@ -237,7 +238,7 @@ If a given coordinate appears in more than one Zcut survey, a cutout will be pro Downloading URL https://mast.stsci.edu/zcut/api/v0.1/astrocut?ra=189.49206&dec=62.20615&y=200&x=300&units=px&format=jpg to ./zcut_20201202132453.zip ... [Done] ... >>> print(manifest) - Local Path + Local Path ------------------------------------------------------------------------------------------------------- ./hlsp_3dhst_spitzer_irac_goods-n_irac1_v4.0_sc_189.492060_62.206150_10.0pix-x-5.0pix_astrocut_0.jpg ./hlsp_3dhst_spitzer_irac_goods-n-s2_irac3_v4.0_sc_189.492060_62.206150_10.0pix-x-5.0pix_astrocut_0.jpg