Skip to content

Commit

Permalink
Merge e0c1f79 into 529a5d8
Browse files Browse the repository at this point in the history
  • Loading branch information
SaOgaz committed Feb 24, 2017
2 parents 529a5d8 + e0c1f79 commit cb6d9a5
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 15 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Expand Up @@ -25,6 +25,8 @@ New Features

- Added absolute tolerance parameter when comparing FITS files. [#4729]

- New convenience function ``printdiff`` to print out diff reports. [#5759]

- ``astropy.io.misc``

- ``astropy.io.registry``
Expand Down
130 changes: 118 additions & 12 deletions astropy/io/fits/convenience.py
Expand Up @@ -61,9 +61,10 @@

import numpy as np

from .diff import FITSDiff, HDUDiff
from .file import FILE_MODES, _File
from .hdu.base import _BaseHDU, _ValidHDU
from .hdu.hdulist import fitsopen
from .hdu.hdulist import fitsopen, HDUList
from .hdu.image import PrimaryHDU, ImageHDU
from .hdu.table import BinTableHDU
from .header import Header
Expand All @@ -79,7 +80,7 @@

__all__ = ['getheader', 'getdata', 'getval', 'setval', 'delval', 'writeto',
'append', 'update', 'info', 'tabledump', 'tableload',
'table_to_hdu']
'table_to_hdu', 'printdiff']


def getheader(filename, *args, **kwargs):
Expand All @@ -98,7 +99,7 @@ def getheader(filename, *args, **kwargs):
kwargs
Any additional keyword arguments to be passed to
`astropy.io.fits.open`.
`~astropy.io.fits.open`.
Returns
-------
Expand Down Expand Up @@ -182,7 +183,7 @@ def getdata(filename, *args, **kwargs):
kwargs
Any additional keyword arguments to be passed to
`astropy.io.fits.open`.
`~astropy.io.fits.open`.
Returns
-------
Expand Down Expand Up @@ -262,7 +263,7 @@ def getval(filename, keyword, *args, **kwargs):
kwargs
Any additional keyword arguments to be passed to
`astropy.io.fits.open`.
`~astropy.io.fits.open`.
*Note:* This function automatically specifies ``do_not_scale_image_data
= True`` when opening the file so that values can be retrieved from the
unmodified header.
Expand Down Expand Up @@ -329,7 +330,7 @@ def setval(filename, keyword, *args, **kwargs):
kwargs
Any additional keyword arguments to be passed to
`astropy.io.fits.open`.
`~astropy.io.fits.open`.
*Note:* This function automatically specifies ``do_not_scale_image_data
= True`` when opening the file so that values can be retrieved from the
unmodified header.
Expand Down Expand Up @@ -376,7 +377,7 @@ def delval(filename, keyword, *args, **kwargs):
kwargs
Any additional keyword arguments to be passed to
`astropy.io.fits.open`.
`~astropy.io.fits.open`.
*Note:* This function automatically specifies ``do_not_scale_image_data
= True`` when opening the file so that values can be retrieved from the
unmodified header.
Expand Down Expand Up @@ -443,7 +444,7 @@ def writeto(filename, data, header=None, output_verify='exception',

def table_to_hdu(table):
"""
Convert an astropy.table.Table object to a FITS BinTableHDU
Convert an `~astropy.table.Table` object to a FITS BinTableHDU
Parameters
----------
Expand All @@ -452,7 +453,7 @@ def table_to_hdu(table):
Returns
-------
table_hdu : astropy.io.fits.BinTableHDU
table_hdu : `~astropy.io.fits.BinTableHDU`
The FITS binary table HDU
"""
# Avoid circular imports
Expand Down Expand Up @@ -571,7 +572,7 @@ def append(filename, data, header=None, checksum=False, verify=True, **kwargs):
kwargs
Any additional keyword arguments to be passed to
`astropy.io.fits.open`.
`~astropy.io.fits.open`.
"""

name, closed, noexist_or_empty = _stat_filename_or_fileobj(filename)
Expand Down Expand Up @@ -642,7 +643,7 @@ def update(filename, data, *args, **kwargs):
kwargs
Any additional keyword arguments to be passed to
`astropy.io.fits.open`.
`~astropy.io.fits.open`.
"""

# The arguments to this function are a bit trickier to deal with than others
Expand Down Expand Up @@ -689,7 +690,7 @@ def info(filename, output=None, **kwargs):
kwargs
Any additional keyword arguments to be passed to
`astropy.io.fits.open`.
`~astropy.io.fits.open`.
*Note:* This function sets ``ignore_missing_end=True`` by default.
"""

Expand All @@ -709,6 +710,111 @@ def info(filename, output=None, **kwargs):
return ret


def printdiff(inputa, inputb, *args, **kwargs):
"""
Compare two parts of a FITS file, including entire FITS files,
FITS `HDUList` objects and FITS ``HDU`` objects.
Parameters
----------
inputa : str, `HDUList` object, or ``HDU`` object
The filename of a FITS file, `HDUList`, or ``HDU``
object to compare to ``inputb``.
inputb : str, `HDUList` object, or ``HDU`` object
The filename of a FITS file, `HDUList`, or ``HDU``
object to compare to ``inputa``.
ext, extname, extver
Additional positional arguments are for extension specification if your
inputs are string filenames (will not work if
``inputa`` and ``inputb`` are ``HDU`` objects or `HDUList` objects).
They are flexible and are best illustrated by examples. In addition
to using these arguments positionally you can directly call the
keyword parameters ``ext``, ``extname``.
By extension number::
printdiff('inA.fits', 'inB.fits', 0) # the primary HDU
printdiff('inA.fits', 'inB.fits', 2) # the second extension
printdiff('inA.fits', 'inB.fits', ext=2) # the second extension
By name, i.e., ``EXTNAME`` value (if unique)::
printdiff('inA.fits', 'inB.fits', 'sci')
printdiff('inA.fits', 'inB.fits', extname='sci') # equivalent
*Note:* ``EXTNAME`` values are not case sensitive.
By combination of ``EXTNAME`` and ``EXTVER`` as separate
arguments or as a tuple::
printdiff('inA.fits', 'inB.fits', 'sci', 2) # EXTNAME='SCI'
# & EXTVER=2
printdiff('inA.fits', 'inB.fits', extname='sci', extver=2)
# equivalent
printdiff('inA.fits', 'inB.fits', ('sci', 2)) # equivalent
Ambiguous or conflicting specifications will raise an exception::
printdiff('inA.fits', 'inB.fits',
ext=('sci', 1), extname='err', extver=2)
kwargs
Any additional keyword arguments to be passed to
`~astropy.io.fits.FITSDiff`.
Notes
-----
The primary use for the `printdiff` function is to allow quick print out
of a FITS difference report and will write to ``sys.stdout``.
To save the diff report to a file please use `~astropy.io.fits.FITSDiff`
directly.
"""

# Pop extension keywords
extension = {key: kwargs.pop(key) for key in ['ext', 'extname', 'extver']
if key in kwargs}
has_extensions = len(args) != 0 or extension

if isinstance(inputa, string_types) and has_extensions:
# Use handy _getext to interpret any ext keywords
modea, closeda = _get_file_mode(inputa)
modeb, closedb = _get_file_mode(inputb)
hdulista, extidxa = _getext(inputa, modea, *args, **extension)
hdulistb, extidxb = _getext(inputa, modeb, *args, **extension)

try:
hdua = hdulista[extidxa]
hdub = hdulistb[extidxb]
# See above print for note
print(HDUDiff(hdua, hdub, **kwargs).report())

finally:
# See note about _close in getheader function
hdulista._close(closed=closeda)
hdulistb._close(closed=closedb)
return

# If input is not a string, can feed HDU objects or HDUList directly,
# but can't currently handle extensions
elif isinstance(inputa, _ValidHDU) and has_extensions:
raise ValueError("Cannot use extension keywords when providing an "
"HDU Object")

elif isinstance(inputa, _ValidHDU) and not has_extensions:
print(HDUDiff(inputa, inputb, **kwargs).report())
return

elif isinstance(inputa, HDUList) and has_extensions:
raise NotImplementedError("Extension specification with HUDList "
"Objects not implemented")

# This function is EXCLUSIVELY for printing the diff report to screen
# in a one-liner call, hence the use of print instead of logging
print(FITSDiff(inputa, inputb, **kwargs).report())


@deprecated_renamed_argument('clobber', 'overwrite', '1.3', pending=True)
def tabledump(filename, datafile=None, cdfile=None, hfile=None, ext=1,
overwrite=False):
Expand Down
34 changes: 31 additions & 3 deletions astropy/io/fits/tests/test_convenience.py
Expand Up @@ -4,11 +4,10 @@

import warnings

import numpy as np

from ....extern import six # pylint: disable=W0611
from ....io import fits
from ....table import Table
from .. import printdiff
from ....tests.helper import pytest, catch_warnings

from . import FitsTestCase
Expand Down Expand Up @@ -52,7 +51,8 @@ def test_table_to_hdu(self, tmpdir):
with catch_warnings() as w:
hdu = fits.table_to_hdu(table)
assert len(w) == 1
assert str(w[0].message).startswith("'not-a-unit' did not parse as fits unit")
assert str(w[0].message).startswith("'not-a-unit' did not parse as"
" fits unit")

# Check that TUNITn cards appear in the correct order
# (https://github.com/astropy/astropy/pull/5720)
Expand All @@ -61,3 +61,31 @@ def test_table_to_hdu(self, tmpdir):
assert isinstance(hdu, fits.BinTableHDU)
filename = str(tmpdir.join('test_table_to_hdu.fits'))
hdu.writeto(filename, overwrite=True)

def test_printdiff(self):
"""
Test that FITSDiff can run the different inputs without crashing.
"""

# Testing different string input options
assert printdiff(self.data('arange.fits'),
self.data('blank.fits')) is None
assert printdiff(self.data('arange.fits'),
self.data('blank.fits'), ext=0) is None
assert printdiff(self.data('o4sp040b0_raw.fits'),
self.data('o4sp040b0_raw.fits'),
extname='sci') is None

# Test HDU object inputs
with fits.open(self.data('stddata.fits'), mode='readonly') as in1:
with fits.open(self.data('checksum.fits'), mode='readonly') as in2:

assert printdiff(in1[0], in2[0]) is None

with pytest.raises(ValueError):
printdiff(in1[0], in2[0], ext=0)

assert printdiff(in1, in2) is None

with pytest.raises(NotImplementedError):
printdiff(in1, in2, 0)
4 changes: 4 additions & 0 deletions docs/io/fits/api/files.rst
Expand Up @@ -15,6 +15,10 @@ File Handling and Convenience Functions
^^^^^^^^^^^^
.. autofunction:: info

:func:`printdiff`
^^^^^^^^^^^^^^^^^
.. autofunction:: printdiff

:func:`append`
^^^^^^^^^^^^^^
.. autofunction:: append
Expand Down
13 changes: 13 additions & 0 deletions docs/io/fits/index.rst
Expand Up @@ -724,6 +724,19 @@ the 3rd argument is not a header, it (and other positional arguments) are
assumed to be the extension specification(s). Header and extension specs can
also be keyword arguments.

The :func:`printdiff` function will print a difference report of two FITS files,
including headers and data. The first two arguments must be two FITS
filenames or FITS file objects with matching data types (i.e., if using strings
to specify filenames, both inputs must be strings). The third
argument is an optional extension specification, with the same call format
of :func:`getheader` and :func:`getdata`. In addition you can add any keywords
accepted by the :class:`FITSDiff` class::

>>> # get a difference report of ext 2 of inA and inB
>>> printdiff('inA.fits', 'inB.fits', ext=2)
>>> # ignore HISTORY keywords
>>> printdiff('inA.fits', 'inB.fits', ignore_keywords=('HISTORY','COMMENT')

Finally, the :func:`info` function will print out information of the specified
FITS file::

Expand Down

0 comments on commit cb6d9a5

Please sign in to comment.