55import numpy as np
66
77from astropy .io import fits
8- from astropy .nddata import NDData
8+ from astropy .nddata import CCDData , NDData
99from astropy .table import Table , vstack
1010from astropy import units as u
1111from astropy .wcs import WCS
@@ -31,6 +31,7 @@ def wcs(self):
3131 w = WCS (naxis = 2 )
3232
3333 # Set up an "Airy's zenithal" projection
34+ # Note: WCS is 1-based, not 0-based
3435 w .wcs .crpix = [- 234.75 , 8.3393 ]
3536 w .wcs .cdelt = np .array ([- 0.066667 , 0.066667 ])
3637 w .wcs .crval = [0 , - 90 ]
@@ -113,12 +114,12 @@ def test_set_get_center_xy(self, data):
113114
114115 def test_set_get_center_world (self , data , wcs ):
115116 self .image .load_image (NDData (data = data , wcs = wcs ), image_label = 'test' )
116- self .image .set_viewport (center = SkyCoord (* wcs .crval , unit = 'deg' ), image_label = 'test' )
117+ self .image .set_viewport (center = SkyCoord (* wcs .wcs . crval , unit = 'deg' ), image_label = 'test' )
117118
118119 vport = self .image .get_viewport (image_label = 'test' )
119120 assert isinstance (vport ['center' ], SkyCoord )
120- assert vport ['center' ].ra .deg == pytest .approx (wcs .crval [0 ])
121- assert vport ['center' ].dec .deg == pytest .approx (wcs .crval [1 ])
121+ assert vport ['center' ].ra .deg == pytest .approx (wcs .wcs . crval [0 ])
122+ assert vport ['center' ].dec .deg == pytest .approx (wcs .wcs . crval [1 ])
122123
123124 def test_set_get_fov_pixel (self , data ):
124125 # Set data first, since that is needed to determine zoom level
@@ -136,10 +137,14 @@ def test_set_get_fov_world(self, data, wcs):
136137 # Set the FOV in world coordinates
137138 self .image .set_viewport (fov = 0.1 * u .deg , image_label = 'test' )
138139 vport = self .image .get_viewport (image_label = 'test' )
139- assert isinstance (vport ['fov' ], SkyCoord )
140- assert vport ['fov' ].deg == pytest .approx (0.1 )
140+ assert isinstance (vport ['fov' ], u .Quantity )
141+ assert len (np .atleast_1d (vport ['fov' ])) == 1
142+ assert vport ['fov' ].unit .physical_type == 'angle'
143+ fov_degree = vport ['fov' ].to (u .degree ).value
144+ assert fov_degree == pytest .approx (0.1 )
141145
142146 def test_set_get_viewport_errors (self , data , wcs ):
147+ # Test several of the expected errors that can be raised
143148 self .image .load_image (NDData (data = data , wcs = wcs ), image_label = 'test' )
144149
145150 # fov can be float or an angular Qunatity
@@ -166,25 +171,64 @@ def test_set_get_viewport_errors(self, data, wcs):
166171 # If there are multiple images loaded, the image_label must be provided
167172 self .image .load_image (data , image_label = 'another test' )
168173
169- with pytest .raises (ValueError , match = '[Ii]mage label.*not provided ' ):
174+ with pytest .raises (ValueError , match = 'Multiple catalog styles defined ' ):
170175 self .image .get_viewport ()
171176
172- def test_viewport_is_defined_aster_loading_image (self , data ):
177+ # setting sky_or_pixel to something other than 'sky' or 'pixel' or None
178+ # should raise an error
179+ with pytest .raises (ValueError , match = '[Ss]ky_or_pixel must be' ):
180+ self .image .get_viewport (sky_or_pixel = 'not a valid value' )
181+
182+ def test_set_get_viewport_errors_because_no_wcs (self , data ):
183+ # Check that errors are raised when they should be when calling
184+ # get_viewport when no WCS is present.
185+
186+ # Load the data without a WCS
187+ self .image .load_image (data , image_label = 'test' )
188+
189+ # Set the viewport with a SkyCoord center
190+ with pytest .raises (TypeError , match = 'Center must be a tuple' ):
191+ self .image .set_viewport (center = SkyCoord (ra = 10 , dec = 20 , unit = 'deg' ), image_label = 'test' )
192+
193+ # Set the viewport with a Quantity fov
194+ with pytest .raises (TypeError , match = 'FOV must be a float' ):
195+ self .image .set_viewport (fov = 100 * u .arcmin , image_label = 'test' )
196+
197+ # Try getting the viewport as sky
198+ with pytest .raises (ValueError , match = 'WCS is not set' ):
199+ self .image .get_viewport (image_label = 'test' , sky_or_pixel = 'sky' )
200+
201+ @pytest .mark .parametrize ("world" , [True , False ])
202+ def test_viewport_is_defined_after_loading_image (self , tmp_path , data , wcs , world ):
173203 # Check that the viewport is set to a default value when an image
174204 # is loaded, even if no viewport is explicitly set.
175- self .image .load_image (data )
205+
206+ # Load the image from FITS to ensure that at least one image with WCS
207+ # has been loaded from FITS.
208+ wcs = wcs if world else None
209+ ccd = CCDData (data = data , unit = "adu" , wcs = wcs )
210+
211+ ccd_path = tmp_path / 'test.fits'
212+ ccd .write (ccd_path )
213+ self .image .load_image (ccd_path )
176214
177215 # Getting the viewport should not fail...
178216 vport = self .image .get_viewport ()
179217
180218 assert 'center' in vport
181- # No world, so center should be a tuple
182- assert isinstance (vport ['center' ], tuple )
219+
183220 assert 'fov' in vport
184- # fov should be a float since no WCS
185- assert isinstance (vport ['fov' ], numbers .Real )
186221 assert 'image_label' in vport
187222 assert vport ['image_label' ] is None
223+ if world :
224+ assert isinstance (vport ['center' ], SkyCoord )
225+ # fov should be a Quantity since WCS is present
226+ assert isinstance (vport ['fov' ], u .Quantity )
227+ else :
228+ # No world, so center should be a tuple
229+ assert isinstance (vport ['center' ], tuple )
230+ # fov should be a float since no WCS
231+ assert isinstance (vport ['fov' ], numbers .Real )
188232
189233 def test_set_get_view_port_no_image_label (self , data ):
190234 # If there is only one image, the viewport should be able to be set
@@ -232,13 +276,15 @@ def test_get_viewport_sky_or_pixel(self, data, wcs):
232276 # Load the data with a WCS
233277 self .image .load_image (NDData (data = data , wcs = wcs ), image_label = 'test' )
234278
235- input_center = SkyCoord (* wcs .val , unit = 'deg' )
279+ input_center = SkyCoord (* wcs .wcs . crval , unit = 'deg' )
236280 input_fov = 2 * u .arcmin
237281 self .image .set_viewport (center = input_center , fov = input_fov , image_label = 'test' )
238282
239283 # Get the viewport in pixel coordinates
240284 vport_pixel = self .image .get_viewport (image_label = 'test' , sky_or_pixel = 'pixel' )
241- assert vport_pixel ['center' ] == wcs .crpix
285+ # The WCS set up for the tests is 1-based, rather than the usual 0-based,
286+ # so we need to subtract 1 from the pixel coordinates.
287+ assert all (vport_pixel ['center' ] == (wcs .wcs .crpix - 1 ))
242288 # tbh, not at all sure what the fov should be in pixel coordinates,
243289 # so just check that it is a float.
244290 assert isinstance (vport_pixel ['fov' ], numbers .Real )
@@ -266,7 +312,33 @@ def test_get_viewport_no_sky_or_pixel(self, data, wcs, sky_or_pixel):
266312 assert vport ['fov' ].unit .physical_type == "angle"
267313 case 'pixel' :
268314 assert isinstance (vport ['center' ], tuple )
269- assert isinstance (vport ['fov' ], float )
315+ assert isinstance (vport ['fov' ], numbers .Real )
316+
317+ def test_get_viewport_with_wcs_set_pixel_or_world (self , data , wcs ):
318+ # Check that the viewport can be retrieved in both pixel and world
319+ # after setting with the opposite if the WCS is set.
320+ # Load the data with a WCS
321+ self .image .load_image (NDData (data = data , wcs = wcs ), image_label = 'test' )
322+
323+ # Set the viewport in world coordinates
324+ input_center = SkyCoord (* wcs .wcs .crval , unit = 'deg' )
325+ input_fov = 2 * u .arcmin
326+ self .image .set_viewport (center = input_center , fov = input_fov , image_label = 'test' )
327+
328+ # Get the viewport in pixel coordinates
329+ vport_pixel = self .image .get_viewport (image_label = 'test' , sky_or_pixel = 'pixel' )
330+ assert all (vport_pixel ['center' ] == (wcs .wcs .crpix - 1 ))
331+ assert isinstance (vport_pixel ['fov' ], numbers .Real )
332+
333+ # Set the viewport in pixel coordinates
334+ input_center_pixel = (wcs .wcs .crpix [0 ], wcs .wcs .crpix [1 ])
335+ input_fov_pixel = 100 # in pixels
336+ self .image .set_viewport (center = input_center_pixel , fov = input_fov_pixel , image_label = 'test' )
337+
338+ # Get the viewport in world coordinates
339+ vport_world = self .image .get_viewport (image_label = 'test' , sky_or_pixel = 'sky' )
340+ assert vport_world ['center' ] == wcs .pixel_to_world (* input_center_pixel )
341+ assert isinstance (vport_world ['fov' ], u .Quantity )
270342
271343 def test_viewport_round_trips (self , data , wcs ):
272344 # Check that the viewport retrieved with get can be used to set
0 commit comments