1.. currentmodule:: astropy.io.fits
2
3..  _fits_io_verification:
4
5Verification
6************
7
8``astropy`` has built in a flexible scheme to verify FITS data conforming to
9the FITS standard. The basic verification philosophy in ``astropy`` is to be
10tolerant with input and strict with output.
11
12When ``astropy`` reads a FITS file which does not conform to FITS standard, it
13will not raise an error and exit. It will try to make the best educated
14interpretation and only gives up when the offending data is accessed and no
15unambiguous interpretation can be reached.
16
17On the other hand, when writing to an output FITS file, the content to be
18written must be strictly compliant to the FITS standard by default. This
19default behavior can be overwritten by several other options, so the user will
20not be held up because of a minor standard violation.
21
22
23FITS Standard
24=============
25
26Since FITS standard is a "loose" standard, there are many places the violation
27can occur and to enforce them all will be almost impossible. It is not uncommon
28for major observatories to generate data products which are not 100% FITS
29compliant. Some observatories have also developed their own nonstandard
30dialect and some of these are so prevalent that they have become de facto
31standards. Examples include the long string value and the use of the CONTINUE
32card.
33
34The violation of the standard can happen at different levels of the data
35structure. ``astropy``'s verification scheme is developed on these hierarchical
36levels. Here are the three ``astropy`` verification levels:
37
381. The HDU List
39
402. Each HDU
41
423. Each Card in the HDU Header
43
44These three levels correspond to the three categories of objects:
45:class:`HDUList`, any HDU (e.g., :class:`PrimaryHDU`, :class:`ImageHDU`, etc.),
46and :class:`Card`. They are the only objects having the ``verify()`` method.
47Most other classes in `astropy.io.fits` do not have a ``verify()`` method.
48
49If ``verify()`` is called at the HDU List level, it verifies standard
50compliance at all three levels, but a call of ``verify()`` at the Card level
51will only check the compliance of that Card. Since ``astropy`` is tolerant when
52reading a FITS file, no ``verify()`` is called on input. On output,
53``verify()`` is called with the most restrictive option as the default.
54
55
56Verification Options
57====================
58
59There are several options accepted by all verify(option) calls in ``astropy``.
60In addition, they available for the ``output_verify`` argument of the following
61methods: ``close()``, ``writeto()``, and ``flush()``. In these cases, they are
62passed to a ``verify()`` call within these methods. The available options are:
63
64**exception**
65
66This option will raise an exception if any FITS standard is violated. This is
67the default option for output (i.e., when ``writeto()``, ``close()``, or
68``flush()`` is called). If a user wants to overwrite this default on output, the
69other options listed below can be used.
70
71**warn**
72
73This option is the same as the ignore option but will send warning messages. It
74will not try to fix any FITS standard violations whether fixable or not.
75
76**ignore**
77
78This option will ignore any FITS standard violation. On output, it will write
79the HDU List content to the output FITS file, whether or not it is conforming
80to the FITS standard.
81
82The ignore option is useful in the following situations:
83
841. An input FITS file with nonstandard formatting is read and the user wants
85   to copy or write out to an output file. The nonstandard formatting will be
86   preserved in the output file.
87
882. A user wants to create a nonstandard FITS file on purpose, possibly for
89   testing or consistency.
90
91No warning message will be printed out. This is like a silent warning option
92(see below).
93
94**fix**
95
96This option will try to fix any FITS standard violations. It is not always
97possible to fix such violations. In general, there are two kinds of FITS
98standard violations: fixable and non-fixable. For example, if a keyword has a
99floating number with an exponential notation in lower case 'e' (e.g., 1.23e11)
100instead of the upper case 'E' as required by the FITS standard, it is a fixable
101violation. On the other hand, a keyword name like 'P.I.' is not fixable, since
102it will not know what to use to replace the disallowed periods. If a violation
103is fixable, this option will print out a message noting it is fixed. If it is
104not fixable, it will throw an exception.
105
106The principle behind fixing is to do no harm. For example, it is plausible to
107'fix' a Card with a keyword name like 'P.I.' by deleting it, but ``astropy``
108will not take such action to hurt the integrity of the data.
109
110Not all fixes may be the "correct" fix, but at least ``astropy`` will try to
111make the fix in such a way that it will not throw off other FITS readers.
112
113**silentfix**
114
115Same as fix, but will not print out informative messages. This may be useful in
116a large script where the user does not want excessive harmless messages. If the
117violation is not fixable, it will still throw an exception.
118
119In addition the following combined options are available:
120
121 * **fix+ignore**
122 * **fix+warn**
123 * **fix+exception**
124 * **silentfix+ignore**
125 * **silentfix+warn**
126 * **silentfix+exception**
127
128These options combine the semantics of the basic options. For example,
129``silentfix+exception`` is actually equivalent to just ``silentfix`` in that
130fixable errors will be fixed silently, but any unfixable errors will raise an
131exception. On the other hand, ``silentfix+warn`` will issue warnings for
132unfixable errors, but will stay silent about any fixed errors.
133
134
135Verifications at Different Data Object Levels
136=============================================
137
138We will examine what ``astropy``'s verification does at the three different
139levels:
140
141
142Verification at HDUList
143-----------------------
144
145At the HDU List level, the verification is only for two simple cases:
146
1471. Verify that the first HDU in the HDU list is a primary HDU. This is a
148   fixable case. The fix is to insert a minimal primary HDU into the HDU list.
149
1502. Verify the second or later HDU in the HDU list is not a primary HDU.
151   Violation will not be fixable.
152
153
154Verification at Each HDU
155------------------------
156
157For each HDU, the mandatory keywords, their locations in the header, and their
158values will be verified. Each FITS HDU has a fixed set of required keywords in
159a fixed order. For example, the primary HDU's header must at least have the
160following keywords:
161
162.. parsed-literal::
163
164    SIMPLE =                     T /
165    BITPIX =                     8 /
166    NAXIS  =                     0
167
168If any of the mandatory keywords are missing or in the wrong order, the fix
169option will fix them::
170
171    >>> from astropy.io import fits
172    >>> filename = fits.util.get_testdata_filepath('verify.fits')
173    >>> hdul = fits.open(filename)
174    >>> hdul[0].header
175    SIMPLE  =                    T / conforms to FITS standard
176    NAXIS   =                    0 / NUMBER OF AXES
177    BITPIX  =                    8 / BITS PER PIXEL
178    >>> hdul[0].verify('fix') # doctest: +SHOW_WARNINGS
179    VerifyWarning: Verification reported errors:
180    VerifyWarning: 'BITPIX' card at the wrong place (card 2).
181      Fixed by moving it to the right place (card 1).
182    VerifyWarning: Note: astropy.io.fits uses zero-based indexing.
183    >>> hdul[0].header           # voila!
184    SIMPLE  =                    T / conforms to FITS standard
185    BITPIX  =                    8 / BITS PER PIXEL
186    NAXIS   =                    0 / NUMBER OF AXES
187    >>> hdul.close()
188
189Verification at Each Card
190-------------------------
191
192The lowest level, the Card, also has the most complicated verification
193possibilities.
194
195Examples
196^^^^^^^^
197
198..
199  EXAMPLE START
200  Verification at Each Card in astropy.io.fits
201
202Here is a list of fixable and not fixable Cards:
203
204Fixable Cards:
205
2061. Floating point numbers with lower case 'e' or 'd'::
207
208    >>> from astropy.io import fits
209    >>> c = fits.Card.fromstring('FIX1    = 2.1e23')
210    >>> c.verify('silentfix')
211    >>> print(c)
212    FIX1    =               2.1E23
213
2142. The equal sign is before column nine in the card image::
215
216    >>> c = fits.Card.fromstring('FIX2= 2')
217    >>> c.verify('silentfix')
218    >>> print(c)
219    FIX2    =                    2
220
2213. String value without enclosing quotes::
222
223    >>> c = fits.Card.fromstring('FIX3    = string value without quotes')
224    >>> c.verify('silentfix')
225    >>> print(c)
226    FIX3    = 'string value without quotes'
227
2284. Missing equal sign before column nine in the card image.
229
2305. Space between numbers and E or D in floating point values::
231
232    >>> c = fits.Card.fromstring('FIX5    = 2.4 e 03')
233    >>> c.verify('silentfix')
234    >>> print(c)
235    FIX5    =               2.4E03
236
2376. Unparsable values will be "fixed" as a string::
238
239    >>> c = fits.Card.fromstring('FIX6    = 2 10 ')
240    >>> c.verify('fix+warn') # doctest: +SHOW_WARNINGS
241    VerifyWarning: Verification reported errors:
242    VerifyWarning: Card 'FIX6' is not FITS standard
243     (invalid value string: '2 10').
244       Fixed 'FIX6' card to meet the FITS standard.
245    VerifyWarning: Note: astropy.io.fits uses zero-based indexing.
246    >>> print(c)
247    FIX6    = '2 10    '
248
249Unfixable Cards:
250
2511. Illegal characters in keyword name.
252
253We will summarize the verification with a "life-cycle" example::
254
255    >>> h = fits.PrimaryHDU()  # create a PrimaryHDU
256    >>> # Try to add an non-standard FITS keyword 'P.I.' (FITS does no allow
257    >>> # '.' in the keyword), if using the update() method - doesn't work!
258    >>> h.header['P.I.'] = 'Hubble' # doctest: +SHOW_WARNINGS
259    VerifyWarning: Keyword name 'P.I.' is greater than 8 characters or
260     contains characters not allowed by the FITS standard;
261      a HIERARCH card will be created.
262    >>> # Have to do it the hard way (so a user will not do this by accident)
263    >>> # First, create a card image and give verbatim card content (including
264    >>> # the proper spacing, but no need to add the trailing blanks)
265    >>> c = fits.Card.fromstring("P.I. = 'Hubble'")
266    >>> h.header.append(c)  # then append it to the header
267    >>> # Now if we try to write to a FITS file, the default output
268    >>> # verification will not take it.
269    >>> h.writeto('pi.fits')  # doctest: +IGNORE_EXCEPTION_DETAIL
270    Traceback (most recent call last):
271     ...
272    VerifyError: HDU 0:
273        Card 5:
274            Card 'P.I. ' is not FITS standard (equal sign not at column 8).
275            Illegal keyword name 'P.I. '
276    >>> # Must set the output_verify argument to 'ignore', to force writing a
277    >>> # non-standard FITS file
278    >>> h.writeto('pi.fits', output_verify='ignore')
279    >>> # Now reading a non-standard FITS file
280    >>> # astropy.io.fits is magnanimous in reading non-standard FITS files
281    >>> hdul = fits.open('pi.fits')
282    >>> hdul[0].header # doctest: +SHOW_WARNINGS
283    SIMPLE  =            T / conforms to FITS standard
284    BITPIX  =            8 / array data type
285    NAXIS   =            0 / number of array dimensions
286    EXTEND  =            T
287    HIERARCH P.I. = 'Hubble  '
288    P.I.    = 'Hubble  '
289    VerifyWarning: Verification reported errors:
290    VerifyWarning: Card 'P.I. ' is not FITS standard (equal sign
291     not at column 8).  Fixed 'P.I. ' card to meet the FITS standard.
292    VerifyWarning: Unfixable error: Illegal keyword name 'P.I. '
293    VerifyWarning: Note: astropy.io.fits uses zero-based indexing.
294    >>> # even when you try to access the offending keyword, it does NOT
295    >>> # complain
296    >>> hdul[0].header['p.i.']
297    'Hubble'
298    >>> # But if you want to make sure if there is anything wrong/non-standard,
299    >>> # use the verify() method
300    >>> hdul.verify() # doctest: +SHOW_WARNINGS
301    VerifyWarning: Verification reported errors:
302    VerifyWarning: HDU 0:
303    VerifyWarning:     Card 5:
304    VerifyWarning:         Illegal keyword name 'P.I. '
305    VerifyWarning: Note: astropy.io.fits uses zero-based indexing.
306    >>> hdul.close()
307
308..
309  EXAMPLE END
310
311Verification Using the FITS Checksum Keyword Convention
312=======================================================
313
314The North American FITS committee has reviewed the FITS Checksum Keyword
315Convention for possible adoption as a FITS Standard. This convention provides
316an integrity check on information contained in FITS HDUs. The convention
317consists of two header keyword cards: CHECKSUM and DATASUM. The CHECKSUM
318keyword is defined as an ASCII character string whose value forces the 32-bit
3191's complement checksum accumulated over all the 2880-byte FITS logical records
320in the HDU to equal negative zero. The DATASUM keyword is defined as a
321character string containing the unsigned integer value of the 32-bit 1's
322complement checksum of the data records in the HDU. Verifying the
323accumulated checksum is still equal to negative zero provides a fairly reliable
324way to determine that the HDU has not been modified by subsequent data
325processing operations or corrupted while copying or storing the file on
326physical media.
327
328In order to avoid any impact on performance, by default ``astropy`` will not
329verify HDU checksums when a file is opened or generate checksum values when a
330file is written. In fact, CHECKSUM and DATASUM cards are automatically removed
331from HDU headers when a file is opened, and any CHECKSUM or DATASUM cards are
332stripped from headers when an HDU is written to a file. In order to verify the
333checksum values for HDUs when opening a file, the user must supply the checksum
334keyword argument in the call to the open convenience function with a value of
335True. When this is done, any checksum verification failure will cause a
336warning to be issued (via the warnings module). If checksum verification is
337requested in the open, and no CHECKSUM or DATASUM cards exist in the HDU
338header, the file will open without comment. Similarly, in order to output the
339CHECKSUM and DATASUM cards in an HDU header when writing to a file, the user
340must supply the checksum keyword argument with a value of True in the call to
341the ``writeto()`` function. It is possible to write only the DATASUM card to the
342header by supplying the checksum keyword argument with a value of 'datasum'.
343
344Examples
345--------
346
347..
348  EXAMPLE START
349  Verification Using the FITS Checksum Keyword Convention
350
351To verify the checksum values for HDUs when opening a file::
352
353    >>> # Open the file checksum.fits verifying the checksum values for all HDUs
354    >>> filename = fits.util.get_testdata_filepath('checksum.fits')
355    >>> hdul = fits.open(filename, checksum=True)
356    >>> hdul.close()
357    >>> # Open the file in.fits where checksum verification fails
358    >>> filename = fits.util.get_testdata_filepath('checksum_false.fits')
359    >>> hdul = fits.open(filename, checksum=True) # doctest: +SHOW_WARNINGS
360    AstropyUserWarning: Checksum verification failed for HDU ('PRIMARY', 1).
361    AstropyUserWarning: Datasum verification failed for HDU ('PRIMARY', 1).
362    AstropyUserWarning: Checksum verification failed for HDU ('RATE', 1).
363    AstropyUserWarning: Datasum verification failed for HDU ('RATE', 1).
364    >>> # Create file out.fits containing an HDU constructed from data
365    >>> # containing both CHECKSUM and DATASUM cards.
366    >>> data = hdul[0].data
367    >>> fits.writeto('out.fits', data=data, checksum=True)
368    >>> hdun = fits.open('out.fits', checksum=True)
369    >>> hdun.close()
370
371    >>> # Create file out.fits containing all the HDUs in the HDULIST
372    >>> # hdul with each HDU header containing only the DATASUM card
373    >>> hdul.writeto('out2.fits', checksum='datasum')
374
375    >>> # Create file out.fits containing the HDU hdu with both CHECKSUM
376    >>> # and DATASUM cards in the header
377    >>> hdu = hdul[1]
378    >>> hdu.writeto('out3.fits', checksum=True)
379
380    >>> # Append a new HDU constructed from array data to the end of
381    >>> # the file existingfile.fits with only the appended HDU
382    >>> # containing both CHECKSUM and DATASUM cards.
383    >>> fits.append('out3.fits', data, checksum=True)
384    >>> hdul.close()
385
386..
387  EXAMPLE END
388