1010from astropy .table import Table , Column
1111from cachetools import TTLCache , cached
1212from spherical_geometry .polygon import SphericalPolygon
13+ from spherical_geometry .vector import radec_to_vector
1314
1415from .cutout import Cutout
1516
@@ -193,7 +194,91 @@ def get_ffis(s3_footprint_cache: str) -> Table:
193194 return ffis
194195
195196
196- def ra_dec_crossmatch (all_ffis : Table , coordinates : SkyCoord , cutout_size , arcsec_per_px : int = 21 ) -> Table :
197+ def _crossmatch_point (ra : SkyCoord , dec : SkyCoord , all_ffis : Table ) -> np .ndarray :
198+ """
199+ Returns the indices of the Full Frame Images (FFIs) that contain the given RA and
200+ Dec coordinates by checking which FFI polygons contain the point.
201+
202+ Parameters
203+ ----------
204+ ra : SkyCoord
205+ Right Ascension in degrees.
206+ dec : SkyCoord
207+ Declination in degrees.
208+ all_ffis : `~astropy.table.Table`
209+ Table of FFIs to crossmatch with the point.
210+
211+ Returns
212+ -------
213+ ffi_inds : `~numpy.ndarray`
214+ Indices of FFIs that contain the given RA and Dec coordinates.
215+ """
216+ ffi_inds = []
217+ vector_coord = radec_to_vector (ra , dec )
218+ for sector in np .unique (all_ffis ['sequence_number' ]):
219+ # Returns a 2-long array where the first element is indexes and the 2nd element is empty
220+ sector_ffi_inds = np .where (all_ffis ['sequence_number' ] == sector )[0 ]
221+
222+ for ind in sector_ffi_inds :
223+ if all_ffis [ind ]["polygon" ].contains_point (vector_coord ):
224+ ffi_inds .append (ind )
225+ break # the ra/dec will only be on one ccd per sector
226+ return np .array (ffi_inds , dtype = int )
227+
228+
229+ def _crossmatch_polygon (ra : SkyCoord , dec : SkyCoord , all_ffis : Table , px_size : np .ndarray ,
230+ arcsec_per_px : int = 21 ) -> np .ndarray :
231+ """
232+ Returns the indices of the Full Frame Images (FFIs) that intersect with the given cutout footprint
233+ by checking which FFI polygons intersect with the cutout polygon.
234+
235+ Parameters
236+ ----------
237+ ra : SkyCoord
238+ Right Ascension in degrees.
239+ dec : SkyCoord
240+ Declination in degrees.
241+ all_ffis : `~astropy.table.Table`
242+ Table of FFIs to crossmatch with the point.
243+ px_size : array-like
244+ Size of the cutout in pixels, in the form [ny, nx].
245+ arcsec_per_px : int, optional
246+ Default 21. The number of arcseconds per pixel in an image. Used to determine
247+ the footprint of the cutout. Default is the number of arcseconds per pixel in
248+ a TESS image.
249+
250+ Returns
251+ -------
252+ ffi_inds : `~numpy.ndarray`
253+ Boolean array indicating whether each FFI intersects with the cutout.
254+ """
255+ # Create polygon for intersection
256+ # Convert dimensions from pixels to arcseconds and divide by 2 to get offset from center
257+ # If one of the dimensions is 0, use a very small value to avoid issues with SphericalPolygon
258+ min_offset = 0.1 # pixels
259+ ra_offset = ((max (px_size [0 ], min_offset ) * arcsec_per_px ) / 2 ) * u .arcsec
260+ dec_offset = ((max (px_size [1 ], min_offset ) * arcsec_per_px ) / 2 ) * u .arcsec
261+
262+ # Calculate RA and Dec boundaries
263+ ra_bounds = [ra - ra_offset , ra + ra_offset ]
264+ dec_bounds = [dec - dec_offset , dec + dec_offset ]
265+
266+ # Get RA and Dec for four corners of rectangle
267+ ras = [ra_bounds [0 ].value , ra_bounds [1 ].value , ra_bounds [1 ].value , ra_bounds [0 ].value ]
268+ decs = [dec_bounds [0 ].value , dec_bounds [0 ].value , dec_bounds [1 ].value , dec_bounds [1 ].value ]
269+
270+ # Create SphericalPolygon for comparison
271+ cutout_fp = SphericalPolygon .from_radec (ras , decs , center = (ra , dec ))
272+
273+ # Find indices of FFIs that intersect with the cutout
274+ ffi_inds = np .vectorize (lambda ffi : ffi .intersects_poly (cutout_fp ))(all_ffis ['polygon' ])
275+ ffi_inds = FootprintCutout ._ffi_intersect (all_ffis , cutout_fp )
276+
277+ return ffi_inds
278+
279+
280+ def ra_dec_crossmatch (all_ffis : Table , coordinates : Union [SkyCoord , str ], cutout_size ,
281+ arcsec_per_px : int = 21 ) -> Table :
197282 """
198283 Returns the Full Frame Images (FFIs) whose footprints overlap with a cutout of a given position and size.
199284
@@ -208,11 +293,15 @@ def ra_dec_crossmatch(all_ffis: Table, coordinates: SkyCoord, cutout_size, arcse
208293 cutout_size : int, array-like, `~astropy.units.Quantity`
209294 The size of the cutout array. If ``cutout_size``
210295 is a scalar number or a scalar `~astropy.units.Quantity`,
211- then a square cutout of ``cutout_size`` will be created . If
296+ then a square cutout of ``cutout_size`` will be used . If
212297 ``cutout_size`` has two elements, they should be in ``(ny, nx)``
213298 order. Scalar numbers in ``cutout_size`` are assumed to be in
214299 units of pixels. `~astropy.units.Quantity` objects must be in pixel or
215300 angular units.
301+
302+ If a cutout size of zero is provided, the function will return FFIs that contain
303+ the exact RA and Dec position. If a non-zero cutout size is provided, the function
304+ will return FFIs whose footprints overlap with the cutout area.
216305 arcsec_per_px : int, optional
217306 Default 21. The number of arcseconds per pixel in an image. Used to determine
218307 the footprint of the cutout. Default is the number of arcseconds per pixel in
@@ -228,27 +317,22 @@ def ra_dec_crossmatch(all_ffis: Table, coordinates: SkyCoord, cutout_size, arcse
228317 coordinates = SkyCoord (coordinates , unit = 'deg' )
229318 ra , dec = coordinates .ra , coordinates .dec
230319
231- # Parse cutout size
232- cutout_size = Cutout ._parse_size_input (cutout_size )
233-
234- # Create polygon for intersection
235- # Convert dimensions from pixels to arcseconds and divide by 2 to get offset from center
236- ra_offset = ((cutout_size [0 ] * arcsec_per_px ) / 2 ) * u .arcsec
237- dec_offset = ((cutout_size [1 ] * arcsec_per_px ) / 2 ) * u .arcsec
238-
239- # Calculate RA and Dec boundaries
240- ra_bounds = [ra - ra_offset , ra + ra_offset ]
241- dec_bounds = [dec - dec_offset , dec + dec_offset ]
242-
243- # Get RA and Dec for four corners of rectangle
244- ras = [ra_bounds [0 ].value , ra_bounds [1 ].value , ra_bounds [1 ].value , ra_bounds [0 ].value ]
245- decs = [dec_bounds [0 ].value , dec_bounds [0 ].value , dec_bounds [1 ].value , dec_bounds [1 ].value ]
246-
247- # Create SphericalPolygon for comparison
248- cutout_fp = SphericalPolygon .from_radec (ras , decs , center = (ra , dec ))
249-
250- # Find indices of FFIs that intersect with the cutout
251- ffi_inds = np .vectorize (lambda ffi : ffi .intersects_poly (cutout_fp ))(all_ffis ['polygon' ])
252- ffi_inds = FootprintCutout ._ffi_intersect (all_ffis , cutout_fp )
320+ px_size = np .zeros (2 , dtype = object )
321+ for axis , size in enumerate (Cutout .parse_size_input (cutout_size , allow_zero = True )):
322+ if isinstance (size , u .Quantity ): # If Quantity, convert to pixels
323+ if size .unit == u .pixel :
324+ px_size [axis ] = size .value
325+ else : # Angular size
326+ # Convert angular size to pixels
327+ px_size [axis ] = (size .to_value (u .arcsec )) / arcsec_per_px
328+ else : # Assume pixels
329+ px_size [axis ] = size
330+
331+ if np .all (px_size == 0 ):
332+ # Cross match with point
333+ ffi_inds = _crossmatch_point (ra , dec , all_ffis )
334+ else :
335+ # Cross match with polygon
336+ ffi_inds = _crossmatch_polygon (ra , dec , all_ffis , px_size , arcsec_per_px )
253337
254338 return all_ffis [ffi_inds ]
0 commit comments