FILE_MODES = {
    'rb': 'readonly', 'rb+': 'update',
    'wb': 'ostream', 'wb+': 'update',
    'ab': 'ostream', 'ab+': 'append'} class _File(object):
    """
    Represents a FITS file on disk (or in some other file-like object).
    """

    def __init__(self, fileobj=None, mode=None, memmap=None, clobber=False):
        self.strict_memmap = bool(memmap)
        memmap = True if memmap is None else memmap

        if fileobj is None:
            self._file = None
            self.closed = False
            self.binary = True
            self.mode = mode
            self.memmap = memmap
            self.compression = None
            self.readonly = False
            self.writeonly = False
            self.simulateonly = True
            return
        else:
            self.simulateonly = False

        # Holds mmap instance for files that use mmap
        self._mmap = None

        if mode is None:
            if _is_random_access_file_backed(fileobj):
                fmode = fileobj_mode(fileobj)
                # If the mode is unsupported just leave it as None; we'll
                # catch this case below
                mode = FILE_MODES.get(fmode)
            else:
                mode = 'readonly'  # The default

        if mode not in PYFITS_MODES:
            raise ValueError("Mode '%s' not recognized" % mode)

        if (isinstance(fileobj, string_types) and
                mode not in ('ostream', 'append') and
                len(urllib.parse.urlparse(fileobj).scheme) > 1):
            # This is an URL.
            self.name, _ = urllib.request.urlretrieve(fileobj)
        else:
            self.name = fileobj_name(fileobj)

        self.closed = False
        self.binary = True
        self.mode = mode
        self.memmap = memmap

        # Underlying fileobj is a file-like object, but an actual file object
        self.file_like = False

        # More defaults to be adjusted below as necessary
        self.compression = None
        self.readonly = False
        self.writeonly = False

        # Initialize the internal self._file object
        if _is_random_access_file_backed(fileobj):
            self._open_fileobj(fileobj, mode, clobber)
        elif isinstance(fileobj, string_types):
            self._open_filename(fileobj, mode, clobber)
        else:
            self._open_filelike(fileobj, mode, clobber)

        self.fileobj_mode = fileobj_mode(self._file)

        if isinstance(fileobj, gzip.GzipFile):
            self.compression = 'gzip'
        elif isinstance(fileobj, zipfile.ZipFile):
            # Reading from zip files is supported but not writing (yet)
            self.compression = 'zip'
        elif isinstance(fileobj, bz2.BZ2File):
            self.compression = 'bzip2'

        if (mode in ('readonly', 'copyonwrite', 'denywrite') or
                (self.compression and mode == 'update')):
            self.readonly = True
        elif (mode == 'ostream' or
                (self.compression and mode == 'append')):
            self.writeonly = True

        # For 'ab+' mode, the pointer is at the end after the open in
        # Linux, but is at the beginning in Solaris.
        if (mode == 'ostream' or self.compression or
            not hasattr(self._file, 'seek')):
            # For output stream start with a truncated file.
            # For compressed files we can't really guess at the size
            self.size = 0
        else:
            pos = self._file.tell()
            self._file.seek(0, 2)
            self.size = self._file.tell()
            self._file.seek(pos)

        if self.memmap:
            if not isfile(self._file):
                self.memmap = False
            elif not self.readonly and not self._mmap_available:
                # Test mmap.flush--see
                # https://github.com/astropy/astropy/issues/968
                self.memmap = False

    def __repr__(self):
        return '<%s.%s %s>' % (self.__module__, self.__class__.__name__,
                               self._file)

    # Support the 'with' statement
    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        self.close()

    def readable(self):
        if self.writeonly:
            return False
        return isreadable(self._file)

    def read(self, size=None):
        if not hasattr(self._file, 'read'):
            raise EOFError
        try:
            return self._file.read(size)
        except IOError:
            # On some versions of Python, it appears, GzipFile will raise an
            # IOError if you try to read past its end (as opposed to just
            # returning '')
            if self.compression == 'gzip':
                return ''
            raise

    def readarray(self, size=None, offset=0, dtype=np.uint8, shape=None):
        """
        Similar to file.read(), but returns the contents of the underlying
        file as a numpy array (or mmap'd array if memmap=True) rather than a
        string.

        Usually it's best not to use the `size` argument with this method, but
        it's provided for compatibility.
        """

        if not hasattr(self._file, 'read'):
            raise EOFError

        if not isinstance(dtype, np.dtype):
            dtype = np.dtype(dtype)

        if size and size % dtype.itemsize != 0:
            raise ValueError('size %d not a multiple of %s' % (size, dtype))

        if isinstance(shape, int):
            shape = (shape,)

        if not (size or shape):
            warnings.warn('No size or shape given to readarray(); assuming a '
                          'shape of (1,)')
            shape = (1,)

        if size and not shape:
            shape = (size // dtype.itemsize,)

        if size and shape:
            actualsize = np.prod(shape) * dtype.itemsize

            if actualsize < size:
                raise ValueError('size %d is too few bytes for a %s array of '
                                 '%s' % (size, shape, dtype))
            if actualsize < size:
                raise ValueError('size %d is too many bytes for a %s array of '
                                 '%s' % (size, shape, dtype))

        if self.memmap:
            if self._mmap is None:
                # Instantiate Memmap array of the file offset at 0
                # (so we can return slices of it to offset anywhere else into
                # the file)
                memmap = Memmap(self._file,
                                mode=MEMMAP_MODES[self.mode],
                                dtype=np.uint8)

                # Now we immediately discard the memmap array; we are really
                # just using it as a factory function to instantiate the mmap
                # object in a convenient way (may later do away with this
                # usage)
                self._mmap = memmap.base

                # Prevent dorking with self._memmap._mmap by memmap.__del__ in
                # Numpy 1.6 (see
                # https://github.com/numpy/numpy/commit/dcc355a0b179387eeba10c95baf2e1eb21d417c7)
                memmap._mmap = None
                del memmap

            return np.ndarray(shape=shape, dtype=dtype, offset=offset,
                              buffer=self._mmap)
        else:
            count = reduce(lambda x, y: x * y, shape)
            pos = self._file.tell()
            self._file.seek(offset)
            data = _array_from_file(self._file, dtype, count, '')
            data.shape = shape
            self._file.seek(pos)
            return data

    def writable(self):
        if self.readonly:
            return False
        return iswritable(self._file)

    def write(self, string):
        if hasattr(self._file, 'write'):
            _write_string(self._file, string)

    def writearray(self, array):
        """
        Similar to file.write(), but writes a numpy array instead of a string.

        Also like file.write(), a flush() or close() may be needed before
        the file on disk reflects the data written.
        """

        if hasattr(self._file, 'write'):
            _array_to_file(array, self._file)

    def flush(self):
        if hasattr(self._file, 'flush'):
            self._file.flush()

    def seek(self, offset, whence=0):
        # In newer Python versions, GzipFiles support the whence argument, but
        # I don't think it was added until 2.6; instead of assuming it's
        # present, we implement our own support for it here
        if not hasattr(self._file, 'seek'):
            return
        if isinstance(self._file, gzip.GzipFile):
            if whence:
                if whence == 1:
                    offset = self._file.offset + offset
                else:
                    raise ValueError('Seek from end not supported')
            self._file.seek(offset)
        else:
            self._file.seek(offset, whence)

        pos = self._file.tell()
        if self.size and pos > self.size:
            warnings.warn('File may have been truncated: actual file length '
                          '(%i) is smaller than the expected size (%i)' %
                          (self.size, pos))

    def tell(self):
        if not hasattr(self._file, 'tell'):
            raise EOFError
        return self._file.tell()

    def truncate(self, size=None):
        if hasattr(self._file, 'truncate'):
            self._file.truncate(size)

    def close(self):
        """
        Close the 'physical' FITS file.
        """

        if hasattr(self._file, 'close'):
            self._file.close()

        self._maybe_close_mmap()
        # Set self._memmap to None anyways since no new .data attributes can be
        # loaded after the file is closed
        self._mmap = None

        self.closed = True

    def _maybe_close_mmap(self, refcount_delta=0):
        """
        When mmap is in use these objects hold a reference to the mmap of the
        file (so there is only one, shared by all HDUs that reference this
        file).

        This will close the mmap if there are no arrays referencing it.
        """

        if (self._mmap is not None and
                sys.getrefcount(self._mmap) == 2 + refcount_delta):
            self._mmap.close()
            self._mmap = None

    def _overwrite_existing(self, clobber, fileobj, closed):
        """Overwrite an existing file if ``clobber`` is ``True``, otherwise
        raise an IOError.  The exact behavior of this method depends on the
        _File object state and is only meant for use within the ``_open_*``
        internal methods.
        """

        # The file will be overwritten...
        if ((self.file_like and
                (hasattr(fileobj, 'len') and fileobj.len > 0)) or
                (os.path.exists(self.name) and
                 os.path.getsize(self.name) != 0)):
            if clobber:
                warnings.warn("Overwriting existing file %r." % self.name)
                if self.file_like and hasattr(fileobj, 'truncate'):
                    fileobj.truncate(0)
                else:
                    if not closed:
                        fileobj.close()
                    os.remove(self.name)
            else:
                raise IOError("File %r already exists." % self.name)

    def _open_fileobj(self, fileobj, mode, clobber):
        """Open a FITS file from a file object or a GzipFile object."""

        closed = fileobj_closed(fileobj)
        fmode = fileobj_mode(fileobj) or PYFITS_MODES[mode]

        if mode == 'ostream':
            self._overwrite_existing(clobber, fileobj, closed)

        if not closed:
            # Although we have a specific mapping in PYFITS_MODES from our
            # custom file modes to raw file object modes, many of the latter
            # can be used appropriately for the former.  So determine whether
            # the modes match up appropriately
            if ((mode in ('readonly', 'denywrite', 'copyonwrite') and
                    not ('r' in fmode or '+' in fmode)) or
                    (mode == 'append' and fmode not in ('ab+', 'rb+')) or
                    (mode == 'ostream' and
                     not ('w' in fmode or 'a' in fmode or '+' in fmode)) or
                    (mode == 'update' and fmode not in ('rb+', 'wb+'))):
                raise ValueError(
                    "Mode argument '%s' does not match mode of the input "
                    "file (%s)." % (mode, fmode))
            self._file = fileobj
        elif isfile(fileobj):
            self._file = fileobj_open(self.name, PYFITS_MODES[mode])
        else:
            self._file = gzip.open(self.name, PYFITS_MODES[mode])

        if fmode == 'ab+':
            # Return to the beginning of the file--in Python 3 when opening in
            # append mode the file pointer is at the end of the file
            self._file.seek(0)

    def _open_filelike(self, fileobj, mode, clobber):
        """Open a FITS file from a file-like object, i.e. one that has
        read and/or write methods.
        """

        self.file_like = True
        self._file = fileobj

        if fileobj_closed(fileobj):
            raise IOError("Cannot read from/write to a closed file-like "
                          "object (%r)." % fileobj)

        if isinstance(fileobj, zipfile.ZipFile):
            self._open_zipfile(fileobj, mode)
            self._file.seek(0)
            # We can bypass any additional checks at this point since now
            # self._file points to the temp file extracted from the zip
            return

        # If there is not seek or tell methods then set the mode to
        # output streaming.
        if (not hasattr(self._file, 'seek') or
            not hasattr(self._file, 'tell')):
            self.mode = mode = 'ostream'

        if mode == 'ostream':
            self._overwrite_existing(clobber, fileobj, False)

        # Any "writeable" mode requires a write() method on the file object
        if (self.mode in ('update', 'append', 'ostream') and
            not hasattr(self._file, 'write')):
            raise IOError("File-like object does not have a 'write' "
                          "method, required for mode '%s'."
                          % self.mode)

        # Any mode except for 'ostream' requires readability
        if self.mode != 'ostream' and not hasattr(self._file, 'read'):
            raise IOError("File-like object does not have a 'read' "
                          "method, required for mode %r."
                          % self.mode)

    def _open_filename(self, filename, mode, clobber):
        """Open a FITS file from a filename string."""

        if mode == 'ostream':
            self._overwrite_existing(clobber, None, True)

        if os.path.exists(self.name):
            with fileobj_open(self.name, 'rb') as f:
                magic = f.read(4)
        else:
            magic = b('')

        ext = os.path.splitext(self.name)[1]

        if ext == '.gz' or magic.startswith(GZIP_MAGIC):
            # Handle gzip files
            self._file = gzip.open(self.name, PYFITS_MODES[mode])
            self.compression = 'gzip'
        elif ext == '.zip' or magic.startswith(PKZIP_MAGIC):
            # Handle zip files
            self._open_zipfile(self.name, mode)
        elif ext == '.bz2' or magic.startswith(BZIP2_MAGIC):
            # Handle bzip2 files
            if mode in ['update', 'append']:
                raise IOError("update and append modes are not supported "
                              "with bzip2 files")
            # bzip2 only supports 'w' and 'r' modes
            bzip2_mode = 'w' if mode == 'ostream' else 'r'
            self._file = bz2.BZ2File(self.name, bzip2_mode)
        else:
            self._file = fileobj_open(self.name, PYFITS_MODES[mode])

        # Make certain we're back at the beginning of the file
        # BZ2File does not support seek when the file is open for writing, but
        # when opening a file for write, bz2.BZ2File always truncates anyway.
        if isinstance(self._file, bz2.BZ2File) and mode == 'ostream':
            pass
        else:
            self._file.seek(0)

    def _open_zipfile(self, fileobj, mode):
        """Limited support for zipfile.ZipFile objects containing a single
        a file.  Allows reading only for now by extracting the file to a
        tempfile.
        """

        if mode in ('update', 'append'):
            raise IOError(
                  "Writing to zipped fits files is not currently "
                  "supported")

        if not isinstance(fileobj, zipfile.ZipFile):
            zfile = zipfile.ZipFile(fileobj)
            close = True
        else:
            zfile = fileobj
            close = False

        namelist = zfile.namelist()
        if len(namelist) != 1:
            raise IOError(
              "Zip files with multiple members are not supported.")
        self._file = tempfile.NamedTemporaryFile(suffix='.fits')
        self._file.write(zfile.read(namelist[0]))

        if close:
            zfile.close()
        self.compression = 'zip'

    @classproperty(lazy=True)
    def _mmap_available(cls):
        """Tests that mmap, and specifically mmap.flush works.  This may
        be the case on some uncommon platforms (see
        https://github.com/astropy/astropy/issues/968).

        If mmap.flush is found not to work, ``self.memmap = False`` is
        set and a warning is issued.
        """

        tmpfd, tmpname = tempfile.mkstemp()
        try:
            # Windows does not allow mappings on empty files
            os.write(tmpfd, encode_ascii(' '))
            os.fsync(tmpfd)
            try:
                mm = mmap.mmap(tmpfd, 1, access=mmap.ACCESS_WRITE)
            except mmap.error as exc:
                warnings.warn('Failed to create mmap: %s; mmap use will be '
                              'disabled' % exc)
                del exc
                return False
            try:
                mm.flush()
            except mmap.error:
                warnings.warn('mmap.flush is unavailable on this platform; '
                              'using mmap in writeable mode will be disabled')
                return False
            finally:
                mm.close()
        finally:
            os.close(tmpfd)
            os.remove(tmpname)

        return True class _BaseHDU(object):
    """Base class for all HDU (header data unit) classes."""

    _hdu_registry = set()

    # This HDU type is part of the FITS standard
    _standard = True

    # Byte to use for padding out blocks
    _padding_byte = '\x00'

    _default_name = ''

    def __new__(cls, data=None, header=None, *args, **kwargs):
        """
        Iterates through the subclasses of _BaseHDU and uses that class's
        match_header() method to determine which subclass to instantiate.

        It's important to be aware that the class hierarchy is traversed in a
        depth-last order.  Each match_header() should identify an HDU type as
        uniquely as possible.  Abstract types may choose to simply return False
        or raise NotImplementedError to be skipped.

        If any unexpected exceptions are raised while evaluating
        match_header(), the type is taken to be _CorruptedHDU.
        """

        klass = _hdu_class_from_header(cls, header)
        return super(_BaseHDU, cls).__new__(klass)

    def __init__(self, data=None, header=None, *args, **kwargs):
        if header is None:
            header = Header()
        self._header = header
        self._file = None
        self._buffer = None
        self._header_offset = None
        self._data_offset = None
        self._data_size = None

        # This internal variable is used to track whether the data attribute
        # still points to the same data array as when the HDU was originally
        # created (this does not track whether the data is actually the same
        # content-wise)
        self._data_replaced = False
        self._data_needs_rescale = False
        self._new = True
        self._output_checksum = False

        if 'DATASUM' in self._header and 'CHECKSUM' not in self._header:
            self._output_checksum = 'datasum'
        elif 'CHECKSUM' in self._header:
            self._output_checksum = True

    @property
    def header(self):
        return self._header

    @header.setter
    def header(self, value):
        self._header = value

    @property
    def name(self):
        # Convert the value to a string to be flexible in some pathological
        # cases (see ticket #96)
        return str(self._header.get('EXTNAME', self._default_name))

    @name.setter
    def name(self, value):
        if not isinstance(value, string_types):
            raise TypeError("'name' attribute must be a string")
        if not pyfits.EXTENSION_NAME_CASE_SENSITIVE:
            value = value.upper()
        if 'EXTNAME' in self._header:
            self._header['EXTNAME'] = value
        else:
            self._header['EXTNAME'] = (value, 'extension name')

    @property
    def ver(self):
        return self._header.get('EXTVER', 1)

    @ver.setter
    def ver(self, value):
        if not _is_int(value):
            raise TypeError("'ver' attribute must be an integer")
        if 'EXTVER' in self._header:
            self._header['EXTVER'] = value
        else:
            self._header['EXTVER'] = (value, 'extension value')

    @property
    def level(self):
        return self._header.get('EXTLEVEL', 1)

    @level.setter
    def level(self, value):
        if not _is_int(value):
            raise TypeError("'level' attribute must be an integer")
        if 'EXTLEVEL' in self._header:
            self._header['EXTLEVEL'] = value
        else:
            self._header['EXTLEVEL'] = (value, 'extension level')

    @property
    def is_image(self):
        return (
            self.name == 'PRIMARY' or
            ('XTENSION' in self._header and
             (self._header['XTENSION'] == 'IMAGE' or
              (self._header['XTENSION'] == 'BINTABLE' and
               'ZIMAGE' in self._header and self._header['ZIMAGE'] == True))))

    @property
    def _data_loaded(self):
        return ('data' in self.__dict__ and self.data is not DELAYED)

    @property
    def _has_data(self):
        return self._data_loaded and self.data is not None

    @property
    @deprecated('3.2', alternative='the `._header_offset` attribute',
                pending=True)
    def _hdrLoc(self):
        """The byte offset of this HDU's header in the file it came from;
        available for backwards compatibility--use ._header_offset instead.
        """

        return self._header_offset

    @_hdrLoc.setter
    @deprecated('3.2', alternative='the `._header_offset` attribute',
                pending=True)
    def _hdrLoc(self, value):
        self._header_offset = value

    @property
    @deprecated('3.2', alternative='the `._data_offset` attribute',
                pending=True)
    def _datLoc(self):
        """The byte offset of this HDU's data portion in the file it came from;
        available for backwards compatibility--use ._data_offset instead.
        """

        return self._data_offset

    @_datLoc.setter
    @deprecated('3.2', alternative='the `._data_offset` attribute',
                pending=True)
    def _datLoc(self, value):
        self._data_offset = value

    @property
    @deprecated('3.2', alternative='the `._data_size` attribute',
                pending=True)
    def _datSpan(self):
        """The byte size of this HDU's data portion in the file it came from;
        available for backwards compatibility--use ._data_size instead.
        """

        return self._data_size

    @_datSpan.setter
    @deprecated('3.2', alternative='the `._data_size` attribute',
                pending=True)
    def _datSpan(self, value):
        self._data_size = value

    @classmethod
    def register_hdu(cls, hducls):
        cls._hdu_registry.add(hducls)

    @classmethod
    def unregister_hdu(cls, hducls):
        if hducls in cls._hdu_registry:
            cls._hdu_registry.remove(hducls)

    @classmethod
    def match_header(cls, header):
        raise NotImplementedError

    @classmethod
    def fromstring(cls, data, checksum=False, ignore_missing_end=False,
                   **kwargs):
        """
        Creates a new HDU object of the appropriate type from a string
        containing the HDU's entire header and, optionally, its data.

        Note: When creating a new HDU from a string without a backing file
        object, the data of that HDU may be read-only.  It depends on whether
        the underlying string was an immutable Python str/bytes object, or some
        kind of read-write memory buffer such as a `memoryview`.

        Parameters
        ----------
        data : str, bytearray, memoryview, ndarray
           A byte string containing the HDU's header and data.

        checksum : bool, optional
           Check the HDU's checksum and/or datasum.

        ignore_missing_end : bool, optional
           Ignore a missing end card in the header data.  Note that without the
           end card the end of the header may be ambiguous and resulted in a
           corrupt HDU.  In this case the assumption is that the first 2880
           block that does not begin with valid FITS header data is the
           beginning of the data.

        kwargs : optional
           May consist of additional keyword arguments specific to an HDU
           type--these correspond to keywords recognized by the constructors of
           different HDU classes such as `PrimaryHDU`, `ImageHDU`, or
           `BinTableHDU`.  Any unrecognized keyword arguments are simply
           ignored.
        """

        return cls._readfrom_internal(data, checksum=checksum,
                                      ignore_missing_end=ignore_missing_end,
                                      **kwargs)

    @classmethod
    def readfrom(cls, fileobj, checksum=False, ignore_missing_end=False,
                 **kwargs):
        """
        Read the HDU from a file.  Normally an HDU should be opened with
        :func:`open` which reads the entire HDU list in a FITS file.  But this
        method is still provided for symmetry with :func:`writeto`.

        Parameters
        ----------
        fileobj : file object or file-like object
            Input FITS file.  The file's seek pointer is assumed to be at the
            beginning of the HDU.

        checksum : bool
            If `True`, verifies that both ``DATASUM`` and ``CHECKSUM`` card
            values (when present in the HDU header) match the header and data
            of all HDU's in the file.

        ignore_missing_end : bool
            Do not issue an exception when opening a file that is missing an
            ``END`` card in the last header.
        """

        # TODO: Figure out a way to make it possible for the _File
        # constructor to be a noop if the argument is already a _File
        if not isinstance(fileobj, _File):
            fileobj = _File(fileobj)


        hdu = cls._readfrom_internal(fileobj, checksum=checksum,
                                     ignore_missing_end=ignore_missing_end,
                                     **kwargs)

        # If the checksum had to be checked the data may have already been read
        # from the file, in which case we don't want to seek relative
        fileobj.seek(hdu._data_offset + hdu._data_size, os.SEEK_SET)
        return hdu

    def writeto(self, name, output_verify='exception', clobber=False,
                checksum=False):
        """
        Write the HDU to a new file.  This is a convenience method to
        provide a user easier output interface if only one HDU needs
        to be written to a file.

        Parameters
        ----------
        name : file path, file object or file-like object
            Output FITS file.  If the file object is already opened, it must
            be opened in a writeable mode.

        output_verify : str
            Output verification option.  Must be one of ``"fix"``,
            ``"silentfix"``, ``"ignore"``, ``"warn"``, or
            ``"exception"``.  May also be any combination of ``"fix"`` or
            ``"silentfix"`` with ``"+ignore"``, ``+warn``, or ``+exception``
            (e.g. ``"fix+warn"``).  See :ref:`verify` for more info.

        clobber : bool
            Overwrite the output file if exists.

        checksum : bool
            When `True` adds both ``DATASUM`` and ``CHECKSUM`` cards
            to the header of the HDU when written to the file.
        """

        from pyfits.hdu.hdulist import HDUList

        hdulist = HDUList([self])
        hdulist.writeto(name, output_verify, clobber=clobber,
                        checksum=checksum)

    @classmethod
    def _readfrom_internal(cls, data, header=None, checksum=False,
                           ignore_missing_end=False, **kwargs):
        """
        Provides the bulk of the internal implementation for readfrom and
        fromstring.

        For some special cases, supports using a header that was already
        created, and just using the input data for the actual array data.
        """

        hdu_buffer = None
        hdu_fileobj = None
        header_offset = 0

        if isinstance(data, _File):
            from_file = True
            if header is None:
                header_offset = data.tell()
                header = Header.fromfile(data, endcard=not ignore_missing_end)
            hdu_fileobj = data
            data_offset = data.tell()  # *after* reading the header
        else:
            from_file = False
            try:
                # Test that the given object supports the buffer interface by
                # ensuring an ndarray can be created from it
                np.ndarray((), dtype='ubyte', buffer=data)
            except TypeError:
                raise TypeError(
                    'The provided object %r does not contain an underlying '
                    'memory buffer.  fromstring() requires an object that '
                    'supports the buffer interface such as bytes, str '
                    '(in Python 2.x but not in 3.x), buffer, memoryview, '
                    'ndarray, etc.  This restriction is to ensure that '
                    'efficient access to the array/table data is possible.'
                    % data)

            if header is None:
                def block_iter(nbytes):
                    idx = 0
                    while idx < len(data):
                        yield data[idx:idx + nbytes]
                        idx += nbytes

                header_str, header = Header._from_blocks(
                    block_iter, True, '', not ignore_missing_end, True)

                if len(data) > len(header_str):
                    hdu_buffer = data
            elif data:
                hdu_buffer = data

            header_offset = 0
            data_offset = len(header_str)

        # Determine the appropriate arguments to pass to the constructor from
        # self._kwargs.  self._kwargs contains any number of optional arguments
        # that may or may not be valid depending on the HDU type
        cls = _hdu_class_from_header(cls, header)
        args, varargs, varkwargs, defaults = getargspec(cls.__init__)
        new_kwargs = kwargs.copy()
        if not varkwargs:
            # If __init__ accepts arbitrary keyword arguments, then we can go
            # ahead and pass all keyword arguments; otherwise we need to delete
            # any that are invalid
            for key in kwargs:
                if key not in args:
                    del new_kwargs[key]

        hdu = cls(data=DELAYED, header=header, **new_kwargs)

        # One of these may be None, depending on whether the data came from a
        # file or a string buffer--later this will be further abstracted
        hdu._file = hdu_fileobj
        hdu._buffer = hdu_buffer

        hdu._header_offset = header_offset     # beginning of the header area
        hdu._data_offset = data_offset         # beginning of the data area

        # data area size, including padding
        size = hdu.size
        hdu._data_size = size + _pad_length(size)

        # Checksums are not checked on invalid HDU types
        if checksum and checksum != 'remove' and isinstance(hdu, _ValidHDU):
            hdu._verify_checksum_datasum(checksum)

        return hdu


    def _get_raw_data(self, shape, code, offset):
        """
        Return raw array from either the HDU's memory buffer or underlying
        file.
        """

        if isinstance(shape, int):
            shape = (shape,)

        if self._buffer:
            return np.ndarray(shape, dtype=code, buffer=self._buffer,
                              offset=offset)
        elif self._file:
            return self._file.readarray(offset=offset, dtype=code, shape=shape)
        else:
            return None

    # TODO: Rework checksum handling so that it's not necessary to add a
    # checksum argument here
    # TODO: The BaseHDU class shouldn't even handle checksums since they're
    # only implemented on _ValidHDU...
    def _prewriteto(self, checksum=False, inplace=False):
        self._update_uint_scale_keywords()

        # Handle checksum
        self._update_checksum(checksum)


    def _update_uint_scale_keywords(self):
        """
        If the data is unsigned int 16, 32, or 64 add BSCALE/BZERO cards to
        header.
        """

        if (self._has_data and self._standard and
                _is_pseudo_unsigned(self.data.dtype)):
            if 'GCOUNT' in self._header:
                self._header.set('BSCALE', 1, after='GCOUNT')
            else:
                self._header.set('BSCALE', 1)
            self._header.set('BZERO', _unsigned_zero(self.data.dtype),
                             after='BSCALE')

    def _update_checksum(self, checksum, checksum_keyword='CHECKSUM',
                         datasum_keyword='DATASUM'):
        """Update the 'CHECKSUM' and 'DATASUM' keywords in the header (or
        keywords with equivalent semantics given by the ``checksum_keyword``
        and ``datasum_keyword`` arguments--see for example ``CompImageHDU``
        for an example of why this might need to be overridden).
        """

        # If the data is loaded it isn't necessarily 'modified', but we have no
        # way of knowing for sure
        modified = self._header._modified or self._data_loaded

        if checksum == 'remove':
            if checksum_keyword in self._header:
                del self._header[checksum_keyword]

            if datasum_keyword in self._header:
                del self._header[datasum_keyword]
        elif (modified or self._new or
                (checksum and ('CHECKSUM' not in self._header or
                               'DATASUM' not in self._header))):
            if checksum == 'datasum':
                self.add_datasum(datasum_keyword=datasum_keyword)
            elif checksum == 'nonstandard_datasum':
                self.add_datasum(blocking='nonstandard',
                                 datasum_keyword=datasum_keyword)
            elif checksum == 'test':
                self.add_datasum(self._datasum_comment,
                                 datasum_keyword=datasum_keyword)
                self.add_checksum(self._checksum_comment, True,
                                  checksum_keyword=checksum_keyword,
                                  datasum_keyword=datasum_keyword)
            elif checksum == 'nonstandard':
                self.add_checksum(blocking='nonstandard',
                                  checksum_keyword=checksum_keyword,
                                  datasum_keyword=datasum_keyword)
            elif checksum:
                self.add_checksum(blocking='standard',
                                  checksum_keyword=checksum_keyword,
                                  datasum_keyword=datasum_keyword)

    def _postwriteto(self):
        # If data is unsigned integer 16, 32 or 64, remove the
        # BSCALE/BZERO cards
        if (self._has_data and self._standard and
                _is_pseudo_unsigned(self.data.dtype)):
            for keyword in ('BSCALE', 'BZERO'):
                with ignored(KeyError):
                    del self._header[keyword]

    def _writeheader(self, fileobj):
        offset = 0
        if not fileobj.simulateonly:
            with ignored(AttributeError, IOError):
                offset = fileobj.tell()

            self._header.tofile(fileobj)

            try:
                size = fileobj.tell() - offset
            except (AttributeError, IOError):
                size = len(str(self._header))
        else:
            size = len(str(self._header))

        return offset, size

    def _writedata(self, fileobj):
        # TODO: A lot of the simulateonly stuff should be moved back into the
        # _File class--basically it should turn write and flush into a noop
        offset = 0
        size = 0

        if not fileobj.simulateonly:
            fileobj.flush()
            try:
                offset = fileobj.tell()
            except IOError:
                offset = 0

        if self._data_loaded or self._data_needs_rescale:
            if self.data is not None:
                size += self._writedata_internal(fileobj)
            # pad the FITS data block
            if size > 0:
                padding = _pad_length(size) * self._padding_byte
                # TODO: Not that this is ever likely, but if for some odd
                # reason _padding_byte is > 0x80 this will fail; but really if
                # somebody's custom fits format is doing that, they're doing it
                # wrong and should be reprimanded harshly.
                fileobj.write(padding.encode('ascii'))
                size += len(padding)
        else:
            # The data has not been modified or does not need need to be
            # rescaled, so it can be copied, unmodified, directly from an
            # existing file or buffer
            size += self._writedata_direct_copy(fileobj)


        # flush, to make sure the content is written
        if not fileobj.simulateonly:
            fileobj.flush()

        # return both the location and the size of the data area
        return offset, size

    def _writedata_internal(self, fileobj):
        """
        The beginning and end of most _writedata() implementations are the
        same, but the details of writing the data array itself can vary between
        HDU types, so that should be implemented in this method.

        Should return the size in bytes of the data written.
        """

        if not fileobj.simulateonly:
            fileobj.writearray(self.data)
        return self.data.size * self.data.itemsize

    def _writedata_direct_copy(self, fileobj):
        """Copies the data directly from one file/buffer to the new file.

        For now this is handled by loading the raw data from the existing data
        (including any padding) via a memory map or from an already in-memory
        buffer and using Numpy's existing file-writing facilities to write to
        the new file.

        If this proves too slow a more direct approach may be used.
        """

        raw = self._get_raw_data(self._data_size, 'ubyte', self._data_offset)
        if raw is not None:
            fileobj.writearray(raw)
            return raw.nbytes
        else:
            return 0

    # TODO: This is the start of moving HDU writing out of the _File class;
    # Though right now this is an internal private method (though still used by
    # HDUList, eventually the plan is to have this be moved into writeto()
    # somehow...
    def _writeto(self, fileobj, inplace=False, copy=False):
        # For now fileobj is assumed to be a _File object
        if not inplace or self._new:
            header_offset, _ = self._writeheader(fileobj)
            data_offset, data_size = self._writedata(fileobj)

            # Set the various data location attributes on newly-written HDUs
            if self._new:
                self._header_offset = header_offset
                self._data_offset = data_offset
                self._data_size = data_size
            return

        hdrloc = self._header_offset
        hdrsize = self._data_offset - self._header_offset
        datloc = self._data_offset
        datsize = self._data_size

        if self._header._modified:
            # Seek to the original header location in the file
            self._file.seek(hdrloc)
            # This should update hdrloc with he header location in the new file
            hdrloc, hdrsize = self._writeheader(fileobj)

            # If the data is to be written below with self._writedata, that
            # will also properly update the data location; but it should be
            # updated here too
            datloc = hdrloc + hdrsize
        elif copy:
            # Seek to the original header location in the file
            self._file.seek(hdrloc)
            # Before writing, update the hdrloc with the current file position,
            # which is the hdrloc for the new file
            hdrloc = fileobj.tell()
            fileobj.write(self._file.read(hdrsize))
            # The header size is unchanged, but the data location may be
            # different from before depending on if previous HDUs were resized
            datloc = fileobj.tell()

        if self._data_loaded:
            if self.data is not None:
                # Seek through the array's bases for an memmap'd array; we
                # can't rely on the _File object to give us this info since the
                # user may have replaced the previous mmap'd array
                if copy or self._data_replaced:
                    # Of course, if we're copying the data to a new file we
                    # don't care about flushing the original mmap; instead just
                    # read it into the new file
                    array_mmap = None
                else:
                    array_mmap = _get_array_mmap(self.data)

                if array_mmap is not None:
                    array_mmap.flush()
                else:
                    self._file.seek(self._data_offset)
                    datloc, datsize = self._writedata(fileobj)
        elif copy:
            datsize = self._writedata_direct_copy(fileobj)

        self._header_offset = hdrloc
        self._data_offset = datloc
        self._data_size = datsize
        self._data_replaced = False

    def _close(self, closed=True):
        # If the data was mmap'd, close the underlying mmap (this will
        # prevent any future access to the .data attribute if there are
        # not other references to it; if there are other references then
        # it is up to the user to clean those up
        if (closed and self._data_loaded and
                _get_array_mmap(self.data) is not None):
            del self.data class _ValidHDU(_BaseHDU, _Verify):
    """
    Base class for all HDUs which are not corrupted.
    """

    def __init__(self, data=None, header=None, name=None, **kwargs):
        super(_ValidHDU, self).__init__(data=data, header=header)
        if name is not None:
            self.name = name

    @classmethod
    def match_header(cls, header):
        """
        Matches any HDU that is not recognized as having either the SIMPLE or
        XTENSION keyword in its header's first card, but is nonetheless not
        corrupted.

        TODO: Maybe it would make more sense to use _NonstandardHDU in this
        case?  Not sure...
        """

        return first(header.keys()) not in ('SIMPLE', 'XTENSION')

    @property
    def size(self):
        """
        Size (in bytes) of the data portion of the HDU.
        """

        size = 0
        naxis = self._header.get('NAXIS', 0)
        if naxis > 0:
            size = 1
            for idx in range(naxis):
                size = size * self._header['NAXIS' + str(idx + 1)]
            bitpix = self._header['BITPIX']
            gcount = self._header.get('GCOUNT', 1)
            pcount = self._header.get('PCOUNT', 0)
            size = abs(bitpix) * gcount * (pcount + size) // 8
        return size

    def filebytes(self):
        """
        Calculates and returns the number of bytes that this HDU will write to
        a file.
        """

        f = _File()
        # TODO: Fix this once new HDU writing API is settled on
        return self._writeheader(f)[1] + self._writedata(f)[1]

    def fileinfo(self):
        """
        Returns a dictionary detailing information about the locations
        of this HDU within any associated file.  The values are only
        valid after a read or write of the associated file with no
        intervening changes to the `HDUList`.

        Returns
        -------
        dict or None

           The dictionary details information about the locations of
           this HDU within an associated file.  Returns `None` when
           the HDU is not associated with a file.

           Dictionary contents:

           ========== ================================================
           Key        Value
           ========== ================================================
           file       File object associated with the HDU
           filemode   Mode in which the file was opened (readonly, copyonwrite,
                      update, append, ostream)
           hdrLoc     Starting byte location of header in file
           datLoc     Starting byte location of data block in file
           datSpan    Data size including padding
           ========== ================================================
        """

        if hasattr(self, '_file') and self._file:
            return {'file': self._file, 'filemode': self._file.mode,
                    'hdrLoc': self._header_offset, 'datLoc': self._data_offset,
                    'datSpan': self._data_size}
        else:
            return None

    def copy(self):
        """
        Make a copy of the HDU, both header and data are copied.
        """

        if self.data is not None:
            data = self.data.copy()
        else:
            data = None
        return self.__class__(data=data, header=self._header.copy())

    @deprecated('3.2', alternative='the ``.name`` attribute or `Header.set`',
                pending=True)
    def update_ext_name(self, value, comment=None, before=None,
                        after=None, savecomment=False):
        """
        Update the extension name associated with the HDU.

        If the keyword already exists in the Header, it's value and/or comment
        will be updated.  If it does not exist, a new card will be created
        and it will be placed before or after the specified location.
        If no ``before`` or ``after`` is specified, it will be appended at
        the end.

        Parameters
        ----------
        value : str
            Value to be used for the new extension name

        comment : str, optional
            To be used for updating, default=None.

        before : str or int, optional
            Name of the keyword, or index of the `Card` before which the new
            card will be placed in the Header.  The argument ``before`` takes
            precedence over ``after`` if both are specified.

        after : str or int, optional
            Name of the keyword, or index of the `Card` after which
            the new card will be placed in the Header

        savecomment : bool, optional
            When `True`, preserve the current comment for an existing
            keyword.  The argument ``savecomment`` takes precedence over
            ``comment`` if both specified.  If ``comment`` is not
            specified then the current comment will automatically be
            preserved.
        """

        if 'EXTNAME' in self._header and savecomment:
            comment = None

        self._header.set('EXTNAME', value, comment, before, after)
        # This may seem redundant, but the previous header.set call just
        # handles anyone who might use the before/after keywords to set the
        # position of the EXTNAME keyword.  Setting self.name = name does some
        # additional processing on the value such as handling
        # EXTENSION_NAME_CASE_SENSITIVE
        self.name = value

    @deprecated('3.2', alternative='the ``.ver`` attribute or `Header.set`',
                pending=True)
    def update_ext_version(self, value, comment=None, before=None,
                           after=None, savecomment=False):
        """
        Update the extension version associated with the HDU.

        If the keyword already exists in the Header, it's value and/or comment
        will be updated.  If it does not exist, a new card will be created
        and it will be placed before or after the specified location.
        If no ``before`` or ``after`` is specified, it will be appended at
        the end.

        Parameters
        ----------
        value : str
            Value to be used for the new extension version

        comment : str, optional
            To be used for updating; default=None.

        before : str or int, optional
            Name of the keyword, or index of the `Card` before which the new
            card will be placed in the Header.  The argument ``before`` takes
            precedence over ``after`` if both are specified.

        after : str or int, optional
            Name of the keyword, or index of the `Card` after which
            the new card will be placed in the Header.

        savecomment : bool, optional
            When `True`, preserve the current comment for an existing
            keyword.  The argument ``savecomment`` takes precedence over
            ``comment`` if both specified.  If ``comment`` is not
            specified then the current comment will automatically be
            preserved.
        """

        if 'EXTVER' in self._header and savecomment:
            comment = None

        self._header.set('EXTVER', value, comment, before, after)

    def _verify(self, option='warn'):
        errs = _ErrList([], unit='Card')

        is_valid = lambda v: v in [8, 16, 32, 64, -32, -64]

        # Verify location and value of mandatory keywords.
        # Do the first card here, instead of in the respective HDU classes, so
        # the checking is in order, in case of required cards in wrong order.
        if isinstance(self, ExtensionHDU):
            firstkey = 'XTENSION'
            firstval = self._extension
        else:
            firstkey = 'SIMPLE'
            firstval = True

        self.req_cards(firstkey, 0, None, firstval, option, errs)
        self.req_cards('BITPIX', 1, lambda v: (_is_int(v) and is_valid(v)), 8,
                       option, errs)
        self.req_cards('NAXIS', 2,
                       lambda v: (_is_int(v) and v >= 0 and v <= 999), 0,
                       option, errs)

        naxis = self._header.get('NAXIS', 0)
        if naxis < 1000:
            for ax in range(3, naxis + 3):
                self.req_cards('NAXIS' + str(ax - 2), ax,
                               lambda v: (_is_int(v) and v >= 0), 1, option,
                               errs)

            # Remove NAXISj cards where j is not in range 1, naxis inclusive.
            for keyword in self._header:
                if keyword.startswith('NAXIS') and len(keyword) > 5:
                    try:
                        number = int(keyword[5:])
                        if number <= 0 or number > naxis:
                            raise ValueError
                    except ValueError:
                        err_text = ("NAXISj keyword out of range ('%s' when "
                                    "NAXIS == %d)" % (keyword, naxis))

                        def fix(self=self, keyword=keyword):
                            del self._header[keyword]

                        errs.append(
                            self.run_option(option=option, err_text=err_text,
                                            fix=fix, fix_text="Deleted."))

        # Verify that the EXTNAME keyword exists and is a string
        if 'EXTNAME' in self._header:
            if not isinstance(self._header['EXTNAME'], string_types):
                err_text = 'The EXTNAME keyword must have a string value.'
                fix_text = 'Converted the EXTNAME keyword to a string value.'

                def fix(header=self._header):
                    header['EXTNAME'] = str(header['EXTNAME'])

                errs.append(self.run_option(option, err_text=err_text,
                                            fix_text=fix_text, fix=fix))

        # verify each card
        for card in self._header.cards:
            errs.append(card._verify(option))

        return errs

    # TODO: Improve this API a little bit--for one, most of these arguments
    # could be optional
    def req_cards(self, keyword, pos, test, fix_value, option, errlist):
        """
        Check the existence, location, and value of a required `Card`.

        Parameters
        ----------
        keyword : str
            The keyword to validate

        pos : int, callable
            If an ``int``, this specifies the exact location this card should
            have in the header.  Remember that Python is zero-indexed, so this
            means ``pos=0`` requires the card to be the first card in the
            header.  If given a callable, it should take one argument--the
            actual position of the keyword--and return `True` or `False`.  This
            can be used for custom evaluation.  For example if
            ``pos=lambda idx: idx > 10`` this will check that the keyword's
            index is greater than 10.

        test : callable
            This should be a callable (generally a function) that is passed the
            value of the given keyword and returns `True` or `False`.  This can
            be used to validate the value associated with the given keyword.

        fix_value : str, int, float, complex, bool, None
            A valid value for a FITS keyword to to use if the given ``test``
            fails to replace an invalid value.  In other words, this provides
            a default value to use as a replacement if the keyword's current
            value is invalid.  If `None`, there is no replacement value and the
            keyword is unfixable.

        option : str
            Output verification option.  Must be one of ``"fix"``,
            ``"silentfix"``, ``"ignore"``, ``"warn"``, or
            ``"exception"``.  May also be any combination of ``"fix"`` or
            ``"silentfix"`` with ``"+ignore"``, ``+warn``, or ``+exception"
            (e.g. ``"fix+warn"``).  See :ref:`verify` for more info.

        errlist : list
            A list of validation errors already found in the FITS file; this is
            used primarily for the validation system to collect errors across
            multiple HDUs and multiple calls to `req_cards`.

        Notes
        -----
        If ``pos=None``, the card can be anywhere in the header.  If the card
        does not exist, the new card will have the ``fix_value`` as its value
        when created.  Also check the card's value by using the ``test``
        argument.
        """

        errs = errlist
        fix = None

        try:
            index = self._header.index(keyword)
        except ValueError:
            index = None

        fixable = fix_value is not None

        insert_pos = len(self._header) + 1

        # If pos is an int, insert at the given position (and convert it to a
        # lambda)
        if _is_int(pos):
            insert_pos = pos
            pos = lambda x: x == insert_pos

        # if the card does not exist
        if index is None:
            err_text = "'%s' card does not exist." % keyword
            fix_text = "Fixed by inserting a new '%s' card." % keyword
            if fixable:
                # use repr to accommodate both string and non-string types
                # Boolean is also OK in this constructor
                card = (keyword, fix_value)

                def fix(self=self, insert_pos=insert_pos, card=card):
                    self._header.insert(insert_pos, card)

            errs.append(self.run_option(option, err_text=err_text,
                        fix_text=fix_text, fix=fix, fixable=fixable))
        else:
            # if the supposed location is specified
            if pos is not None:
                if not pos(index):
                    err_text = ("'%s' card at the wrong place (card %d)." %
                                (keyword, index))

                    fix_text = ("Fixed by moving it to the right place "
                                "(card %d)." % insert_pos)

                    def fix(self=self, index=index, insert_pos=insert_pos):
                        card = self._header.cards[index]
                        del self._header[index]
                        self._header.insert(insert_pos, card)

                    errs.append(self.run_option(option, err_text=err_text,
                                fix_text=fix_text, fix=fix))

            # if value checking is specified
            if test:
                val = self._header[keyword]
                if not test(val):
                    err_text = ("'%s' card has invalid value '%s'." %
                                (keyword, val))
                    fix_text = "Fixed by setting a new value '%s'." % fix_value

                    if fixable:
                        def fix(self=self, keyword=keyword, val=fix_value):
                            self._header[keyword] = fix_value

                    errs.append(self.run_option(option, err_text=err_text,
                                fix_text=fix_text, fix=fix, fixable=fixable))

        return errs

    def add_datasum(self, when=None, blocking='standard',
                    datasum_keyword='DATASUM'):
        """
        Add the ``DATASUM`` card to this HDU with the value set to the
        checksum calculated for the data.

        Parameters
        ----------
        when : str, optional
            Comment string for the card that by default represents the
            time when the checksum was calculated

        blocking: str, optional
            "standard" or "nonstandard", compute sum 2880 bytes at a time, or
            not

        datasum_keyword : str, optional
            The name of the header keyword to store the datasum value in;
            this is typically 'DATASUM' per convention, but there exist
            use cases in which a different keyword should be used

        Returns
        -------
        checksum : int
            The calculated datasum

        Notes
        -----
        For testing purposes, provide a ``when`` argument to enable the comment
        value in the card to remain consistent.  This will enable the
        generation of a ``CHECKSUM`` card with a consistent value.
        """

        cs = self._calculate_datasum(blocking)

        if when is None:
            when = 'data unit checksum updated %s' % self._get_timestamp()

        self._header[datasum_keyword] = (str(cs), when)
        return cs

    def add_checksum(self, when=None, override_datasum=False,
                     blocking='standard', checksum_keyword='CHECKSUM',
                     datasum_keyword='DATASUM'):
        """
        Add the ``CHECKSUM`` and ``DATASUM`` cards to this HDU with
        the values set to the checksum calculated for the HDU and the
        data respectively.  The addition of the ``DATASUM`` card may
        be overridden.

        Parameters
        ----------
        when : str, optional
           comment string for the cards; by default the comments
           will represent the time when the checksum was calculated

        override_datasum : bool, optional
           add the ``CHECKSUM`` card only

        blocking: str, optional
            "standard" or "nonstandard", compute sum 2880 bytes at a time, or
            not

        checksum_keyword : str, optional
            The name of the header keyword to store the checksum value in; this
            is typically 'CHECKSUM' per convention, but there exist use cases
            in which a different keyword should be used

        datasum_keyword : str, optional
            See ``checksum_keyword``

        Notes
        -----
        For testing purposes, first call `add_datasum` with a ``when``
        argument, then call `add_checksum` with a ``when`` argument and
        ``override_datasum`` set to `True`.  This will provide consistent
        comments for both cards and enable the generation of a ``CHECKSUM``
        card with a consistent value.
        """

        if not override_datasum:
            # Calculate and add the data checksum to the header.
            data_cs = self.add_datasum(when, blocking,
                                       datasum_keyword=datasum_keyword)
        else:
            # Just calculate the data checksum
            data_cs = self._calculate_datasum(blocking)

        if when is None:
            when = 'HDU checksum updated %s' % self._get_timestamp()

        # Add the CHECKSUM card to the header with a value of all zeros.
        if datasum_keyword in self._header:
            self._header.set(checksum_keyword, '0' * 16, when,
                             before=datasum_keyword)
        else:
            self._header.set(checksum_keyword, '0' * 16, when)

        csum = self._calculate_checksum(data_cs, blocking,
                                        checksum_keyword=checksum_keyword)
        self._header[checksum_keyword] = csum

    def verify_datasum(self, blocking='standard'):
        """
        Verify that the value in the ``DATASUM`` keyword matches the value
        calculated for the ``DATASUM`` of the current HDU data.

        blocking : str, optional
            "standard" or "nonstandard", compute sum 2880 bytes at a time, or
            not

        Returns
        -------
        valid : int
           - 0 - failure
           - 1 - success
           - 2 - no ``DATASUM`` keyword present
        """

        if 'DATASUM' in self._header:
            datasum = self._calculate_datasum(blocking)
            if datasum == int(self._header['DATASUM']):
                return 1
            elif blocking == 'either':
                # i.e. standard failed,  try nonstandard
                return self.verify_datasum(blocking='nonstandard')
            else:
                # Failed with all permitted blocking kinds
                return 0
        else:
            return 2

    def verify_checksum(self, blocking='standard'):
        """
        Verify that the value in the ``CHECKSUM`` keyword matches the
        value calculated for the current HDU CHECKSUM.

        blocking : str, optional
            "standard" or "nonstandard", compute sum 2880 bytes at a time, or
            not

        Returns
        -------
        valid : int
           - 0 - failure
           - 1 - success
           - 2 - no ``CHECKSUM`` keyword present
        """

        if 'CHECKSUM' in self._header:
            if 'DATASUM' in self._header:
                datasum = self._calculate_datasum(blocking)
            else:
                datasum = 0
            checksum = self._calculate_checksum(datasum, blocking)
            if checksum == self._header['CHECKSUM']:
                return 1
            elif blocking == 'either':
                # i.e. standard failed,  try nonstandard
                return self.verify_checksum(blocking='nonstandard')
            else:
                # Failed with all permitted blocking kinds
                return 0
        else:
            return 2

    def _verify_checksum_datasum(self, blocking):
        """
        Verify the checksum/datasum values if the cards exist in the header.
        Simply displays warnings if either the checksum or datasum don't match.
        """

        # NOTE:  private data members _checksum and _datasum are
        # used by the utility script "fitscheck" to detect missing
        # checksums.

        if 'CHECKSUM' in self._header:
            self._checksum = self._header['CHECKSUM']
            self._checksum_comment = self._header.comments['CHECKSUM']
            if not self.verify_checksum(blocking):
                warnings.warn('Checksum verification failed for HDU %s.\n' %
                              ((self.name, self.ver),))
        else:
            self._checksum = None
            self._checksum_comment = None

        if 'DATASUM' in self._header:
            self._datasum = self._header['DATASUM']
            self._datasum_comment = self._header.comments['DATASUM']

            if not self.verify_datasum(blocking):
                warnings.warn('Datasum verification failed for HDU %s.\n' %
                              ((self.name, self.ver),))
        else:
            self._checksum = None
            self._checksum_comment = None
            self._datasum = None
            self._datasum_comment = None

    def _get_timestamp(self):
        """
        Return the current timestamp in ISO 8601 format, with microseconds
        stripped off.

        Ex.: 2007-05-30T19:05:11
        """

        return datetime.datetime.now().isoformat()[:19]

    def _calculate_datasum(self, blocking):
        """
        Calculate the value for the ``DATASUM`` card in the HDU.
        """

        if not self._data_loaded:
            # This is the case where the data has not been read from the file
            # yet.  We find the data in the file, read it, and calculate the
            # datasum.
            if self.size > 0:
                raw_data = self._get_raw_data(self._data_size, 'ubyte',
                                              self._data_offset)
                return self._compute_checksum(raw_data, blocking=blocking)
            else:
                return 0
        elif self.data is not None:
            return self._compute_checksum(self.data.view('ubyte'),
                                          blocking=blocking)
        else:
            return 0

    def _calculate_checksum(self, datasum, blocking,
                            checksum_keyword='CHECKSUM'):
        """
        Calculate the value of the ``CHECKSUM`` card in the HDU.
        """

        old_checksum = self._header[checksum_keyword]
        self._header[checksum_keyword] = '0' * 16

        # Convert the header to a string.
        s = str(self._header)

        # Calculate the checksum of the Header and data.
        cs = self._compute_checksum(np.fromstring(s, dtype='ubyte'), datasum,
                                    blocking=blocking)

        # Encode the checksum into a string.
        s = self._char_encode(~cs)

        # Return the header card value.
        self._header[checksum_keyword] = old_checksum

        return s

    def _compute_checksum(self, data, sum32=0, blocking="standard"):
        """
        Compute the ones-complement checksum of a sequence of bytes.

        Parameters
        ----------
        data
            a memory region to checksum

        sum32
            incremental checksum value from another region

        blocking
            "standard", "nonstandard", or "either"
            selects the block size on which to perform checksumming,
            originally the blocksize was chosen incorrectly.  "nonstandard"
            selects the original approach,  "standard" selects the
            interoperable blocking size of 2880 bytes.  In the context of
            _compute_checksum, "either" is synonymous with "standard".

        Returns
        -------
        ones complement checksum
        """

        blocklen = {'standard': 2880,
                    'nonstandard': len(data),
                    'either': 2880,  # do standard first
                    True: 2880}[blocking]

        sum32 = np.uint32(sum32)
        for i in range(0, len(data), blocklen):
            length = min(blocklen, len(data) - i)   # ????
            sum32 = self._compute_hdu_checksum(data[i:i + length], sum32)
        return sum32

    def _compute_hdu_checksum(self, data, sum32=0):
        """
        Translated from FITS Checksum Proposal by Seaman, Pence, and Rots.
        Use uint32 literals as a hedge against type promotion to int64.

        This code should only be called with blocks of 2880 bytes
        Longer blocks result in non-standard checksums with carry overflow
        Historically,  this code *was* called with larger blocks and for that
        reason still needs to be for backward compatibility.
        """

        u8 = np.uint32(8)
        u16 = np.uint32(16)
        uFFFF = np.uint32(0xFFFF)

        if data.nbytes % 2:
            last = data[-1]
            data = data[:-1]
        else:
            last = np.uint32(0)

        data = data.view('>u2')

        hi = sum32 >> u16
        lo = sum32 & uFFFF
        hi += np.add.reduce(data[0::2], dtype=np.uint64)
        lo += np.add.reduce(data[1::2], dtype=np.uint64)

        if (data.nbytes // 2) % 2:
            lo += last << u8
        else:
            hi += last << u8

        hicarry = hi >> u16
        locarry = lo >> u16

        while hicarry or locarry:
            hi = (hi & uFFFF) + locarry
            lo = (lo & uFFFF) + hicarry
            hicarry = hi >> u16
            locarry = lo >> u16

        return (hi << u16) + lo

    # _MASK and _EXCLUDE used for encoding the checksum value into a character
    # string.
    _MASK = [0xFF000000,
             0x00FF0000,
             0x0000FF00,
             0x000000FF]

    _EXCLUDE = [0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40,
                0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60]

    def _encode_byte(self, byte):
        """
        Encode a single byte.
        """

        quotient = byte // 4 + ord('0')
        remainder = byte % 4

        ch = np.array(
            [(quotient + remainder), quotient, quotient, quotient],
            dtype='int32')

        check = True
        while check:
            check = False
            for x in self._EXCLUDE:
                for j in [0, 2]:
                    if ch[j] == x or ch[j + 1] == x:
                        ch[j] += 1
                        ch[j + 1] -= 1
                        check = True
        return ch

    def _char_encode(self, value):
        """
        Encodes the checksum ``value`` using the algorithm described
        in SPR section A.7.2 and returns it as a 16 character string.

        Parameters
        ----------
        value
            a checksum

        Returns
        -------
        ascii encoded checksum
        """

        value = np.uint32(value)

        asc = np.zeros((16,), dtype='byte')
        ascii = np.zeros((16,), dtype='byte')

        for i in range(4):
            byte = (value & self._MASK[i]) >> ((3 - i) * 8)
            ch = self._encode_byte(byte)
            for j in range(4):
                asc[4 * j + i] = ch[j]

        for i in range(16):
            ascii[i] = asc[(i + 15) % 16]

        return decode_ascii(ascii.tostring()) def fitsopen(name, mode='readonly', memmap=None, save_backup=False, **kwargs):
    """Factory function to open a FITS file and return an `HDUList` object.

    Parameters
    ----------
    name : file path, file object or file-like object
        File to be opened.

    mode : str, optional
        Open mode, 'readonly' (default), 'update', 'append', 'denywrite', or
        'ostream'.

        If ``name`` is a file object that is already opened, ``mode`` must
        match the mode the file was opened with, readonly (rb), update (rb+),
        append (ab+), ostream (w), denywrite (rb)).

    memmap : bool, optional
        Is memory mapping to be used?

    save_backup : bool, optional
        If the file was opened in update or append mode, this ensures that a
        backup of the original file is saved before any changes are flushed.
        The backup has the same name as the original file with ".bak" appended.
        If "file.bak" already exists then "file.bak.1" is used, and so on.

    kwargs : dict, optional
        additional optional keyword arguments, possible values are:

        - **uint** : bool

            Interpret signed integer data where ``BZERO`` is the
            central value and ``BSCALE == 1`` as unsigned integer
            data.  For example, ``int16`` data with ``BZERO = 32768``
            and ``BSCALE = 1`` would be treated as ``uint16`` data.
            This is enabled by default so that the pseudo-unsigned
            integer convention is assumed.

            Note, for backward compatibility, the kwarg **uint16** may
            be used instead.  The kwarg was renamed when support was
            added for integers of any size.

        - **ignore_missing_end** : bool

            Do not issue an exception when opening a file that is
            missing an ``END`` card in the last header.

        - **checksum** : bool, str

            If `True`, verifies that both ``DATASUM`` and
            ``CHECKSUM`` card values (when present in the HDU header)
            match the header and data of all HDU's in the file.  Updates to a
            file that already has a checksum will preserve and update the
            existing checksums unless this argument is given a value of
            'remove', in which case the CHECKSUM and DATASUM values are not
            checked, and are removed when saving changes to the file.

        - **disable_image_compression** : bool

            If `True`, treats compressed image HDU's like normal
            binary table HDU's.

        - **do_not_scale_image_data** : bool

            If `True`, image data is not scaled using BSCALE/BZERO values
            when read.

        - **ignore_blank** : bool
           If `True`, the BLANK keyword is ignored if present.

        - **scale_back** : bool

            If `True`, when saving changes to a file that contained scaled
            image data, restore the data to the original type and reapply the
            original BSCALE/BZERO values.  This could lead to loss of accuracy
            if scaling back to integer values after performing floating point
            operations on the data.

    Returns
    -------
        hdulist : an `HDUList` object
            `HDUList` containing all of the header data units in the
            file.

    """

    if memmap is None:
        from pyfits import USE_MEMMAP
        # distinguish between True (kwarg explicitly set)
        # and None (preference for memmap in config, might be ignored)
        memmap = None if USE_MEMMAP else False

    if 'uint16' in kwargs and 'uint' not in kwargs:
        kwargs['uint'] = kwargs['uint16']
        del kwargs['uint16']
        warnings.warn(
            'The uint16 keyword argument is deprecated since v3.4.0.  Use '
            'the uint argument instead.', PyfitsDeprecationWarning)

    if 'uint' not in kwargs:
        from pyfits import ENABLE_UINT
        kwargs['uint'] = ENABLE_UINT

    if not name:
        raise ValueError('Empty filename: %s' % repr(name))

    return HDUList.fromfile(name, mode, memmap, save_backup, **kwargs) class PrimaryHDU(_ImageBaseHDU):
    """
    FITS primary HDU class.
    """

    _default_name = 'PRIMARY'

    def __init__(self, data=None, header=None, do_not_scale_image_data=False,
                 ignore_blank=False,
                 uint=True, scale_back=None):
        """
        Construct a primary HDU.

        Parameters
        ----------
        data : array or DELAYED, optional
            The data in the HDU.

        header : Header instance, optional
            The header to be used (as a template).  If ``header`` is `None`, a
            minimal header will be provided.

        do_not_scale_image_data : bool, optional
            If `True`, image data is not scaled using BSCALE/BZERO values
            when read. (default: False)

        ignore_blank : bool, optional
            If `True`, the BLANK header keyword will be ignored if present.
            Otherwise, pixels equal to this value will be replaced with
            NaNs. (default: False)

        uint : bool, optional
            Interpret signed integer data where ``BZERO`` is the
            central value and ``BSCALE == 1`` as unsigned integer
            data.  For example, ``int16`` data with ``BZERO = 32768``
            and ``BSCALE = 1`` would be treated as ``uint16`` data.
            (default: True)

        scale_back : bool, optional
            If `True`, when saving changes to a file that contained scaled
            image data, restore the data to the original type and reapply the
            original BSCALE/BZERO values.  This could lead to loss of accuracy
            if scaling back to integer values after performing floating point
            operations on the data.  Pseudo-unsigned integers are automatically
            rescaled unless scale_back is explicitly set to `False`.
            (default: None)
        """

        super(PrimaryHDU, self).__init__(
            data=data, header=header,
            do_not_scale_image_data=do_not_scale_image_data, uint=uint,
            ignore_blank=ignore_blank,
            scale_back=scale_back)

        # insert the keywords EXTEND
        if header is None:
            dim = self._header['NAXIS']
            if dim == 0:
                dim = ''
            self._header.set('EXTEND', True, after='NAXIS' + str(dim))

    @classmethod
    def match_header(cls, header):
        card = header.cards[0]
        return (card.keyword == 'SIMPLE' and
                ('GROUPS' not in header or header['GROUPS'] != True) and
                card.value == True)

    def update_header(self):
        super(PrimaryHDU, self).update_header()

        # Update the position of the EXTEND keyword if it already exists
        if 'EXTEND' in self._header:
            if len(self._axes):
                after = 'NAXIS' + str(len(self._axes))
            else:
                after = 'NAXIS'
            self._header.set('EXTEND', after=after)

    def _verify(self, option='warn'):
        errs = super(PrimaryHDU, self)._verify(option=option)

        # Verify location and value of mandatory keywords.
        # The EXTEND keyword is only mandatory if the HDU has extensions; this
        # condition is checked by the HDUList object.  However, if we already
        # have an EXTEND keyword check that its position is correct
        if 'EXTEND' in self._header:
            naxis = self._header.get('NAXIS', 0)
            self.req_cards('EXTEND', naxis + 3, lambda v: isinstance(v, bool),
                           True, option, errs)
        return errs class ImageHDU(_ImageBaseHDU, ExtensionHDU):
    """
    FITS image extension HDU class.
    """

    _extension = 'IMAGE'

    def __init__(self, data=None, header=None, name=None,
                 do_not_scale_image_data=False, uint=True, scale_back=None):
        """
        Construct an image HDU.

        Parameters
        ----------
        data : array
            The data in the HDU.

        header : Header instance
            The header to be used (as a template).  If ``header`` is
            `None`, a minimal header will be provided.

        name : str, optional
            The name of the HDU, will be the value of the keyword
            ``EXTNAME``.

        do_not_scale_image_data : bool, optional
            If `True`, image data is not scaled using BSCALE/BZERO values
            when read. (default: False)

        uint : bool, optional
            Interpret signed integer data where ``BZERO`` is the
            central value and ``BSCALE == 1`` as unsigned integer
            data.  For example, ``int16`` data with ``BZERO = 32768``
            and ``BSCALE = 1`` would be treated as ``uint16`` data.
            (default: True)

        scale_back : bool, optional
            If `True`, when saving changes to a file that contained scaled
            image data, restore the data to the original type and reapply the
            original BSCALE/BZERO values.  This could lead to loss of accuracy
            if scaling back to integer values after performing floating point
            operations on the data.  Pseudo-unsigned integers are automatically
            rescaled unless scale_back is explicitly set to `False`.
            (default: None)
        """

        # This __init__ currently does nothing differently from the base class,
        # and is only explicitly defined for the docstring.

        super(ImageHDU, self).__init__(
            data=data, header=header, name=name,
            do_not_scale_image_data=do_not_scale_image_data, uint=uint,
            scale_back=scale_back)

    @classmethod
    def match_header(cls, header):
        card = header.cards[0]
        xtension = card.value
        if isinstance(xtension, string_types):
            xtension = xtension.rstrip()
        return card.keyword == 'XTENSION' and xtension == cls._extension

    def _verify(self, option='warn'):
        """
        ImageHDU verify method.
        """

        errs = super(ImageHDU, self)._verify(option=option)
        naxis = self._header.get('NAXIS', 0)
        # PCOUNT must == 0, GCOUNT must == 1; the former is verified in
        # ExtensionHDU._verify, however ExtensionHDU._verify allows PCOUNT
        # to be >= 0, so we need to check it here
        self.req_cards('PCOUNT', naxis + 3, lambda v: (_is_int(v) and v == 0),
                       0, option, errs)
        return errs class BinTableHDU(_TableBaseHDU):
    """
    Binary table HDU class.
    """

    _extension = 'BINTABLE'
    _ext_comment = 'binary table extension'


    @classmethod
    def match_header(cls, header):
        card = header.cards[0]
        xtension = card.value
        if isinstance(xtension, string_types):
            xtension = xtension.rstrip()
        return (card.keyword == 'XTENSION' and
                xtension in (cls._extension, 'A3DTABLE'))

    def _calculate_datasum_with_heap(self, blocking):
        """
        Calculate the value for the ``DATASUM`` card given the input data
        """

        with _binary_table_byte_swap(self.data) as data:
            dout = data.view(type=np.ndarray, dtype=np.ubyte)
            csum = self._compute_checksum(dout, blocking=blocking)

            # Now add in the heap data to the checksum (we can skip any gap
            # between the table and the heap since it's all zeros and doesn't
            # contribute to the checksum
            # TODO: The following code may no longer be necessary since it is
            # now possible to get a pointer directly to the heap data as a
            # whole.  That said, it is possible for the heap section to contain
            # data that is not actually pointed to by the table (i.e. garbage;
            # this *shouldn't* happen but it is not disallowed either)--need to
            # double check whether or not the checksum should include such
            # garbage
            for idx in range(data._nfields):
                if isinstance(data.columns._recformats[idx], _FormatP):
                    for coldata in data.field(idx):
                        # coldata should already be byteswapped from the call
                        # to _binary_table_byte_swap
                        if not len(coldata):
                            continue

                        csum = self._compute_checksum(coldata, csum,
                                                      blocking=blocking)

            return csum

    def _calculate_datasum(self, blocking):
        """
        Calculate the value for the ``DATASUM`` card in the HDU.
        """

        if self._has_data:
            # This method calculates the datasum while incorporating any
            # heap data, which is obviously not handled from the base
            # _calculate_datasum
            return self._calculate_datasum_with_heap(blocking)
        else:
            # This is the case where the data has not been read from the file
            # yet.  We can handle that in a generic manner so we do it in the
            # base class.  The other possibility is that there is no data at
            # all.  This can also be handled in a generic manner.
            return super(BinTableHDU, self)._calculate_datasum(blocking)

    def _writedata_internal(self, fileobj):
        size = 0

        if self.data is None:
            return size

        with _binary_table_byte_swap(self.data) as data:
            if _has_unicode_fields(data):
                # If the raw data was a user-supplied recarray, we can't write
                # unicode columns directly to the file, so we have to switch
                # to a slower row-by-row write
                self._writedata_by_row(fileobj)
            else:
                fileobj.writearray(data)
                # write out the heap of variable length array columns this has
                # to be done after the "regular" data is written (above)
                fileobj.write((data._gap * '\0').encode('ascii'))

            nbytes = data._gap

            if not self._manages_own_heap:
                # Write the heap data one column at a time, in the order
                # that the data pointers appear in the column (regardless
                # if that data pointer has a different, previous heap
                # offset listed)
                for idx in range(data._nfields):
                    if not isinstance(data.columns._recformats[idx],
                                      _FormatP):
                        continue

                    field = self.data.field(idx)
                    for row in field:
                        if len(row) > 0:
                            nbytes += row.nbytes
                            if not fileobj.simulateonly:
                                fileobj.writearray(row)
            else:
                heap_data = data._get_heap_data()
                if len(heap_data) > 0:
                    nbytes += len(heap_data)
                    if not fileobj.simulateonly:
                        fileobj.writearray(heap_data)

            data._heapsize = nbytes - data._gap
            size += nbytes

        size += self.data.size * self.data._raw_itemsize

        return size

    def _writedata_by_row(self, fileobj):
        fields = [self.data.field(idx)
                  for idx in range(len(self.data.columns))]

        # Creating Record objects is expensive (as in
        # `for row in self.data:` so instead we just iterate over the row
        # indicies and get one field at a time:
        for idx in range(len(self.data)):
            for field in fields:
                item = field[idx]

                if field.dtype.kind == 'U':
                    item = np.char.encode(item, 'ascii')

                fileobj.writearray(item)

    _tdump_file_format = textwrap.dedent("""

        - **datafile:** Each line of the data file represents one row of table
          data.  The data is output one column at a time in column order.  If
          a column contains an array, each element of the column array in the
          current row is output before moving on to the next column.  Each row
          ends with a new line.

          Integer data is output right-justified in a 21-character field
          followed by a blank.  Floating point data is output right justified
          using 'g' format in a 21-character field with 15 digits of
          precision, followed by a blank.  String data that does not contain
          whitespace is output left-justified in a field whose width matches
          the width specified in the ``TFORM`` header parameter for the
          column, followed by a blank.  When the string data contains
          whitespace characters, the string is enclosed in quotation marks
          (``""``).  For the last data element in a row, the trailing blank in
          the field is replaced by a new line character.

          For column data containing variable length arrays ('P' format), the
          array data is preceded by the string ``'VLA_Length= '`` and the
          integer length of the array for that row, left-justified in a
          21-character field, followed by a blank.

          .. note::

              This format does *not* support variable length arrays using the
              ('Q' format) due to difficult to overcome ambiguities. What this
              means is that this file format cannot support VLA columns in
              tables stored in files that are over 2 GB in size.

          For column data representing a bit field ('X' format), each bit
          value in the field is output right-justified in a 21-character field
          as 1 (for true) or 0 (for false).

        - **cdfile:** Each line of the column definitions file provides the
          definitions for one column in the table.  The line is broken up into
          8, sixteen-character fields.  The first field provides the column
          name (``TTYPEn``).  The second field provides the column format
          (``TFORMn``).  The third field provides the display format
          (``TDISPn``).  The fourth field provides the physical units
          (``TUNITn``).  The fifth field provides the dimensions for a
          multidimensional array (``TDIMn``).  The sixth field provides the
          value that signifies an undefined value (``TNULLn``).  The seventh
          field provides the scale factor (``TSCALn``).  The eighth field
          provides the offset value (``TZEROn``).  A field value of ``""`` is
          used to represent the case where no value is provided.

        - **hfile:** Each line of the header parameters file provides the
          definition of a single HDU header card as represented by the card
          image.
      """)

    def dump(self, datafile=None, cdfile=None, hfile=None, clobber=False):
        """
        Dump the table HDU to a file in ASCII format.  The table may be dumped
        in three separate files, one containing column definitions, one
        containing header parameters, and one for table data.

        Parameters
        ----------
        datafile : file path, file object or file-like object, optional
            Output data file.  The default is the root name of the
            fits file associated with this HDU appended with the
            extension ``.txt``.

        cdfile : file path, file object or file-like object, optional
            Output column definitions file.  The default is `None`, no
            column definitions output is produced.

        hfile : file path, file object or file-like object, optional
            Output header parameters file.  The default is `None`,
            no header parameters output is produced.

        clobber : bool
            Overwrite the output files if they exist.

        Notes
        -----
        The primary use for the `dump` method is to allow viewing and editing
        the table data and parameters in a standard text editor.
        The `load` method can be used to create a new table from the three
        plain text (ASCII) files.
        """

        # TODO: This is looking pretty long and complicated--might be a few
        # places we can break this up into smaller functions

        # check if the output files already exist
        exist = []
        files = [datafile, cdfile, hfile]

        for f in files:
            if isinstance(f, string_types):
                if os.path.exists(f) and os.path.getsize(f) != 0:
                    if clobber:
                        warnings.warn("Overwriting existing file %r." % f)
                        os.remove(f)
                    else:
                        exist.append(f)

        if exist:
            raise IOError('  '.join(["File '%s' already exists." % f
                                     for f in exist]))

        # Process the data
        self._dump_data(datafile)

        # Process the column definitions
        if cdfile:
            self._dump_coldefs(cdfile)

        # Process the header parameters
        if hfile:
            self._header.tofile(hfile, sep='\n', endcard=False, padding=False)

    if isinstance(dump.__doc__, string_types):
        dump.__doc__ += _tdump_file_format.replace('\n', '\n        ')

    def load(cls, datafile, cdfile=None, hfile=None, replace=False,
             header=None):
        """
        Create a table from the input ASCII files.  The input is from up to
        three separate files, one containing column definitions, one containing
        header parameters, and one containing column data.

        The column definition and header parameters files are not required.
        When absent the column definitions and/or header parameters are taken
        from the header object given in the header argument; otherwise sensible
        defaults are inferred (though this mode is not recommended).

        Parameters
        ----------
        datafile : file path, file object or file-like object
            Input data file containing the table data in ASCII format.

        cdfile : file path, file object, file-like object, optional
            Input column definition file containing the names,
            formats, display formats, physical units, multidimensional
            array dimensions, undefined values, scale factors, and
            offsets associated with the columns in the table.  If
            `None`, the column definitions are taken from the current
            values in this object.

        hfile : file path, file object, file-like object, optional
            Input parameter definition file containing the header
            parameter definitions to be associated with the table.  If
            `None`, the header parameter definitions are taken from
            the current values in this objects header.

        replace : bool
            When `True`, indicates that the entire header should be
            replaced with the contents of the ASCII file instead of
            just updating the current header.

        header : Header object
            When the cdfile and hfile are missing, use this Header object in
            the creation of the new table and HDU.  Otherwise this Header
            supercedes the keywords from hfile, which is only used to update
            values not present in this Header, unless ``replace=True`` in which
            this Header's values are completely replaced with the values from
            hfile.

        Notes
        -----
        The primary use for the `load` method is to allow the input of ASCII
        data that was edited in a standard text editor of the table data and
        parameters.  The `dump` method can be used to create the initial ASCII
        files.
        """

        # Process the parameter file
        if header is None:
            header = Header()

        if hfile:
            if replace:
                header = Header.fromtextfile(hfile)
            else:
                header.extend(Header.fromtextfile(hfile), update=True,
                              update_first=True)

        coldefs = None
        # Process the column definitions file
        if cdfile:
            coldefs = cls._load_coldefs(cdfile)

        # Process the data file
        data = cls._load_data(datafile, coldefs)
        if coldefs is None:
            coldefs = ColDefs(data)

        # Create a new HDU using the supplied header and data
        hdu = cls(data=data, header=header)
        hdu.columns = coldefs
        return hdu

    if isinstance(load.__doc__, string_types):
        load.__doc__ += _tdump_file_format.replace('\n', '\n        ')

    load = classmethod(load)
    # Have to create a classmethod from this here instead of as a decorator;
    # otherwise we can't update __doc__

    def _dump_data(self, fileobj):
        """
        Write the table data in the ASCII format read by BinTableHDU.load()
        to fileobj.
        """

        if not fileobj and self._file:
            root = os.path.splitext(self._file.name)[0]
            fileobj = root + '.txt'

        close_file = False

        if isinstance(fileobj, string_types):
            fileobj = open(fileobj, 'w')
            close_file = True

        linewriter = csv.writer(fileobj, dialect=FITSTableDumpDialect)

        # Process each row of the table and output one row at a time
        def format_value(val, format):
            if format[0] == 'S':
                itemsize = int(format[1:])
                return '%-*s' % (itemsize, val)
            elif format in np.typecodes['AllInteger']:
                # output integer
                return '%21d' % val
            elif format in np.typecodes['Complex']:
                return '%21.15g+%.15gj' % (val.real, val.imag)
            elif format in np.typecodes['Float']:
                # output floating point
                return '%#21.15g' % val

        for row in self.data:
            line = []   # the line for this row of the table

            # Process each column of the row.
            for column in self.columns:
                # format of data in a variable length array
                # where None means it is not a VLA:
                vla_format = None
                format = column.format.recformat

                if isinstance(format, _FormatP):
                    # P format means this is a variable length array so output
                    # the length of the array for this row and set the format
                    # for the VLA data
                    line.append('VLA_Length=')
                    line.append('%-21d' % len(row[column.name]))
                    _, dtype, option = _parse_tformat(column.format)
                    vla_format = FITS2NUMPY[option[0]][0]

                if vla_format:
                    # Output the data for each element in the array
                    for val in row[column.name].flat:
                        line.append(format_value(val, vla_format))
                else:
                    # The column data is a single element
                    dtype = self.data.dtype.fields[column.name][0]
                    array_format = dtype.char
                    if array_format == 'V':
                        array_format = dtype.base.char
                    if array_format == 'S':
                        array_format += str(dtype.itemsize)

                    if dtype.char == 'V':
                        for value in row[column.name].flat:
                            line.append(format_value(value, array_format))
                    else:
                        line.append(format_value(row[column.name],
                                    array_format))
            linewriter.writerow(line)
        if close_file:
            fileobj.close()

    def _dump_coldefs(self, fileobj):
        """
        Write the column definition parameters in the ASCII format read by
        BinTableHDU.load() to fileobj.
        """

        close_file = False

        if isinstance(fileobj, string_types):
            fileobj = open(fileobj, 'w')
            close_file = True

        # Process each column of the table and output the result to the
        # file one at a time
        for column in self.columns:
            line = [column.name, column.format]
            attrs = ['disp', 'unit', 'dim', 'null', 'bscale', 'bzero']
            line += ['%-16s' % (value if value else '""')
                     for value in (getattr(column, attr) for attr in attrs)]
            fileobj.write(' '.join(line))
            fileobj.write('\n')

        if close_file:
            fileobj.close()

    @classmethod
    def _load_data(cls, fileobj, coldefs=None):
        """
        Read the table data from the ASCII file output by BinTableHDU.dump().
        """

        close_file = False

        if isinstance(fileobj, string_types):
            fileobj = open(fileobj, 'r')
            close_file = True

        initialpos = fileobj.tell()  # We'll be returning here later
        linereader = csv.reader(fileobj, dialect=FITSTableDumpDialect)

        # First we need to do some preprocessing on the file to find out how
        # much memory we'll need to reserve for the table.  This is necessary
        # even if we already have the coldefs in order to determine how many
        # rows to reserve memory for
        vla_lengths = []
        recformats = []
        names = []
        nrows = 0
        if coldefs is not None:
            recformats = coldefs._recformats
            names = coldefs.names

        def update_recformats(value, idx):
            fitsformat = _scalar_to_format(value)
            recformat = _convert_format(fitsformat)
            if idx >= len(recformats):
                recformats.append(recformat)
            else:
                if _cmp_recformats(recformats[idx], recformat) < 0:
                    recformats[idx] = recformat

        # TODO: The handling of VLAs could probably be simplified a bit
        for row in linereader:
            nrows += 1
            if coldefs is not None:
                continue
            col = 0
            idx = 0
            while idx < len(row):
                if row[idx] == 'VLA_Length=':
                    if col < len(vla_lengths):
                        vla_length = vla_lengths[col]
                    else:
                        vla_length = int(row[idx + 1])
                        vla_lengths.append(vla_length)
                    idx += 2
                    while vla_length:
                        update_recformats(row[idx], col)
                        vla_length -= 1
                        idx += 1
                    col += 1
                else:
                    if col >= len(vla_lengths):
                        vla_lengths.append(None)
                    update_recformats(row[idx], col)
                    col += 1
                    idx += 1

        # Update the recformats for any VLAs
        for idx, length in enumerate(vla_lengths):
            if length is not None:
                recformats[idx] = str(length) + recformats[idx]

        dtype = np.rec.format_parser(recformats, names, None).dtype

        # TODO: In the future maybe enable loading a bit at a time so that we
        # can convert from this format to an actual FITS file on disk without
        # needing enough physical memory to hold the entire thing at once;
        # new_table() could use a similar feature.
        hdu = BinTableHDU.from_columns(np.recarray(shape=1, dtype=dtype),
                                       nrows=nrows, fill=True)

        # TODO: It seems to me a lot of this could/should be handled from
        # within the FITS_rec class rather than here.
        data = hdu.data
        for idx, length in enumerate(vla_lengths):
            if length is not None:
                arr = data.columns._arrays[idx]
                dt = recformats[idx][len(str(length)):]

                # NOTE: FormatQ not supported here; it's hard to determine
                # whether or not it will be necessary to use a wider descriptor
                # type. The function documentation will have to serve as a
                # warning that this is not supported.
                recformats[idx] = _FormatP(dt, max=length)
                data.columns._recformats[idx] = recformats[idx]
                name = data.columns.names[idx]
                data._cache_field(name, _makep(arr, arr, recformats[idx]))

        def format_value(col, val):
            # Special formatting for a couple particular data types
            if recformats[col] == FITS2NUMPY['L']:
                return bool(int(val))
            elif recformats[col] == FITS2NUMPY['M']:
                # For some reason, in arrays/fields where numpy expects a
                # complex it's not happy to take a string representation
                # (though it's happy to do that in other contexts), so we have
                # to convert the string representation for it:
                return complex(val)
            else:
                return val

        # Jump back to the start of the data and create a new line reader
        fileobj.seek(initialpos)
        linereader = csv.reader(fileobj, dialect=FITSTableDumpDialect)
        for row, line in enumerate(linereader):
            col = 0
            idx = 0
            while idx < len(line):
                if line[idx] == 'VLA_Length=':
                    vla_len = vla_lengths[col]
                    idx += 2
                    slice_ = slice(idx, idx + vla_len)
                    data[row][col][:] = line[idx:idx + vla_len]
                    idx += vla_len
                elif dtype[col].shape:
                    # This is an array column
                    array_size = int(np.multiply.reduce(dtype[col].shape))
                    slice_ = slice(idx, idx + array_size)
                    idx += array_size
                else:
                    slice_ = None

                if slice_ is None:
                    # This is a scalar row element
                    data[row][col] = format_value(col, line[idx])
                    idx += 1
                else:
                    data[row][col].flat[:] = [format_value(col, val)
                                              for val in line[slice_]]

                col += 1

        if close_file:
            fileobj.close()

        return data

    @classmethod
    def _load_coldefs(cls, fileobj):
        """
        Read the table column definitions from the ASCII file output by
        BinTableHDU.dump().
        """

        close_file = False

        if isinstance(fileobj, string_types):
            fileobj = open(fileobj, 'r')
            close_file = True

        columns = []

        for line in fileobj:
            words = line[:-1].split()
            kwargs = {}
            for key in ['name', 'format', 'disp', 'unit', 'dim']:
                kwargs[key] = words.pop(0).replace('""', '')

            for key in ['null', 'bscale', 'bzero']:
                word = words.pop(0).replace('""', '')
                if word:
                    word = _str_to_num(word)
                kwargs[key] = word
            columns.append(Column(**kwargs))

        if close_file:
            fileobj.close()

        return ColDefs(columns) class Header(object):
    """
    FITS header class.  This class exposes both a dict-like interface and a
    list-like interface to FITS headers.

    The header may be indexed by keyword and, like a dict, the associated value
    will be returned.  When the header contains cards with duplicate keywords,
    only the value of the first card with the given keyword will be returned.
    It is also possible to use a 2-tuple as the index in the form (keyword,
    n)--this returns the n-th value with that keyword, in the case where there
    are duplicate keywords.

    For example::

        >>> header['NAXIS']
        0
        >>> header[('FOO', 1)]  # Return the value of the second FOO keyword
        'foo'

    The header may also be indexed by card number::

        >>> header[0]  # Return the value of the first card in the header
        'T'

    Commentary keywords such as HISTORY and COMMENT are special cases: When
    indexing the Header object with either 'HISTORY' or 'COMMENT' a list of all
    the HISTORY/COMMENT values is returned::

        >>> header['HISTORY']
        This is the first history entry in this header.
        This is the second history entry in this header.
        ...

    See the PyFITS documentation for more details on working with headers.
    """

    def __init__(self, cards=[]):
        """
        Construct a `Header` from an iterable and/or text file.

        Parameters
        ----------
        cards : A list of `Card` objects, optional
            The cards to initialize the header with.
        """

        self.clear()

        if isinstance(cards, Header):
            cards = cards.cards

        for card in cards:
            self.append(card, end=True)

        self._modified = False

    def __len__(self):
        return len(self._cards)

    def __iter__(self):
        for card in self._cards:
            yield card.keyword

    def __contains__(self, keyword):
        if keyword in self._keyword_indices or keyword in self._rvkc_indices:
            # For the most common case (single, standard form keyword lookup)
            # this will work and is an O(1) check.  If it fails that doesn't
            # guarantee absence, just that we have to perform the full set of
            # checks in self._cardindex
            return True
        try:
            self._cardindex(keyword)
        except (KeyError, IndexError):
            return False
        return True

    def __getitem__(self, key):
        if isinstance(key, slice):
            return Header([copy.copy(c) for c in self._cards[key]])
        elif self._haswildcard(key):
            return Header([copy.copy(self._cards[idx])
                           for idx in self._wildcardmatch(key)])
        elif (isinstance(key, string_types) and
              key.upper() in Card._commentary_keywords):
            key = key.upper()
            # Special case for commentary cards
            return _HeaderCommentaryCards(self, key)
        if isinstance(key, tuple):
            keyword = key[0]
        else:
            keyword = key
        card = self._cards[self._cardindex(key)]
        if card.field_specifier is not None and keyword == card.rawkeyword:
            # This is RVKC; if only the top-level keyword was specified return
            # the raw value, not the parsed out float value
            return card.rawvalue
        return card.value

    def __setitem__(self, key, value):
        if self._set_slice(key, value, self):
            return

        if isinstance(value, tuple):
            if not (0 < len(value) <= 2):
                raise ValueError(
                    'A Header item may be set with either a scalar value, '
                    'a 1-tuple containing a scalar value, or a 2-tuple '
                    'containing a scalar value and comment string.')
            if len(value) == 1:
                value, comment = value[0], None
                if value is None:
                    value = ''
            elif len(value) == 2:
                value, comment = value
                if value is None:
                    value = ''
                if comment is None:
                    comment = ''
        else:
            comment = None

        card = None
        if isinstance(key, int):
            card = self._cards[key]
        elif isinstance(key, tuple):
            card = self._cards[self._cardindex(key)]
        if card:
            card.value = value
            if comment is not None:
                card.comment = comment
            if card._modified:
                self._modified = True
        else:
            # If we get an IndexError that should be raised; we don't allow
            # assignment to non-existing indices
            self._update((key, value, comment))

    def __delitem__(self, key):
        if isinstance(key, slice) or self._haswildcard(key):
            # This is very inefficient but it's not a commonly used feature.
            # If someone out there complains that they make heavy use of slice
            # deletions and it's too slow, well, we can worry about it then
            # [the solution is not too complicated--it would be wait 'til all
            # the cards are deleted before updating _keyword_indices rather
            # than updating it once for each card that gets deleted]
            if isinstance(key, slice):
                indices = range(*key.indices(len(self)))
                # If the slice step is backwards we want to reverse it, because
                # it will be reversed in a few lines...
                if key.step and key.step < 0:
                    indices = reversed(indices)
            else:
                indices = self._wildcardmatch(key)
            for idx in reversed(indices):
                del self[idx]
            return
        elif isinstance(key, string_types):
            # delete ALL cards with the same keyword name
            key = Card.normalize_keyword(key)
            indices = self._keyword_indices
            if key not in self._keyword_indices:
                indices = self._rvkc_indices

            if key not in indices:
                raise KeyError("Keyword '%s' not found." % key)

            for idx in reversed(indices[key]):
                # Have to copy the indices list since it will be modified below
                del self[idx]
            return

        idx = self._cardindex(key)
        card = self._cards[idx]
        keyword = card.keyword
        del self._cards[idx]
        indices = self._keyword_indices[keyword]
        indices.remove(idx)
        if not indices:
            del self._keyword_indices[keyword]

        # Also update RVKC indices if necessary :/
        if card.field_specifier is not None:
            indices = self._rvkc_indices[card.rawkeyword]
            indices.remove(idx)
            if not indices:
                del self._rvkc_indices[card.rawkeyword]


        # We also need to update all other indices
        self._updateindices(idx, increment=False)
        self._modified = True

    def __repr__(self):
        return self.tostring(sep='\n', endcard=False, padding=False)

    def __str__(self):
        return self.tostring()

    def __eq__(self, other):
        """
        Two Headers are equal only if they have the exact same string
        representation.
        """

        return str(self) == str(other)

    def __add__(self, other):
        temp = self.copy(strip=False)
        temp.extend(other)
        return temp

    def __iadd__(self, other):
        self.extend(other)
        return self

    @property
    def cards(self):
        """
        The underlying physical cards that make up this Header; it can be
        looked at, but it should not be modified directly.
        """

        return _CardAccessor(self)

    @property
    def comments(self):
        """
        View the comments associated with each keyword, if any.

        For example, to see the comment on the NAXIS keyword:

            >>> header.comments['NAXIS']
            number of data axes

        Comments can also be updated through this interface:

            >>> header.comments['NAXIS'] = 'Number of data axes'

        """

        return _HeaderComments(self)

    @property
    def _modified(self):
        """
        Whether or not the header has been modified; this is a property so that
        it can also check each card for modifications--cards may have been
        modified directly without the header containing it otherwise knowing.
        """

        modified_cards = any(c._modified for c in self._cards)
        if modified_cards:
            # If any cards were modified then by definition the header was
            # modified
            self.__dict__['_modified'] = True

        return self.__dict__['_modified']

    @_modified.setter
    def _modified(self, val):
        self.__dict__['_modified'] = val

    @classmethod
    def fromstring(cls, data, sep=''):
        """
        Creates an HDU header from a byte string containing the entire header
        data.

        Parameters
        ----------
        data : str
           String containing the entire header.

        sep : str, optional
            The string separating cards from each other, such as a newline.  By
            default there is no card separator (as is the case in a raw FITS
            file).

        Returns
        -------
        header
            A new `Header` instance.
        """

        cards = []

        # If the card separator contains characters that may validly appear in
        # a card, the only way to unambiguously distinguish between cards is to
        # require that they be Card.length long.  However, if the separator
        # contains non-valid characters (namely \n) the cards may be split
        # immediately at the separator
        require_full_cardlength = set(sep).issubset(VALID_HEADER_CHARS)

        # Split the header into individual cards
        idx = 0
        image = []

        while idx < len(data):
            if require_full_cardlength:
                end_idx = idx + Card.length
            else:
                try:
                    end_idx = data.index(sep, idx)
                except ValueError:
                    end_idx = len(data)

            next_image = data[idx:end_idx]
            idx = end_idx + len(sep)

            if image:
                if next_image[:8] == 'CONTINUE':
                    image.append(next_image)
                    continue
                cards.append(Card.fromstring(''.join(image)))

            if require_full_cardlength:
                if next_image == END_CARD:
                    image = []
                    break
            else:
                if next_image.split(sep)[0].rstrip() == 'END':
                    image = []
                    break

            image = [next_image]

        # Add the last image that was found before the end, if any
        if image:
            cards.append(Card.fromstring(''.join(image)))

        return cls(cards)

    @classmethod
    def fromfile(cls, fileobj, sep='', endcard=True, padding=True):
        """
        Similar to :meth:`Header.fromstring`, but reads the header string from
        a given file-like object or filename.

        Parameters
        ----------
        fileobj : str, file-like
            A filename or an open file-like object from which a FITS header is
            to be read.  For open file handles the file pointer must be at the
            beginning of the header.

        sep : str, optional
            The string separating cards from each other, such as a newline.  By
            default there is no card separator (as is the case in a raw FITS
            file).

        endcard : bool, optional
            If True (the default) the header must end with an END card in order
            to be considered valid.  If an END card is not found an
            `~.exceptions.IOError` is raised.

        padding : bool, optional
            If True (the default) the header will be required to be padded out
            to a multiple of 2880, the FITS header block size.  Otherwise any
            padding, or lack thereof, is ignored.

        Returns
        -------
        header
            A new `Header` instance.
        """

        close_file = False
        if isinstance(fileobj, string_types):
            # Open in text mode by default to support newline handling; if a
            # binary-mode file object is passed in, the user is on their own
            # with respect to newline handling
            fileobj = open(fileobj, 'r')
            close_file = True

        try:
            is_binary = fileobj_is_binary(fileobj)

            def block_iter(nbytes):
                while True:
                    data = fileobj.read(nbytes)

                    if data:
                        yield data
                    else:
                        break

            return cls._from_blocks(block_iter, is_binary, sep, endcard,
                                    padding)[1]
        finally:
            if close_file:
                fileobj.close()

    @classmethod
    def _from_blocks(cls, block_iter, is_binary, sep, endcard, padding):
        """
        The meat of `Header.fromfile`; in a separate method so that
        `Header.fromfile` itself is just responsible for wrapping file
        handling.  Also used by `_BaseHDU.fromstring`.

        ``block_iter`` should be a callable which, given a block size n
        (typically 2880 bytes as used by the FITS standard) returns an iterator
        of byte strings of that block size.

        ``is_binary`` specifies whether the returned blocks are bytes or text

        Returns both the entire header *string*, and the `Header` object
        returned by Header.fromstring on that string.
        """

        actual_block_size = _block_size(sep)
        clen = Card.length + len(sep)

        blocks = block_iter(actual_block_size)

        # Read the first header block.
        try:
            block = next(blocks)
        except StopIteration:
            raise EOFError()

        if not is_binary:
            # TODO: There needs to be error handling at *this* level for
            # non-ASCII characters; maybe at this stage decoding latin-1 might
            # be safer
            block = encode_ascii(block)

        read_blocks = []
        is_eof = False
        end_found = False

        # continue reading header blocks until END card or EOF is reached
        while True:
            # find the END card
            end_found, block = cls._find_end_card(block, clen)

            read_blocks.append(decode_ascii(block))

            if end_found:
                break

            try:
                block = next(blocks)
            except StopIteration:
                is_eof = True
                break

            if not block:
                is_eof = True
                break

            if not is_binary:
                block = encode_ascii(block)

        if not end_found and is_eof and endcard:
            # TODO: Pass this error to validation framework as an ERROR,
            # rather than raising an exception
            raise IOError('Header missing END card.')

        header_str = ''.join(read_blocks)

        # Strip any zero-padding (see ticket #106)
        if header_str and header_str[-1] == '\0':
            if is_eof and header_str.strip('\0') == '':
                # TODO: Pass this warning to validation framework
                warnings.warn(
                    'Unexpected extra padding at the end of the file.  This '
                    'padding may not be preserved when saving changes.')
                raise EOFError()
            else:
                # Replace the illegal null bytes with spaces as required by
                # the FITS standard, and issue a nasty warning
                # TODO: Pass this warning to validation framework
                warnings.warn(
                    'Header block contains null bytes instead of spaces for '
                    'padding, and is not FITS-compliant. Nulls may be '
                    'replaced with spaces upon writing.')
                header_str.replace('\0', ' ')

        if padding and (len(header_str) % actual_block_size) != 0:
            # This error message ignores the length of the separator for
            # now, but maybe it shouldn't?
            actual_len = len(header_str) - actual_block_size + BLOCK_SIZE
            # TODO: Pass this error to validation framework
            raise ValueError('Header size is not multiple of %d: %d'
                             % (BLOCK_SIZE, actual_len))

        return header_str, cls.fromstring(header_str, sep=sep)

    @classmethod
    def _find_end_card(cls, block, card_len):
        """
        Utility method to search a header block for the END card and handle
        invalid END cards.

        This method can also returned a modified copy of the input header block
        in case an invalid end card needs to be sanitized.
        """

        for mo in HEADER_END_RE.finditer(block):
            # Ensure the END card was found, and it started on the
            # boundary of a new card (see ticket #142)
            if mo.start() % card_len != 0:
                continue

            # This must be the last header block, otherwise the
            # file is malformatted
            if mo.group('invalid'):
                offset = mo.start()
                trailing = block[offset + 3:offset + card_len - 3].rstrip()
                if trailing:
                    trailing = repr(trailing).lstrip('ub')
                    # TODO: Pass this warning up to the validation framework
                    warnings.warn(
                        'Unexpected bytes trailing END keyword: %s; these '
                        'bytes will be replaced with spaces on write.' %
                        trailing)
                else:
                    # TODO: Pass this warning up to the validation framework
                    warnings.warn(
                        'Missing padding to end of the FITS block after the '
                        'END keyword; additional spaces will be appended to '
                        'the file upon writing to pad out to %d bytes.' %
                        BLOCK_SIZE)

                # Sanitize out invalid END card now that the appropriate
                # warnings have been issued
                block = (block[:offset] + encode_ascii(END_CARD) +
                         block[offset + len(END_CARD):])

            return True, block

        return False, block

    def tostring(self, sep='', endcard=True, padding=True):
        r"""
        Returns a string representation of the header.

        By default this uses no separator between cards, adds the END card, and
        pads the string with spaces to the next multiple of 2880 bytes.  That
        is, it returns the header exactly as it would appear in a FITS file.

        Parameters
        ----------
        sep : str, optional
            The character or string with which to separate cards.  By default
            there is no separator, but one could use ``'\\n'``, for example, to
            separate each card with a new line

        endcard : bool, optional
            If True (default) adds the END card to the end of the header
            string

        padding : bool, optional
            If True (default) pads the string with spaces out to the next
            multiple of 2880 characters

        Returns
        -------
        s : string
            A string representing a FITS header.
        """

        lines = []
        for card in self._cards:
            s = str(card)
            # Cards with CONTINUE cards may be longer than 80 chars; so break
            # them into multiple lines
            while s:
                lines.append(s[:Card.length])
                s = s[Card.length:]

        s = sep.join(lines)
        if endcard:
            s += sep + _pad('END')
        if padding:
            s += ' ' * _pad_length(len(s))
        return s

    def tofile(self, fileobj, sep='', endcard=True, padding=True,
               clobber=False):
        r"""
        Writes the header to file or file-like object.

        By default this writes the header exactly as it would be written to a
        FITS file, with the END card included and padding to the next multiple
        of 2880 bytes.  However, aspects of this may be controlled.

        Parameters
        ----------
        fileobj : str, file, optional
            Either the pathname of a file, or an open file handle or file-like
            object

        sep : str, optional
            The character or string with which to separate cards.  By default
            there is no separator, but one could use ``'\\n'``, for example, to
            separate each card with a new line

        endcard : bool, optional
            If `True` (default) adds the END card to the end of the header
            string

        padding : bool, optional
            If `True` (default) pads the string with spaces out to the next
            multiple of 2880 characters

        clobber : bool, optional
            If `True`, overwrites the output file if it already exists
        """

        close_file = fileobj_closed(fileobj)

        if not isinstance(fileobj, _File):
            fileobj = _File(fileobj, mode='ostream', clobber=clobber)

        try:
            blocks = self.tostring(sep=sep, endcard=endcard, padding=padding)
            actual_block_size = _block_size(sep)
            if padding and len(blocks) % actual_block_size != 0:
                raise IOError('Header size (%d) is not a multiple of block '
                              'size (%d).' %
                              (len(blocks) - actual_block_size + BLOCK_SIZE,
                               BLOCK_SIZE))

            if not fileobj.simulateonly:
                fileobj.flush()
                try:
                    offset = fileobj.tell()
                except (AttributeError, IOError):
                    offset = 0
                fileobj.write(blocks.encode('ascii'))
                fileobj.flush()
        finally:
            if close_file:
                fileobj.close()

    @classmethod
    def fromtextfile(cls, fileobj, endcard=False):
        """
        Equivalent to::

            >>> Header.fromfile(fileobj, sep='\\n', endcard=False,
            ...                 padding=False)
        """

        return cls.fromfile(fileobj, sep='\n', endcard=endcard, padding=False)

    def totextfile(self, fileobj, endcard=False, clobber=False):
        """
        Equivalent to::

            >>> Header.tofile(fileobj, sep='\\n', endcard=False,
            ...               padding=False, clobber=clobber)
        """

        self.tofile(fileobj, sep='\n', endcard=endcard, padding=False,
                    clobber=clobber)

    def clear(self):
        """
        Remove all cards from the header.
        """

        self._cards = []
        self._keyword_indices = defaultdict(list)
        self._rvkc_indices = defaultdict(list)

    def copy(self, strip=False):
        """
        Make a copy of the :class:`Header`.

        Parameters
        ----------
        strip : bool, optional
           If `True`, strip any headers that are specific to one of the
           standard HDU types, so that this header can be used in a different
           HDU.

        Returns
        -------
        header
            A new :class:`Header` instance.
        """

        tmp = Header([copy.copy(card) for card in self._cards])
        if strip:
            tmp._strip()
        return tmp

    @classmethod
    def fromkeys(cls, iterable, value=None):
        """
        Similar to :meth:`dict.fromkeys`--creates a new `Header` from an
        iterable of keywords and an optional default value.

        This method is not likely to be particularly useful for creating real
        world FITS headers, but it is useful for testing.

        Parameters
        ----------
        iterable
            Any iterable that returns strings representing FITS keywords.

        value : optional
            A default value to assign to each keyword; must be a valid type for
            FITS keywords.

        Returns
        -------
        header
            A new `Header` instance.
        """

        d = cls()
        if not isinstance(value, tuple):
            value = (value,)
        for key in iterable:
            d.append((key,) + value)
        return d

    def get(self, key, default=None):
        """
        Similar to :meth:`dict.get`--returns the value associated with keyword
        in the header, or a default value if the keyword is not found.

        Parameters
        ----------
        key : str
            A keyword that may or may not be in the header.

        default : optional
            A default value to return if the keyword is not found in the
            header.

        Returns
        -------
        value
            The value associated with the given keyword, or the default value
            if the keyword is not in the header.
        """

        try:
            return self[key]
        except (KeyError, IndexError):
            return default

    def set(self, keyword, value=None, comment=None, before=None, after=None,
            savecomment=False):
        """
        Set the value and/or comment and/or position of a specified keyword.

        If the keyword does not already exist in the header, a new keyword is
        created in the specified position, or appended to the end of the header
        if no position is specified.

        This method is similar to :meth:`Header.update` prior to PyFITS 3.1.

        .. note::
            It should be noted that ``header.set(keyword, value)`` and
            ``header.set(keyword, value, comment)`` are equivalent to
            ``header[keyword] = value`` and
            ``header[keyword] = (value, comment)`` respectively.

            New keywords can also be inserted relative to existing keywords
            using, for example::

                >>> header.insert('NAXIS1', ('NAXIS', 2, 'Number of axes'))

            to insert before an existing keyword, or::

                >>> header.insert('NAXIS', ('NAXIS1', 4096), after=True)

            to insert after an existing keyword.

            The only advantage of using :meth:`Header.set` is that it
            easily replaces the old usage of :meth:`Header.update` both
            conceptually and in terms of function signature.

        Parameters
        ----------
        keyword : str
            A header keyword

        value : str, optional
            The value to set for the given keyword; if None the existing value
            is kept, but '' may be used to set a blank value

        comment : str, optional
            The comment to set for the given keyword; if None the existing
            comment is kept, but ``''`` may be used to set a blank comment

        before : str, int, optional
            Name of the keyword, or index of the `Card` before which this card
            should be located in the header.  The argument ``before`` takes
            precedence over ``after`` if both specified.

        after : str, int, optional
            Name of the keyword, or index of the `Card` after which this card
            should be located in the header.

        savecomment : bool, optional
            This option is provided primarily for compatibilty with the old
            `Header.update` (which worked differently before PyFITS v3.4).
            If `True`, this causes the existing comment for the specified
            keyword (if it exists) to be preserved even if a different comment
            is provided via the ``comment`` argument.  In other words, it
            causes the ``comment`` argument to be ignored (but *only* if there
            is already a comment).
        """

        # Create a temporary card that looks like the one being set; if the
        # temporary card turns out to be a RVKC this will make it easier to
        # deal with the idiosyncrasies thereof
        # Don't try to make a temporary card though if they keyword looks like
        # it might be a HIERARCH card or is otherwise invalid--this step is
        # only for validating RVKCs.
        if (len(keyword) <= KEYWORD_LENGTH and
            Card._keywd_FSC_RE.match(keyword) and
                keyword not in self._keyword_indices):
            new_card = Card(keyword, value, comment)
            new_keyword = new_card.keyword
        else:
            new_keyword = keyword

        if (new_keyword not in Card._commentary_keywords and
                new_keyword in self):
            if comment is None or savecomment:
                comment = self.comments[keyword]
            if value is None:
                value = self[keyword]

            self[keyword] = (value, comment)

            if before is not None or after is not None:
                card = self._cards[self._cardindex(keyword)]
                self._relativeinsert(card, before=before, after=after,
                                     replace=True)
        elif before is not None or after is not None:
            self._relativeinsert((keyword, value, comment), before=before,
                                 after=after)
        else:
            self[keyword] = (value, comment)

    def items(self):
        """Like :meth:`dict.items`."""

        return list(self.iteritems())

    def iteritems(self):
        """Like :meth:`dict.iteritems`."""

        for card in self._cards:
            yield (card.keyword, card.value)

    def iterkeys(self):
        """
        Like :meth:`dict.iterkeys`--iterating directly over the `Header`
        instance has the same behavior.
        """

        return self.__iter__()

    def itervalues(self):
        """Like :meth:`dict.itervalues`."""

        for _, v in iteritems(self):
            yield v

    def keys(self):
        """
        Return a list of keywords in the header in the order they
        appear--like :meth:`dict.keys` but ordered.
        """

        return [keyword for keyword in self]

    def pop(self, *args):
        """
        Works like :meth:`list.pop` if no arguments or an index argument are
        supplied; otherwise works like :meth:`dict.pop`.
        """

        if len(args) > 2:
            raise TypeError('Header.pop expected at most 2 arguments, got '
                            '%d' % len(args))

        if len(args) == 0:
            key = -1
        else:
            key = args[0]

        try:
            value = self[key]
        except (KeyError, IndexError):
            if len(args) == 2:
                return args[1]
            raise

        del self[key]
        return value

    def popitem(self):
        """Similar to :meth:`dict.popitem`."""

        try:
            k, v = next(iteritems(self))
        except StopIteration:
            raise KeyError('Header is empty')
        del self[k]
        return k, v

    def setdefault(self, key, default=None):
        """Similar to :meth:`dict.setdefault`."""

        try:
            return self[key]
        except (KeyError, IndexError):
            self[key] = default
        return default

    def update(self, *args, **kwargs):
        """
        Update the Header with new keyword values, updating the values of
        existing keywords and appending new keywords otherwise; similar to
        `dict.update`.

        `update` accepts either a dict-like object or an iterable.  In the
        former case the keys must be header keywords and the values may be
        either scalar values or (value, comment) tuples.  In the case of an
        iterable the items must be (keyword, value) tuples or (keyword, value,
        comment) tuples.

        Arbitrary arguments are also accepted, in which case the update() is
        called again with the kwargs dict as its only argument.  That is,

        ::

            >>> header.update(NAXIS1=100, NAXIS2=100)

        is equivalent to::

            >>> header.update({'NAXIS1': 100, 'NAXIS2': 100})
        """

        if args:
            other = args[0]
        else:
            other = None

        def update_from_dict(k, v):
            if not isinstance(v, tuple):
                card = Card(k, v)
            elif 0 < len(v) <= 2:
                card = Card(*((k,) + v))
            else:
                raise ValueError(
                    'Header update value for key %r is invalid; the value '
                    'must be either a scalar, a 1-tuple containing the '
                    'scalar value, or a 2-tuple containing the value and a '
                    'comment string.' % k)
            self._update(card)

        if other is None:
            pass
        elif hasattr(other, 'iteritems'):
            for k, v in other.iteritems():
                update_from_dict(k, v)
        elif hasattr(other, 'keys'):
            for k in other.keys():
                update_from_dict(k, other[k])
        else:
            for idx, card in enumerate(other):
                if isinstance(card, Card):
                    self._update(card)
                elif isinstance(card, tuple) and (1 < len(card) <= 3):
                    self._update(Card(*card))
                else:
                    raise ValueError(
                        'Header update sequence item #%d is invalid; the '
                        'item must either be a 2-tuple containing a keyword '
                        'and value, or a 3-tuple containing a keyword, '
                        'value, and comment string.' % idx)
        if kwargs:
            self.update(kwargs)

    def values(self):
        """Returns a list of the values of all cards in the header."""

        return [v for _, v in iteritems(self)]

    def append(self, card=None, useblanks=True, bottom=False, end=False):
        """
        Appends a new keyword+value card to the end of the Header, similar
        to `list.append`.

        By default if the last cards in the Header have commentary keywords,
        this will append the new keyword before the commentary (unless the new
        keyword is also commentary).

        Also differs from `list.append` in that it can be called with no
        arguments: In this case a blank card is appended to the end of the
        Header.  In the case all the keyword arguments are ignored.

        Parameters
        ----------
        card : str, tuple
            A keyword or a (keyword, value, [comment]) tuple representing a
            single header card; the comment is optional in which case a
            2-tuple may be used

        useblanks : bool, optional
            If there are blank cards at the end of the Header, replace the
            first blank card so that the total number of cards in the Header
            does not increase.  Otherwise preserve the number of blank cards.

        bottom : bool, optional
            If True, instead of appending after the last non-commentary card,
            append after the last non-blank card.

        end : bool, optional
            If True, ignore the useblanks and bottom options, and append at the
            very end of the Header.

        """

        if isinstance(card, string_types):
            card = Card(card)
        elif isinstance(card, tuple):
            card = Card(*card)
        elif card is None:
            card = Card()
        elif not isinstance(card, Card):
            raise ValueError(
                'The value appended to a Header must be either a keyword or '
                '(keyword, value, [comment]) tuple; got: %r' % card)

        if not end and card.is_blank:
            # Blank cards should always just be appended to the end
            end = True

        if end:
            self._cards.append(card)
            idx = len(self._cards) - 1
        else:
            idx = len(self._cards) - 1
            while idx >= 0 and self._cards[idx].is_blank:
                idx -= 1

            if not bottom and card.keyword not in Card._commentary_keywords:
                while (idx >= 0 and
                       self._cards[idx].keyword in Card._commentary_keywords):
                    idx -= 1

            idx += 1
            self._cards.insert(idx, card)
            self._updateindices(idx)

        keyword = Card.normalize_keyword(card.keyword)
        self._keyword_indices[keyword].append(idx)
        if card.field_specifier is not None:
            self._rvkc_indices[card.rawkeyword].append(idx)

        if not end:
            # If the appended card was a commentary card, and it was appended
            # before existing cards with the same keyword, the indices for
            # cards with that keyword may have changed
            if not bottom and card.keyword in Card._commentary_keywords:
                self._keyword_indices[keyword].sort()

            # Finally, if useblanks, delete a blank cards from the end
            if useblanks and self._countblanks():
                # Don't do this unless there is at least one blanks at the end
                # of the header; we need to convert the card to its string
                # image to see how long it is.  In the vast majority of cases
                # this will just be 80 (Card.length) but it may be longer for
                # CONTINUE cards
                self._useblanks(len(str(card)) // Card.length)

        self._modified = True

    def extend(self, cards, strip=True, unique=False, update=False,
               update_first=False, useblanks=True, bottom=False, end=False):
        """
        Appends multiple keyword+value cards to the end of the header, similar
        to `list.extend`.

        Parameters
        ----------
        cards : iterable
            An iterable of (keyword, value, [comment]) tuples; see
            `Header.append`.

        strip : bool, optional
            Remove any keywords that have meaning only to specific types of
            HDUs, so that only more general keywords are added from extension
            Header or Card list (default: `True`).

        unique : bool, optional
            If `True`, ensures that no duplicate keywords are appended;
            keywords already in this header are simply discarded.  The
            exception is commentary keywords (COMMENT, HISTORY, etc.): they are
            only treated as duplicates if their values match.

        update : bool, optional
            If `True`, update the current header with the values and comments
            from duplicate keywords in the input header.  This supercedes the
            ``unique`` argument.  Commentary keywords are treated the same as
            if ``unique=True``.

        update_first : bool, optional
            If the first keyword in the header is 'SIMPLE', and the first
            keyword in the input header is 'XTENSION', the 'SIMPLE' keyword is
            replaced by the 'XTENSION' keyword.  Likewise if the first keyword
            in the header is 'XTENSION' and the first keyword in the input
            header is 'SIMPLE', the 'XTENSION' keyword is replaced by the
            'SIMPLE' keyword.  This behavior is otherwise dumb as to whether or
            not the resulting header is a valid primary or extension header.
            This is mostly provided to support backwards compatibility with the
            old :meth:`Header.fromTxtFile` method, and only applies if
            ``update=True``.

        useblanks, bottom, end : bool, optional
            These arguments are passed to :meth:`Header.append` while appending
            new cards to the header.
        """

        temp = Header(cards)
        if strip:
            temp._strip()

        if len(self):
            first = self.cards[0].keyword
        else:
            first = None

        # We don't immediately modify the header, because first we need to sift
        # out any duplicates in the new header prior to adding them to the
        # existing header, but while *allowing* duplicates from the header
        # being extended from (see ticket #156)
        extend_cards = []

        for idx, card in enumerate(temp.cards):
            keyword = card.keyword
            if keyword not in Card._commentary_keywords:
                if unique and not update and keyword in self:
                    continue
                elif update:
                    if idx == 0 and update_first:
                        # Dumbly update the first keyword to either SIMPLE or
                        # XTENSION as the case may be, as was in the case in
                        # Header.fromTxtFile
                        if ((keyword == 'SIMPLE' and first == 'XTENSION') or
                                (keyword == 'XTENSION' and first == 'SIMPLE')):
                            del self[0]
                            self.insert(0, card)
                        else:
                            self[keyword] = (card.value, card.comment)
                    elif keyword in self:
                        self[keyword] = (card.value, card.comment)
                    else:
                        extend_cards.append(card)
                else:
                    extend_cards.append(card)
            else:
                if (unique or update) and keyword in self:
                    if card.is_blank:
                        extend_cards.append(card)
                        continue

                    for value in self[keyword]:
                        if value == card.value:
                            break
                    else:
                        extend_cards.append(card)
                else:
                    extend_cards.append(card)

        for card in extend_cards:
            self.append(card, useblanks=useblanks, bottom=bottom, end=end)

    def count(self, keyword):
        """
        Returns the count of the given keyword in the header, similar to
        `list.count` if the Header object is treated as a list of keywords.

        Parameters
        ----------
        keyword : str
            The keyword to count instances of in the header

        """

        keyword = Card.normalize_keyword(keyword)

        # We have to look before we leap, since otherwise _keyword_indices,
        # being a defaultdict, will create an entry for the nonexistent keyword
        if keyword not in self._keyword_indices:
            raise KeyError("Keyword %r not found." % keyword)

        return len(self._keyword_indices[keyword])

    def index(self, keyword, start=None, stop=None):
        """
        Returns the index if the first instance of the given keyword in the
        header, similar to `list.index` if the Header object is treated as a
        list of keywords.

        Parameters
        ----------
        keyword : str
            The keyword to look up in the list of all keywords in the header

        start : int, optional
            The lower bound for the index

        stop : int, optional
            The upper bound for the index

        """

        if start is None:
            start = 0

        if stop is None:
            stop = len(self._cards)

        if stop < start:
            step = -1
        else:
            step = 1

        norm_keyword = Card.normalize_keyword(keyword)

        for idx in range(start, stop, step):
            if self._cards[idx].keyword.upper() == norm_keyword:
                return idx
        else:
            raise ValueError('The keyword %r is not in the header.' % keyword)

    def insert(self, key, card, useblanks=True, after=False):
        """
        Inserts a new keyword+value card into the Header at a given location,
        similar to `list.insert`.

        Parameters
        ----------
        key : int, str, or tuple
            The index into the list of header keywords before which the
            new keyword should be inserted, or the name of a keyword before
            which the new keyword should be inserted.  Can also accept a
            (keyword, index) tuple for inserting around duplicate keywords.

        card : str, tuple
            A keyword or a (keyword, value, [comment]) tuple; see
            `Header.append`

        useblanks : bool, optional
            If there are blank cards at the end of the Header, replace the
            first blank card so that the total number of cards in the Header
            does not increase.  Otherwise preserve the number of blank cards.

        after : bool, optional
            If set to `True`, insert *after* the specified index or keyword,
            rather than before it.  Defaults to `False`.
        """

        if not isinstance(key, int):
            # Don't pass through ints to _cardindex because it will not take
            # kindly to indices outside the existing number of cards in the
            # header, which insert needs to be able to support (for example
            # when inserting into empty headers)
            idx = self._cardindex(key)
        else:
            idx = key

        if after:
            if idx == -1:
                idx = len(self._cards)
            else:
                idx += 1

        if idx >= len(self._cards):
            # This is just an append (Though it must be an append absolutely to
            # the bottom, ignoring blanks, etc.--the point of the insert method
            # is that you get exactly what you asked for with no surprises)
            self.append(card, end=True)
            return

        if isinstance(card, string_types):
            card = Card(card)
        elif isinstance(card, tuple):
            card = Card(*card)
        elif not isinstance(card, Card):
            raise ValueError(
                'The value inserted into a Header must be either a keyword or '
                '(keyword, value, [comment]) tuple; got: %r' % card)

        self._cards.insert(idx, card)

        keyword = card.keyword

        # If idx was < 0, determine the actual index according to the rules
        # used by list.insert()
        if idx < 0:
            idx += len(self._cards) - 1
            if idx < 0:
                idx = 0

        # All the keyword indices above the insertion point must be updated
        self._updateindices(idx)

        self._keyword_indices[keyword].append(idx)
        count = len(self._keyword_indices[keyword])
        if count > 1:
            # There were already keywords with this same name
            if keyword not in Card._commentary_keywords:
                warnings.warn(
                    'A %r keyword already exists in this header.  Inserting '
                    'duplicate keyword.' % keyword)
            self._keyword_indices[keyword].sort()

        if card.field_specifier is not None:
            # Update the index of RVKC as well
            rvkc_indices = self._rvkc_indices[card.rawkeyword]
            rvkc_indices.append(idx)
            rvkc_indices.sort()

        if useblanks:
            self._useblanks(len(str(card)) // Card.length)

        self._modified = True

    def remove(self, keyword):
        """
        Removes the first instance of the given keyword from the header similar
        to `list.remove` if the Header object is treated as a list of keywords.

        Parameters
        ----------
        keyword : str
            The keyword of which to remove the first instance in the header

        """

        del self[self.index(keyword)]

    def rename_keyword(self, oldkeyword, newkeyword, force=False):
        """
        Rename a card's keyword in the header.

        Parameters
        ----------
        oldkeyword : str or int
            Old keyword or card index

        newkeyword : str
            New keyword

        force : bool, optional
            When `True`, if the new keyword already exists in the header, force
            the creation of a duplicate keyword. Otherwise a
            `~.exceptions.ValueError` is raised.
        """

        oldkeyword = Card.normalize_keyword(oldkeyword)
        newkeyword = Card.normalize_keyword(newkeyword)

        if newkeyword == 'CONTINUE':
            raise ValueError('Can not rename to CONTINUE')

        if (newkeyword in Card._commentary_keywords or
                oldkeyword in Card._commentary_keywords):
            if not (newkeyword in Card._commentary_keywords and
                    oldkeyword in Card._commentary_keywords):
                raise ValueError('Regular and commentary keys can not be '
                                 'renamed to each other.')
        elif not force and newkeyword in self:
            raise ValueError('Intended keyword %s already exists in header.'
                             % newkeyword)

        idx = self.index(oldkeyword)
        card = self.cards[idx]
        del self[idx]
        self.insert(idx, (newkeyword, card.value, card.comment))

    def add_history(self, value, before=None, after=None):
        """
        Add a ``HISTORY`` card.

        Parameters
        ----------
        value : str
            History text to be added.

        before : str or int, optional
            Same as in `Header.update`

        after : str or int, optional
            Same as in `Header.update`
        """

        self._add_commentary('HISTORY', value, before=before, after=after)

    def add_comment(self, value, before=None, after=None):
        """
        Add a ``COMMENT`` card.

        Parameters
        ----------
        value : str
            Text to be added.

        before : str or int, optional
            Same as in `Header.update`

        after : str or int, optional
            Same as in `Header.update`
        """

        self._add_commentary('COMMENT', value, before=before, after=after)

    def add_blank(self, value='', before=None, after=None):
        """
        Add a blank card.

        Parameters
        ----------
        value : str, optional
            Text to be added.

        before : str or int, optional
            Same as in `Header.update`

        after : str or int, optional
            Same as in `Header.update`
        """

        self._add_commentary('', value, before=before, after=after)

    def _update(self, card):
        """
        The real update code.  If keyword already exists, its value and/or
        comment will be updated.  Otherwise a new card will be appended.

        This will not create a duplicate keyword except in the case of
        commentary cards.  The only other way to force creation of a duplicate
        is to use the insert(), append(), or extend() methods.
        """

        keyword, value, comment = card

        # Lookups for existing/known keywords are case-insensitive
        keyword = keyword.upper()
        if keyword.startswith('HIERARCH '):
            keyword = keyword[9:]

        if (keyword not in Card._commentary_keywords and
                keyword in self._keyword_indices):
            # Easy; just update the value/comment
            idx = self._keyword_indices[keyword][0]
            existing_card = self._cards[idx]
            existing_card.value = value
            if comment is not None:
                # '' should be used to explicitly blank a comment
                existing_card.comment = comment
            if existing_card._modified:
                self._modified = True
        elif keyword in Card._commentary_keywords:
            cards = self._splitcommentary(keyword, value)
            if keyword in self._keyword_indices:
                # Append after the last keyword of the same type
                idx = self.index(keyword, start=len(self) - 1, stop=-1)
                isblank = not (keyword or value or comment)
                for c in reversed(cards):
                    self.insert(idx + 1, c, useblanks=(not isblank))
            else:
                for c in cards:
                    self.append(c, bottom=True)
        else:
            # A new keyword! self.append() will handle updating _modified
            self.append(card)

    def _cardindex(self, key):
        """Returns an index into the ._cards list given a valid lookup key."""

        # This used to just set key = (key, 0) and then go on to act as if the
        # user passed in a tuple, but it's much more common to just be given a
        # string as the key, so optimize more for that case
        if isinstance(key, string_types):
            keyword = key
            n = 0
        elif isinstance(key, int):
            # If < 0, determine the actual index
            if key < 0:
                key += len(self._cards)
            if key < 0 or key >= len(self._cards):
                raise IndexError('Header index out of range.')
            return key
        elif isinstance(key, slice):
            return key
        elif isinstance(key, tuple):
            if (len(key) != 2 or not isinstance(key[0], string_types) or
                    not isinstance(key[1], int)):
                raise ValueError(
                    'Tuple indices must be 2-tuples consisting of a '
                    'keyword string and an integer index.')
            keyword, n = key
        else:
            raise ValueError(
                'Header indices must be either a string, a 2-tuple, or '
                'an integer.')

        keyword = Card.normalize_keyword(keyword)
        # Returns the index into _cards for the n-th card with the given
        # keyword (where n is 0-based)
        indices = self._keyword_indices.get(keyword, None)

        if keyword and not indices:
            if len(keyword) > KEYWORD_LENGTH or '.' in keyword:
                raise KeyError("Keyword %r not found." % keyword)
            else:
                # Maybe it's a RVKC?
                indices = self._rvkc_indices.get(keyword, None)

        if not indices:
            raise KeyError("Keyword %r not found." % keyword)

        try:
            return indices[n]
        except IndexError:
            raise IndexError('There are only %d %r cards in the header.' %
                             (len(indices), keyword))

    def _keyword_from_index(self, idx):
        """
        Given an integer index, return the (keyword, repeat) tuple that index
        refers to.  For most keywords the repeat will always be zero, but it
        may be greater than zero for keywords that are duplicated (especially
        commentary keywords).

        In a sense this is the inverse of self.index, except that it also
        supports duplicates.
        """

        if idx < 0:
            idx += len(self._cards) - 1

        keyword = self._cards[idx].keyword
        repeat = self._keyword_indices[keyword].index(idx)
        return keyword, repeat

    def _relativeinsert(self, card, before=None, after=None, replace=False):
        """
        Inserts a new card before or after an existing card; used to
        implement support for the legacy before/after keyword arguments to
        Header.update().

        If replace=True, move an existing card with the same keyword.
        """

        if before is None:
            insertionkey = after
        else:
            insertionkey = before

        def get_insertion_idx():
            if not (isinstance(insertionkey, int) and
                    insertionkey >= len(self._cards)):
                idx = self._cardindex(insertionkey)
            else:
                idx = insertionkey

            if before is None:
                idx += 1

            return idx

        if replace:
            # The card presumably already exists somewhere in the header.
            # Check whether or not we actually have to move it; if it does need
            # to be moved we just delete it and then it will be reinserted
            # below
            old_idx = self._cardindex(card.keyword)
            insertion_idx = get_insertion_idx()

            if (insertion_idx >= len(self._cards) and
                    old_idx == len(self._cards) - 1):
                # The card would be appended to the end, but it's already at
                # the end
                return

            if before is not None:
                if old_idx == insertion_idx - 1:
                    return
            elif after is not None and old_idx == insertion_idx:
                return

            del self[old_idx]

        # Even if replace=True, the insertion idx may have changed since the
        # old card was deleted
        idx = get_insertion_idx()

        if card[0] in Card._commentary_keywords:
            cards = reversed(self._splitcommentary(card[0], card[1]))
        else:
            cards = [card]
        for c in cards:
            self.insert(idx, c)

    def _updateindices(self, idx, increment=True):
        """
        For all cards with index above idx, increment or decrement its index
        value in the keyword_indices dict.
        """

        if idx > len(self._cards):
            # Save us some effort
            return

        increment = 1 if increment else -1

        for index_sets in (self._keyword_indices, self._rvkc_indices):
            for indices in itervalues(index_sets):
                for jdx, keyword_index in enumerate(indices):
                    if keyword_index >= idx:
                        indices[jdx] += increment

    def _countblanks(self):
        """Returns the number of blank cards at the end of the Header."""

        for idx in range(1, len(self._cards)):
            if not self._cards[-idx].is_blank:
                return idx - 1
        return 0

    def _useblanks(self, count):
        for _ in range(count):
            if self._cards[-1].is_blank:
                del self[-1]
            else:
                break

    def _haswildcard(self, keyword):
        """Return `True` if the input keyword contains a wildcard pattern."""

        return (isinstance(keyword, string_types) and
                (keyword.endswith('...') or '*' in keyword or '?' in keyword))

    def _wildcardmatch(self, pattern):
        """
        Returns a list of indices of the cards matching the given wildcard
        pattern.

         * '*' matches 0 or more characters
         * '?' matches a single character
         * '...' matches 0 or more of any non-whitespace character
        """

        pattern = pattern.replace('*', r'.*').replace('?', r'.')
        pattern = pattern.replace('...', r'\S*') + '$'
        pattern_re = re.compile(pattern, re.I)

        return [idx for idx, card in enumerate(self._cards)
                if pattern_re.match(card.keyword)]

    def _set_slice(self, key, value, target):
        """
        Used to implement Header.__setitem__ and CardAccessor.__setitem__.
        """

        if isinstance(key, slice) or self._haswildcard(key):
            if isinstance(key, slice):
                indices = range(*key.indices(len(target)))
            else:
                indices = self._wildcardmatch(key)

            if isinstance(value, string_types) or not isiterable(value):
                value = itertools.repeat(value, len(indices))

            for idx, val in zip(indices, value):
                target[idx] = val

            return True

        return False

    def _splitcommentary(self, keyword, value):
        """
        Given a commentary keyword and value, returns a list of the one or more
        cards needed to represent the full value.  This is primarily used to
        create the multiple commentary cards needed to represent a long value
        that won't fit into a single commentary card.
        """

        # The maximum value in each card can be the maximum card length minus
        # the maximum key length (which can include spaces if they key length
        # less than 8
        maxlen = Card.length - KEYWORD_LENGTH
        valuestr = str(value)

        if len(valuestr) <= maxlen:
            # The value can fit in a single card
            cards = [Card(keyword, value)]
        else:
            # The value must be split across multiple consecutive commentary
            # cards
            idx = 0
            cards = []
            while idx < len(valuestr):
                cards.append(Card(keyword, valuestr[idx:idx + maxlen]))
                idx += maxlen
        return cards

    def _strip(self):
        """
        Strip cards specific to a certain kind of header.

        Strip cards like ``SIMPLE``, ``BITPIX``, etc. so the rest of
        the header can be used to reconstruct another kind of header.
        """

        # TODO: Previously this only deleted some cards specific to an HDU if
        # _hdutype matched that type.  But it seemed simple enough to just
        # delete all desired cards anyways, and just ignore the KeyErrors if
        # they don't exist.
        # However, it might be desirable to make this extendable somehow--have
        # a way for HDU classes to specify some headers that are specific only
        # to that type, and should be removed otherwise.

        if 'NAXIS' in self:
            naxis = self['NAXIS']
        else:
            naxis = 0

        if 'TFIELDS' in self:
            tfields = self['TFIELDS']
        else:
            tfields = 0

        for idx in range(naxis):
            try:
                del self['NAXIS' + str(idx + 1)]
            except KeyError:
                pass

        for name in ('TFORM', 'TSCAL', 'TZERO', 'TNULL', 'TTYPE',
                     'TUNIT', 'TDISP', 'TDIM', 'THEAP', 'TBCOL'):
            for idx in range(tfields):
                try:
                    del self[name + str(idx + 1)]
                except KeyError:
                    pass

        for name in ('SIMPLE', 'XTENSION', 'BITPIX', 'NAXIS', 'EXTEND',
                     'PCOUNT', 'GCOUNT', 'GROUPS', 'BSCALE', 'BZERO',
                     'TFIELDS'):
            try:
                del self[name]
            except KeyError:
                pass

    def _add_commentary(self, key, value, before=None, after=None):
        """
        Add a commentary card.

        If ``before`` and ``after`` are `None`, add to the last occurrence
        of cards of the same name (except blank card).  If there is no
        card (or blank card), append at the end.
        """

        if before is not None or after is not None:
            self._relativeinsert((key, value), before=before,
                                 after=after)
        else:
            self[key] = value

    # Some fixes for compatibility with the Python 3 dict interface, where
    # iteritems -> items, etc.
    if six.PY3:
        keys = iterkeys
        values = itervalues
        items = iteritems
        del iterkeys
        del itervalues
        del iteritems def fileobj_closed(f):
    """
    Returns True if the given file-like object is closed or if f is not a
    file-like object.
    """

    if hasattr(f, 'closed'):
        return f.closed
    elif hasattr(f, 'fileobj') and hasattr(f.fileobj, 'closed'):
        return f.fileobj.closed
    elif hasattr(f, 'fp') and hasattr(f.fp, 'closed'):
        return f.fp.closed
    else:
        return False def fileobj_name(f):
    """
    Returns the 'name' of file-like object f, if it has anything that could be
    called its name.  Otherwise f's class or type is returned.  If f is a
    string f itself is returned.
    """

    if isinstance(f, string_types):
        return f
    elif isinstance(f, gzip.GzipFile):
        # The .name attribute on GzipFiles does not always represent the name
        # of the file being read/written--it can also represent the original
        # name of the file being compressed
        # See the documentation at
        # https://docs.python.org/3/library/gzip.html#gzip.GzipFile
        # As such, for gzip files only return the name of the underlying
        # fileobj, if it exists
        return fileobj_name(f.fileobj)
    elif hasattr(f, 'name'):
        return f.name
    elif hasattr(f, 'filename'):
        return f.filename
    elif hasattr(f, '__class__'):
        return str(f.__class__)
    else:
        return str(type(f)) def fileobj_mode(f):
    """
    Returns the 'mode' string of a file-like object if such a thing exists.
    Otherwise returns None.
    """

    # Go from most to least specific--for example gzip objects have a 'mode'
    # attribute, but it's not analogous to the file.mode attribute
    if hasattr(f, 'fileobj') and hasattr(f.fileobj, 'mode'):
        fileobj = f.fileobj
    elif hasattr(f, 'fp') and hasattr(f.fp, 'mode'):
        fileobj = f.fp
    elif hasattr(f, 'mode'):
        fileobj = f
    else:
        return None

    return _fileobj_normalize_mode(fileobj) def _is_int(val):
    return isinstance(val, integer_types + (np.integer,))
import os
import numpy as np
from .extern.six import string_types
from .file import FILE_MODES, _File
from .hdu.base import _BaseHDU, _ValidHDU
from .hdu.hdulist import fitsopen
from .hdu.image import PrimaryHDU, ImageHDU
from .hdu.table import BinTableHDU
from .header import Header
from .util import fileobj_closed, fileobj_name, fileobj_mode, _is_int

if isinstance(tabledump.__doc__, string_types):
    tabledump.__doc__ += BinTableHDU._tdump_file_format.replace('\n', '\n    ')


def tableload(datafile, cdfile, hfile=None):
    """
    Create a table from the input ASCII files.  The input is from up
    to three separate files, one containing column definitions, one
    containing header parameters, and one containing column data.  The
    header parameters file is not required.  When the header
    parameters file is absent a minimal header is constructed.

    Parameters
    ----------
    datafile : file path, file object or file-like object
        Input data file containing the table data in ASCII format.

    cdfile : file path, file object or file-like object
        Input column definition file containing the names, formats,
        display formats, physical units, multidimensional array
        dimensions, undefined values, scale factors, and offsets
        associated with the columns in the table.

    hfile : file path, file object or file-like object, optional
        Input parameter definition file containing the header
        parameter definitions to be associated with the table.
        If `None`, a minimal header is constructed.

    Notes
    -----
    The primary use for the `tableload` function is to allow the input of
    ASCII data that was edited in a standard text editor of the table
    data and parameters.  The tabledump function can be used to create the
    initial ASCII files.
    """

    return BinTableHDU.load(datafile, cdfile, hfile, replace=True)

if isinstance(tableload.__doc__, string_types):
    tableload.__doc__ += BinTableHDU._tdump_file_format.replace('\n', '\n    ')


def _getext(filename, mode, *args, **kwargs):
    """
    Open the input file, return the `HDUList` and the extension.

    This supports several different styles of extension selection.  See the
    :func:`getdata()` documentation for the different possibilities.
    """

    ext = kwargs.pop('ext', None)
    extname = kwargs.pop('extname', None)
    extver = kwargs.pop('extver', None)

    err_msg = ('Redundant/conflicting extension arguments(s): %s' %
               ({'args': args, 'ext': ext,  'extname': extname,
                 'extver': extver},))

    # This code would be much simpler if just one way of specifying an
    # extension were picked.  But now we need to support all possible ways for
    # the time being.
    if len(args) == 1:
        # Must be either an extension number, an extension name, or an
        # (extname, extver) tuple
        if _is_int(args[0]) or (isinstance(ext, tuple) and len(ext) == 2):
            if ext is not None or extname is not None or extver is not None:
                raise TypeError(err_msg)
            ext = args[0]
        elif isinstance(args[0], string_types):
            # The first arg is an extension name; it could still be valid
            # to provide an extver kwarg
            if ext is not None or extname is not None:
                raise TypeError(err_msg)
            extname = args[0]
        else:
            # Take whatever we have as the ext argument; we'll validate it
            # below
            ext = args[0]
    elif len(args) == 2:
        # Must be an extname and extver
        if ext is not None or extname is not None or extver is not None:
            raise TypeError(err_msg)
        extname = args[0]
        extver = args[1]
    elif len(args) > 2:
        raise TypeError('Too many positional arguments.')

    if (ext is not None and
            not (_is_int(ext) or
                 (isinstance(ext, tuple) and len(ext) == 2 and
                  isinstance(ext[0], string_types) and _is_int(ext[1])))):
        raise ValueError(
            'The ext keyword must be either an extension number '
            '(zero-indexed) or a (extname, extver) tuple.')
    if extname is not None and not isinstance(extname, string_types):
        raise ValueError('The extname argument must be a string.')
    if extver is not None and not _is_int(extver):
        raise ValueError('The extver argument must be an integer.')

    if ext is None and extname is None and extver is None:
        ext = 0
    elif ext is not None and (extname is not None or extver is not None):
        raise TypeError(err_msg)
    elif extname:
        if extver:
            ext = (extname, extver)
        else:
            ext = (extname, 1)
    elif extver and extname is None:
        raise TypeError('extver alone cannot specify an extension.')

    hdulist = fitsopen(filename, mode=mode, **kwargs)

    return hdulist, ext


def _makehdu(data, header):
    if header is None:
        header = Header()

    hdu = _BaseHDU(data, header)