44import zipfile
55import io
66import importlib .util
7+ from unittest .mock import MagicMock , patch
78
89import asdf
910from astropy import coordinates as coord
1718from PIL import Image
1819
1920from astrocut .asdf_cutout import ASDFCutout , asdf_cut , get_center_pixel
20- from astrocut .exceptions import DataWarning , InvalidInputError , InvalidQueryError
21+ from astrocut .exceptions import DataWarning , InvalidInputError , InvalidQueryError , ModuleWarning
2122
2223
2324def make_wcs (xsize , ysize , ra = 30. , dec = 45. ):
@@ -159,20 +160,22 @@ def test_asdf_cutout(test_images, center_coord, cutout_size):
159160
160161
161162def test_asdf_cutout_write_to_file (test_images , center_coord , cutout_size , tmpdir ):
162- def check_asdf_metadata (af , original_file , cutout_data ):
163+ def check_asdf_metadata (af , original_file , cutout_data , meta_only = False ):
163164 """Check that ASDF file contains correct metadata"""
164165 assert 'roman' in af
165166 assert 'meta' in af ['roman' ]
166- # Check cutout data and metadata
167- for key in ['data' , 'dq' , 'err' , 'context' ]:
168- assert key in af ['roman' ]
169- assert np .all (af ['roman' ][key ] == cutout_data )
170167 meta = af ['roman' ]['meta' ]
171168 assert meta ['wcs' ].pixel_shape == (10 , 10 )
172169 assert meta ['product_type' ] == 'l2'
173170 assert meta ['file_date' ] == Time ('2023-10-01T00:00:00' , format = 'isot' )
174171 assert meta ['origin' ] == 'STSCI/SOC'
175172 assert meta ['orig_file' ] == original_file .as_posix ()
173+
174+ if not meta_only :
175+ # Check cutout data and metadata
176+ for key in ['data' , 'dq' , 'err' , 'context' ]:
177+ assert key in af ['roman' ]
178+ assert np .all (af ['roman' ][key ] == cutout_data )
176179
177180 # Write cutouts to ASDF files on disk
178181 cutout = ASDFCutout (test_images , center_coord , cutout_size )
@@ -190,17 +193,19 @@ def check_asdf_metadata(af, original_file, cutout_data):
190193 assert len (fits_files ) == 3
191194 for i , fits_file in enumerate (fits_files ):
192195 with fits .open (fits_file ) as hdul :
193- assert np .all (hdul [0 ].data == cutout .cutouts [i ].data )
194- assert hdul [0 ].header ['NAXIS1' ] == 10
195- assert hdul [0 ].header ['NAXIS2' ] == 10
196- assert hdul [0 ].header ['ORIG_FLE' ] == test_images [i ].as_posix ()
196+ assert hdul [0 ].name == 'PRIMARY'
197+ assert hdul [1 ].name == 'CUTOUT'
198+ assert np .all (hdul [1 ].data == cutout .cutouts [i ].data )
199+ assert hdul [1 ].header ['NAXIS1' ] == 10
200+ assert hdul [1 ].header ['NAXIS2' ] == 10
201+ assert hdul [1 ].header ['ORIG_FLE' ] == test_images [i ].as_posix ()
197202 assert Path (fits_file ).stat ().st_size < Path (test_images [i ]).stat ().st_size
198203
199204 # Check ASDF extension contents (stdatamodels optional)
200205 if importlib .util .find_spec ('stdatamodels' ) is not None :
201206 from stdatamodels import asdf_in_fits
202207 with asdf_in_fits .open (fits_file ) as af :
203- check_asdf_metadata (af , test_images [i ], cutout .cutouts [i ].data )
208+ check_asdf_metadata (af , test_images [i ], cutout .cutouts [i ].data , meta_only = True )
204209
205210
206211@pytest .mark .parametrize ('output_format' , ['.asdf' , '.fits' ])
@@ -226,8 +231,8 @@ def test_asdf_cutout_write_to_zip(tmpdir, test_images, center_coord, cutout_size
226231 else :
227232 with fits .open (io .BytesIO (data )) as hdul :
228233 assert isinstance (hdul , fits .HDUList )
229- assert len (hdul ) == 2 if importlib .util .find_spec ('stdatamodels' ) is not None else 1
230- assert hdul [0 ].data .shape == (cutout_size , cutout_size )
234+ assert len (hdul ) == 3 if importlib .util .find_spec ('stdatamodels' ) is not None else 2
235+ assert hdul [1 ].data .shape == (cutout_size , cutout_size )
231236
232237
233238def test_asdf_cutout_write_to_zip_invalid_format (tmpdir , test_images , center_coord , cutout_size ):
@@ -238,15 +243,16 @@ def test_asdf_cutout_write_to_zip_invalid_format(tmpdir, test_images, center_coo
238243
239244
240245def test_asdf_cutout_lite (test_images , center_coord , cutout_size ):
241- def check_lite_metadata (af ):
246+ def check_lite_metadata (af , meta_only = False ):
242247 """Check that ASDF file contains only lite metadata"""
243248 assert 'roman' in af
244- assert 'data' in af ['roman' ]
245249 assert 'meta' in af ['roman' ]
246250 assert 'wcs' in af ['roman' ]['meta' ]
247251 assert 'orig_file' in af ['roman' ]['meta' ]
248- assert len (af ['roman' ]) == 2 # only data and meta
252+ assert len (af ['roman' ]) == ( 1 if meta_only else 2 )
249253 assert len (af ['roman' ]['meta' ]) == 2 # only wcs and original filename
254+ if not meta_only :
255+ assert 'data' in af ['roman' ]
250256
251257 # Write cutouts to ASDF objects in lite mode
252258 cutout = ASDFCutout (test_images , center_coord , cutout_size , lite = True )
@@ -257,15 +263,16 @@ def check_lite_metadata(af):
257263 cutout = ASDFCutout (test_images , center_coord , cutout_size , lite = True )
258264 for hdul in cutout .fits_cutouts :
259265 has_stdatamodels = importlib .util .find_spec ('stdatamodels' ) is not None
260- assert len (hdul ) == 2 if has_stdatamodels else 1 # primary HDU + embedded ASDF extension
266+ assert len (hdul ) == 3 if has_stdatamodels else 2 # primary HDU + cutout HDU + embedded ASDF extension
261267 assert hdul [0 ].name == 'PRIMARY'
268+ assert hdul [1 ].name == 'CUTOUT'
262269
263270 # Check ASDF extension contents (stdatamodels optional)
264271 if has_stdatamodels :
265- assert hdul [1 ].name == 'ASDF'
272+ assert hdul [2 ].name == 'ASDF'
266273 from stdatamodels import asdf_in_fits
267274 with asdf_in_fits .open (hdul ) as af :
268- check_lite_metadata (af )
275+ check_lite_metadata (af , meta_only = True )
269276
270277
271278def test_asdf_cutout_partial (test_images , center_coord , cutout_size ):
@@ -404,6 +411,36 @@ def test_asdf_cutout_gwcs(test_images, center_coord):
404411 assert gwcs .bounding_box .intervals [1 ].upper == 39
405412
406413
414+ @pytest .mark .parametrize (('is_installed' , 'warn_msg' ),
415+ [(True , 'not available in the correct version' ), (False , 'package cannot be imported' )])
416+ def test_asdf_cutout_stdatamodels (test_images , center_coord , cutout_size , is_installed , warn_msg ):
417+ """ Test that warning is emitted about ASDF-in-FITS embedding for stdatamodels issues """
418+ mock_stdatamodels = None
419+ if is_installed :
420+ mock_stdatamodels = MagicMock ()
421+ mock_stdatamodels .__version__ = '1.0.0'
422+ mock_stdatamodels .asdf_in_fits = MagicMock ()
423+ patch_dict = {'stdatamodels' : mock_stdatamodels }
424+
425+ with patch .dict ('sys.modules' , patch_dict ):
426+ with pytest .warns (ModuleWarning , match = warn_msg ):
427+ cutout = ASDFCutout (test_images , center_coord , cutout_size )
428+ fits_cutouts = cutout .fits_cutouts
429+ assert cutout ._can_embed_asdf_in_fits is False
430+ assert len (fits_cutouts [0 ]) == 2 # primary + cutout HDU only
431+
432+
433+ def test_asdf_cutout_python_version (test_images , center_coord , cutout_size ):
434+ """ Test that warning is emitted about ASDF-in-FITS embedding for Python <3.11 """
435+ with patch ('sys.version_info' , (3 , 10 , 0 )):
436+ with pytest .warns (ModuleWarning , match = 'requires Python 3.11 or higher' ):
437+ cutout = ASDFCutout (test_images , center_coord , cutout_size )
438+ fits_cutouts = cutout .fits_cutouts
439+ assert cutout ._py311_or_higher is False
440+ assert cutout ._can_embed_asdf_in_fits is False
441+ assert len (fits_cutouts [0 ]) == 2 # primary + cutout HDU only
442+
443+
407444def test_get_center_pixel (fakedata ):
408445 """ Test get_center_pixel function """
409446 # Get the fake data
0 commit comments