@@ -65,6 +65,12 @@ class TessFootprintCutout(FootprintCutout):
6565 Write the cutouts as Target Pixel Files (TPFs) to the specified directory.
6666 """
6767
68+ # Mission-specific defaults
69+ ARCSEC_PER_PX = 21 # Number of arcseconds per pixel in a TESS image
70+ S3_FOOTPRINT_CACHE = 's3://stpubdata/tess/public/footprints/tess_ffi_footprint_cache.json'
71+ S3_BASE_FILE_PATH = 's3://stpubdata/tess/public/mast/'
72+
73+
6874 @deprecated_renamed_argument ('product' , None , since = '1.1.0' , message = 'Astrocut no longer supports cutouts from '
6975 'TESS Image Calibrator (TICA) products. '
7076 'The `product` argument is deprecated and will be removed in a future version.' )
@@ -78,101 +84,9 @@ def __init__(self, coordinates: Union[SkyCoord, str],
7884 if product .upper () != 'SPOC' :
7985 raise InvalidInputError ('Product for TESS cube cutouts must be "SPOC".' )
8086 self ._product = 'SPOC'
81- self ._arcsec_per_px = 21 # Number of arcseconds per pixel in a TESS image
82-
83- # Set S3 URIs to footprint cache file and base file path
84- self ._s3_footprint_cache = 's3://stpubdata/tess/public/footprints/tess_ffi_footprint_cache.json'
85- self ._s3_base_file_path = 's3://stpubdata/tess/public/mast/'
8687
8788 # Make the cutouts upon initialization
8889 self .cutout ()
89-
90- def _extract_sequence_information (self , sector_name : str ) -> dict :
91- """
92- Extract the sector, camera, and ccd information from the sector name.
93-
94- Parameters
95- ----------
96- sector_name : str
97- The name of the sector.
98-
99- Returns
100- -------
101- dict
102- A dictionary containing the sector name, sector number, camera number, and CCD number.
103- """
104- # Example sector name format: "tess-s0001-4-4"
105- pattern = re .compile (r"(tess-s)(?P<sector>\d{4})-(?P<camera>\d{1,4})-(?P<ccd>\d{1,4})" )
106- sector_match = re .match (pattern , sector_name )
107-
108- if not sector_match :
109- # Return an empty dictionary if the name does not match the product pattern
110- return {}
111-
112- # Extract the sector, camera, and ccd information
113- sector = sector_match .group ("sector" )
114- camera = sector_match .group ("camera" )
115- ccd = sector_match .group ("ccd" )
116-
117- return {"sectorName" : sector_name , "sector" : sector , "camera" : camera , "ccd" : ccd }
118-
119- def _create_sequence_list (self , observations : Table ) -> List [dict ]:
120- """
121- Extracts sequence information from a list of observations.
122-
123- Parameters
124- ----------
125- observations : `~astropy.table.Table`
126- A table of FFI observations.
127-
128- Returns
129- -------
130- list of dict
131- A list of dictionaries, each containing the sector name, sector number, camera number, and CCD number.
132- """
133- # Filter observations by target name to get only the FFI observations
134- obs_filtered = [obs for obs in observations if obs ["target_name" ].upper () == "TESS FFI" ]
135-
136- sequence_results = []
137- for row in obs_filtered :
138- # Extract the sector information for each FFI observation
139- sequence_extraction = self ._extract_sequence_information (row ["obs_id" ])
140- if sequence_extraction :
141- sequence_results .append (sequence_extraction )
142-
143- return sequence_results
144-
145- def _get_files_from_cone_results (self , cone_results : Table ) -> List [dict ]:
146- """
147- Converts a `~astropy.table.Table` of cone search results to a list of dictionaries containing
148- information for each cloud cube file that intersects with the cutout.
149-
150- Parameters
151- ----------
152- cone_results : `~astropy.table.Table`
153- A table containing observation results, including sector information.
154-
155- Returns
156- -------
157- cube_files : list of dict
158- A list of dictionaries, each containing:
159- - "folder": The folder name corresponding to the sector, prefixed with 's' and zero-padded to 4 digits.
160- - "cube": The expected filename for the cube FITS file in the format "{sectorName}-cube.fits".
161- - "sectorName": The sector name.
162- """
163- # Create a list of dictionaries containing the sector information
164- seq_list = self ._create_sequence_list (cone_results )
165-
166- # Create a list of dictionaries containing the cube file information
167- cube_files = [
168- {
169- "folder" : "s" + sector ["sector" ].rjust (4 , "0" ),
170- "cube" : sector ["sectorName" ] + "-cube.fits" ,
171- "sectorName" : sector ["sectorName" ],
172- }
173- for sector in seq_list
174- ]
175- return cube_files
17690
17791 def cutout (self ):
17892 """
@@ -185,7 +99,7 @@ def cutout(self):
18599 If the given coordinates are not found within the specified sequence(s).
186100 """
187101 # Get footprints from the cloud
188- all_ffis = get_ffis (self ._s3_footprint_cache )
102+ all_ffis = get_ffis (self .S3_FOOTPRINT_CACHE )
189103 log .debug ('Found %d footprint files.' , len (all_ffis ))
190104
191105 # Filter footprints by sequence
@@ -200,15 +114,15 @@ def cutout(self):
200114 ', ' .join (str (s ) for s in self ._sequence ))
201115
202116 # Get sequence names and files that contain the cutout
203- cone_results = ra_dec_crossmatch (all_ffis , self ._coordinates , self ._cutout_size , self ._arcsec_per_px )
117+ cone_results = ra_dec_crossmatch (all_ffis , self ._coordinates , self ._cutout_size , self .ARCSEC_PER_PX )
204118 if not cone_results :
205119 raise InvalidQueryError ('The given coordinates were not found within the specified sequence(s).' )
206- files_mapping = self . _get_files_from_cone_results (cone_results )
120+ files_mapping = _get_files_from_cone_results (cone_results )
207121 log .debug ('Found %d matching files.' , len (files_mapping ))
208122
209123 # Generate the cube cutouts
210124 log .debug ('Generating cutouts...' )
211- input_files = [f"{ self ._s3_base_file_path } { file ['cube' ]} " for file in files_mapping ]
125+ input_files = [f"{ self .S3_BASE_FILE_PATH } { file ['cube' ]} " for file in files_mapping ]
212126 tess_cube_cutout = TessCubeCutout (input_files , self ._coordinates , self ._cutout_size ,
213127 self ._fill_value , self ._limit_rounding_method , threads = 8 ,
214128 verbose = self ._verbose )
@@ -237,6 +151,102 @@ def write_as_tpf(self, output_dir: Union[str, Path] = '.') -> List[str]:
237151 return self .tess_cube_cutout .write_as_tpf (output_dir )
238152
239153
154+ def _extract_sequence_information (sector_name : str ) -> dict :
155+ """
156+ Extract the sector, camera, and ccd information from the sector name.
157+
158+ This is a helper function and should be left private.
159+
160+ Parameters
161+ ----------
162+ sector_name : str
163+ The name of the sector.
164+
165+ Returns
166+ -------
167+ dict
168+ A dictionary containing the sector name, sector number, camera number, and CCD number.
169+ """
170+ # Example sector name format: "tess-s0001-4-4"
171+ pattern = re .compile (r"(tess-s)(?P<sector>\d{4})-(?P<camera>\d{1,4})-(?P<ccd>\d{1,4})" )
172+ sector_match = re .match (pattern , sector_name )
173+
174+ if not sector_match :
175+ # Return an empty dictionary if the name does not match the product pattern
176+ return {}
177+
178+ # Extract the sector, camera, and ccd information
179+ sector = sector_match .group ("sector" )
180+ camera = sector_match .group ("camera" )
181+ ccd = sector_match .group ("ccd" )
182+
183+ return {"sectorName" : sector_name , "sector" : sector , "camera" : camera , "ccd" : ccd }
184+
185+
186+ def _create_sequence_list (observations : Table ) -> List [dict ]:
187+ """
188+ Extracts sequence information from a list of observations.
189+
190+ This is a helper function and should be left private.
191+
192+ Parameters
193+ ----------
194+ observations : `~astropy.table.Table`
195+ A table of FFI observations.
196+
197+ Returns
198+ -------
199+ list of dict
200+ A list of dictionaries, each containing the sector name, sector number, camera number, and CCD number.
201+ """
202+ # Filter observations by target name to get only the FFI observations
203+ obs_filtered = [obs for obs in observations if obs ["target_name" ].upper () == "TESS FFI" ]
204+
205+ sequence_results = []
206+ for row in obs_filtered :
207+ # Extract the sector information for each FFI observation
208+ sequence_extraction = _extract_sequence_information (row ["obs_id" ])
209+ if sequence_extraction :
210+ sequence_results .append (sequence_extraction )
211+
212+ return sequence_results
213+
214+
215+ def _get_files_from_cone_results (cone_results : Table ) -> List [dict ]:
216+ """
217+ Converts a `~astropy.table.Table` of cone search results to a list of dictionaries containing
218+ information for each cloud cube file that intersects with the cutout.
219+
220+ This is a helper function and should be left private.
221+
222+ Parameters
223+ ----------
224+ cone_results : `~astropy.table.Table`
225+ A table containing observation results, including sector information.
226+
227+ Returns
228+ -------
229+ cube_files : list of dict
230+ A list of dictionaries, each containing:
231+ - "folder": The folder name corresponding to the sector, prefixed with 's' and zero-padded to 4 digits.
232+ - "cube": The expected filename for the cube FITS file in the format "{sectorName}-cube.fits".
233+ - "sectorName": The sector name.
234+ """
235+ # Create a list of dictionaries containing the sector information
236+ seq_list = _create_sequence_list (cone_results )
237+
238+ # Create a list of dictionaries containing the cube file information
239+ cube_files = [
240+ {
241+ "folder" : "s" + sector ["sector" ].rjust (4 , "0" ),
242+ "cube" : sector ["sectorName" ] + "-cube.fits" ,
243+ "sectorName" : sector ["sectorName" ],
244+ }
245+ for sector in seq_list
246+ ]
247+ return cube_files
248+
249+
240250@deprecated_renamed_argument ('product' , None , since = '1.1.0' , message = 'Astrocut no longer supports cutouts from '
241251 'TESS Image Calibrator (TICA) products. '
242252 'The `product` argument is deprecated and will be removed in a future version.' )
@@ -295,11 +305,62 @@ def cube_cut_from_footprint(coordinates: Union[str, SkyCoord], cutout_size,
295305 ['./cutouts/tess-s0001-4-4/tess-s0001-4-4_83.406310_-62.489771_64x64_astrocut.fits',
296306 './cutouts/tess-s0002-4-1/tess-s0002-4-1_83.406310_-62.489771_64x64_astrocut.fits']
297307 """
298-
308+ # Create the TessFootprintCutout object
299309 cutouts = TessFootprintCutout (coordinates , cutout_size , sequence = sequence , product = product , verbose = verbose )
300310
311+ # Return cutouts as memory objects
301312 if memory_only :
302313 return cutouts .tpf_cutouts
303314
304315 # Write cutouts
305316 return cutouts .write_as_tpf (output_dir )
317+
318+
319+ def get_tess_sectors (coordinates : Union [str , SkyCoord ],
320+ cutout_size : Union [int , u .Quantity , List [int ], Tuple [int ]]) -> Table :
321+ """
322+ Return the TESS sectors (sequence, camera, CCD) whose FFI footprints overlap
323+ the given cutout defined by position and size.
324+
325+ Parameters
326+ ----------
327+ coordinates : str or `astropy.coordinates.SkyCoord` object
328+ The position around which to cutout. It may be specified as a string ("ra dec" in degrees)
329+ or as the appropriate `~astropy.coordinates.SkyCoord` object.
330+ cutout_size : int, array-like, or `~astropy.units.Quantity`
331+ The size of the cutout array. If ``cutout_size``
332+ is a scalar number or a scalar `~astropy.units.Quantity`,
333+ then a square cutout of ``cutout_size`` will be used. If
334+ ``cutout_size`` has two elements, they should be in ``(ny, nx)``
335+ order. Scalar numbers in ``cutout_size`` are assumed to be in
336+ units of pixels. `~astropy.units.Quantity` objects must be in pixel or
337+ angular units.
338+
339+ If a cutout size of zero is provided, the function will return sectors that contain
340+ the exact RA and Dec position. If a non-zero cutout size is provided, the function
341+ will return sectors whose footprints overlap with the cutout area.
342+
343+ Returns
344+ -------
345+ `~astropy.table.Table`
346+ A table containing the sector name, sector number, camera number, and CCD number
347+ for each sector that contains the specified coordinates within the cutout size.
348+ """
349+ column_names = ['sectorName' , 'sector' , 'camera' , 'ccd' ]
350+ column_dtypes = ['S20' , 'i4' , 'i4' , 'i4' ]
351+
352+ # Get footprints from the cloud
353+ ffis = get_ffis (TessFootprintCutout .S3_FOOTPRINT_CACHE )
354+
355+ # Crossmatch to find matching FFIs
356+ matched_ffis = ra_dec_crossmatch (ffis , coordinates , cutout_size , TessFootprintCutout .ARCSEC_PER_PX )
357+ if len (matched_ffis ) == 0 : # Return empty table if no matches
358+ return Table (names = column_names , dtype = column_dtypes )
359+
360+ # Create a list of unique sector entries
361+ sector_list = _create_sequence_list (matched_ffis )
362+
363+ return Table (rows = [
364+ (entry ['sectorName' ], int (entry ['sector' ]), int (entry ['camera' ]), int (entry ['ccd' ]))
365+ for entry in sector_list
366+ ], names = column_names , dtype = column_dtypes )
0 commit comments