55
66from astropy .coordinates import SkyCoord # noqa: E402
77from astropy .io import fits # noqa: E402
8- from astropy .nddata import NDData # noqa: E402
8+ from astropy .nddata import CCDData , NDData # noqa: E402
99from astropy .table import Table , vstack # noqa: E402
1010from astropy import units as u # noqa: E402
1111from astropy .wcs import WCS # noqa: E402
@@ -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 ]
@@ -101,12 +102,12 @@ def test_set_get_center_xy(self, data):
101102
102103 def test_set_get_center_world (self , data , wcs ):
103104 self .image .load_image (NDData (data = data , wcs = wcs ), image_label = 'test' )
104- self .image .set_viewport (center = SkyCoord (* wcs .crval , unit = 'deg' ), image_label = 'test' )
105+ self .image .set_viewport (center = SkyCoord (* wcs .wcs . crval , unit = 'deg' ), image_label = 'test' )
105106
106107 vport = self .image .get_viewport (image_label = 'test' )
107108 assert isinstance (vport ['center' ], SkyCoord )
108- assert vport ['center' ].ra .deg == pytest .approx (wcs .crval [0 ])
109- assert vport ['center' ].dec .deg == pytest .approx (wcs .crval [1 ])
109+ assert vport ['center' ].ra .deg == pytest .approx (wcs .wcs . crval [0 ])
110+ assert vport ['center' ].dec .deg == pytest .approx (wcs .wcs . crval [1 ])
110111
111112 def test_set_get_fov_pixel (self , data ):
112113 # Set data first, since that is needed to determine zoom level
@@ -124,10 +125,14 @@ def test_set_get_fov_world(self, data, wcs):
124125 # Set the FOV in world coordinates
125126 self .image .set_viewport (fov = 0.1 * u .deg , image_label = 'test' )
126127 vport = self .image .get_viewport (image_label = 'test' )
127- assert isinstance (vport ['fov' ], SkyCoord )
128- assert vport ['fov' ].deg == pytest .approx (0.1 )
128+ assert isinstance (vport ['fov' ], u .Quantity )
129+ assert len (np .atleast_1d (vport ['fov' ])) == 1
130+ assert vport ['fov' ].unit .physical_type == 'angle'
131+ fov_degree = vport ['fov' ].to (u .degree ).value
132+ assert fov_degree == pytest .approx (0.1 )
129133
130134 def test_set_get_viewport_errors (self , data , wcs ):
135+ # Test several of the expected errors that can be raised
131136 self .image .load_image (NDData (data = data , wcs = wcs ), image_label = 'test' )
132137
133138 # fov can be float or an angular Qunatity
@@ -154,25 +159,64 @@ def test_set_get_viewport_errors(self, data, wcs):
154159 # If there are multiple images loaded, the image_label must be provided
155160 self .image .load_image (data , image_label = 'another test' )
156161
157- with pytest .raises (ValueError , match = '[Ii]mage label.*not provided ' ):
162+ with pytest .raises (ValueError , match = 'Multiple catalog styles defined ' ):
158163 self .image .get_viewport ()
159164
160- def test_viewport_is_defined_aster_loading_image (self , data ):
165+ # setting sky_or_pixel to something other than 'sky' or 'pixel' or None
166+ # should raise an error
167+ with pytest .raises (ValueError , match = '[Ss]ky_or_pixel must be' ):
168+ self .image .get_viewport (sky_or_pixel = 'not a valid value' )
169+
170+ def test_set_get_viewport_errors_because_no_wcs (self , data ):
171+ # Check that errors are raised when they should be when calling
172+ # get_viewport when no WCS is present.
173+
174+ # Load the data without a WCS
175+ self .image .load_image (data , image_label = 'test' )
176+
177+ # Set the viewport with a SkyCoord center
178+ with pytest .raises (TypeError , match = 'Center must be a tuple' ):
179+ self .image .set_viewport (center = SkyCoord (ra = 10 , dec = 20 , unit = 'deg' ), image_label = 'test' )
180+
181+ # Set the viewport with a Quantity fov
182+ with pytest .raises (TypeError , match = 'FOV must be a float' ):
183+ self .image .set_viewport (fov = 100 * u .arcmin , image_label = 'test' )
184+
185+ # Try getting the viewport as sky
186+ with pytest .raises (ValueError , match = 'WCS is not set' ):
187+ self .image .get_viewport (image_label = 'test' , sky_or_pixel = 'sky' )
188+
189+ @pytest .mark .parametrize ("world" , [True , False ])
190+ def test_viewport_is_defined_after_loading_image (self , tmp_path , data , wcs , world ):
161191 # Check that the viewport is set to a default value when an image
162192 # is loaded, even if no viewport is explicitly set.
163- self .image .load_image (data )
193+
194+ # Load the image from FITS to ensure that at least one image with WCS
195+ # has been loaded from FITS.
196+ wcs = wcs if world else None
197+ ccd = CCDData (data = data , unit = "adu" , wcs = wcs )
198+
199+ ccd_path = tmp_path / 'test.fits'
200+ ccd .write (ccd_path )
201+ self .image .load_image (ccd_path )
164202
165203 # Getting the viewport should not fail...
166204 vport = self .image .get_viewport ()
167205
168206 assert 'center' in vport
169- # No world, so center should be a tuple
170- assert isinstance (vport ['center' ], tuple )
207+
171208 assert 'fov' in vport
172- # fov should be a float since no WCS
173- assert isinstance (vport ['fov' ], numbers .Real )
174209 assert 'image_label' in vport
175210 assert vport ['image_label' ] is None
211+ if world :
212+ assert isinstance (vport ['center' ], SkyCoord )
213+ # fov should be a Quantity since WCS is present
214+ assert isinstance (vport ['fov' ], u .Quantity )
215+ else :
216+ # No world, so center should be a tuple
217+ assert isinstance (vport ['center' ], tuple )
218+ # fov should be a float since no WCS
219+ assert isinstance (vport ['fov' ], numbers .Real )
176220
177221 def test_set_get_view_port_no_image_label (self , data ):
178222 # If there is only one image, the viewport should be able to be set
@@ -220,13 +264,15 @@ def test_get_viewport_sky_or_pixel(self, data, wcs):
220264 # Load the data with a WCS
221265 self .image .load_image (NDData (data = data , wcs = wcs ), image_label = 'test' )
222266
223- input_center = SkyCoord (* wcs .val , unit = 'deg' )
267+ input_center = SkyCoord (* wcs .wcs . crval , unit = 'deg' )
224268 input_fov = 2 * u .arcmin
225269 self .image .set_viewport (center = input_center , fov = input_fov , image_label = 'test' )
226270
227271 # Get the viewport in pixel coordinates
228272 vport_pixel = self .image .get_viewport (image_label = 'test' , sky_or_pixel = 'pixel' )
229- assert vport_pixel ['center' ] == wcs .crpix
273+ # The WCS set up for the tests is 1-based, rather than the usual 0-based,
274+ # so we need to subtract 1 from the pixel coordinates.
275+ assert all (vport_pixel ['center' ] == (wcs .wcs .crpix - 1 ))
230276 # tbh, not at all sure what the fov should be in pixel coordinates,
231277 # so just check that it is a float.
232278 assert isinstance (vport_pixel ['fov' ], numbers .Real )
@@ -254,7 +300,33 @@ def test_get_viewport_no_sky_or_pixel(self, data, wcs, sky_or_pixel):
254300 assert vport ['fov' ].unit .physical_type == "angle"
255301 case 'pixel' :
256302 assert isinstance (vport ['center' ], tuple )
257- assert isinstance (vport ['fov' ], float )
303+ assert isinstance (vport ['fov' ], numbers .Real )
304+
305+ def test_get_viewport_with_wcs_set_pixel_or_world (self , data , wcs ):
306+ # Check that the viewport can be retrieved in both pixel and world
307+ # after setting with the opposite if the WCS is set.
308+ # Load the data with a WCS
309+ self .image .load_image (NDData (data = data , wcs = wcs ), image_label = 'test' )
310+
311+ # Set the viewport in world coordinates
312+ input_center = SkyCoord (* wcs .wcs .crval , unit = 'deg' )
313+ input_fov = 2 * u .arcmin
314+ self .image .set_viewport (center = input_center , fov = input_fov , image_label = 'test' )
315+
316+ # Get the viewport in pixel coordinates
317+ vport_pixel = self .image .get_viewport (image_label = 'test' , sky_or_pixel = 'pixel' )
318+ assert all (vport_pixel ['center' ] == (wcs .wcs .crpix - 1 ))
319+ assert isinstance (vport_pixel ['fov' ], numbers .Real )
320+
321+ # Set the viewport in pixel coordinates
322+ input_center_pixel = (wcs .wcs .crpix [0 ], wcs .wcs .crpix [1 ])
323+ input_fov_pixel = 100 # in pixels
324+ self .image .set_viewport (center = input_center_pixel , fov = input_fov_pixel , image_label = 'test' )
325+
326+ # Get the viewport in world coordinates
327+ vport_world = self .image .get_viewport (image_label = 'test' , sky_or_pixel = 'sky' )
328+ assert vport_world ['center' ] == wcs .pixel_to_world (* input_center_pixel )
329+ assert isinstance (vport_world ['fov' ], u .Quantity )
258330
259331 def test_viewport_round_trips (self , data , wcs ):
260332 # Check that the viewport retrieved with get can be used to set
0 commit comments