~ubuntu-branches/ubuntu/raring/pyfits/raring

« back to all changes in this revision

Viewing changes to docs/source/appendix/header_transition.rst

  • Committer: Package Import Robot
  • Author(s): Aurelien Jarno
  • Date: 2012-12-30 15:03:07 UTC
  • mfrom: (8.1.6 sid)
  • Revision ID: package-import@ubuntu.com-20121230150307-n4mgnvvincyzmze3
Tags: 1:3.1-1
* New upstream version.
  - Refreshed 01-zlib.diff.
  - Added 02-disable-version-setup-hook.diff to prevent version.py to
    be regenerated with a timestamp at clean time.
* Compress source and .deb with xz.
* Bump Standards-Version to 3.9.4.0 (no changes).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
.. currentmodule:: pyfits
 
2
 
 
3
.. _header-transition-guide:
 
4
 
 
5
*********************************
 
6
Header Interface Transition Guide
 
7
*********************************
 
8
 
 
9
PyFITS v3.1 included an almost complete rewrite of the :class:`Header`
 
10
interface.  Although the new interface is largely compatible with the old
 
11
interace (whether due to similarities in the design, or backwards-compatibility
 
12
support), there are enough differences that a full explanation of the new
 
13
interface is merited.
 
14
 
 
15
The Trac ticket discussing the initial motivation and changes to be made to the
 
16
:class:`Header` class is `#64`_.  It may be worth reading for some of the
 
17
background to this work, though this document contains a more complete
 
18
description of the "final" product (which will continue to evolve).
 
19
 
 
20
.. _#64: https://trac.assembla.com/pyfits/ticket/64
 
21
 
 
22
 
 
23
Background
 
24
==========
 
25
 
 
26
Prior to 3.1, PyFITS users interacted with FITS headers by way of three
 
27
different classes: :class:`Card`, :class:`CardList`, and :class:`Header`.
 
28
 
 
29
The Card class represents a single header card with a keyword, value, and
 
30
comment.  It also contains all the machinery for parsing FITS header cards,
 
31
given the 80 character string, or "card image" read from the header.
 
32
 
 
33
The CardList class is actually a subclass of Python's `list` built-in.  It was
 
34
meant to represent the actual list of cards that make up a header.  That is, it
 
35
represents an ordered list of cards in the physical order that they appear in
 
36
the header.  It supports the usual list methods for inserting and appending new
 
37
cards into the list.  It also supports `dict`-like keyword access, where
 
38
``cardlist['KEYWORD']`` would return the first card in the list with the given
 
39
keyword.
 
40
 
 
41
A lot of the functionality for manipulating headers was actually buried in the
 
42
CardList class.  The Header class was more of a wrapper around CardList that
 
43
added a little bit of abstraction.  It also implemented a partial dict-like
 
44
interface, though for Headers a keyword lookup returned the header value
 
45
associated with that keyword, and not the Card object.  Though almost every
 
46
method on the Header class was just performing some operations on the
 
47
underlying CardList.
 
48
 
 
49
The problem is that there were certain things one could *only* do by directly
 
50
accessing the CardList, such as look up the comments on a card, or access cards
 
51
that have duplicate keywords, such as HISTORY.  Another long-standing
 
52
misfeature that slicing a Header object actually returned a CardList object,
 
53
rather than a new Header.  For all but the most simple use cases, working with
 
54
CardList objects was largely unavoidable.
 
55
 
 
56
But it was realized that CardList is really an implementation detail
 
57
not representing any element of a FITS file distinct from the header itself.
 
58
Users familiar with the FITS format know what a header is, but it's not clear
 
59
how a "card list" is distinct from that, or why operations go through the
 
60
Header object, while some have to be performed through the CardList.
 
61
 
 
62
So the primary goal of this redesign was eliminate the :class:`CardList` class
 
63
altogether, and make it possible for users to perform all header manipulations
 
64
directly through :class:`Header` objects.  It also tries to present headers as
 
65
similar as possible to more a more familiar data structure--an ordered mapping
 
66
(or :class:`~collections.OrderedDict` in Python) for ease of use by new users
 
67
less familiar with the FITS format.  Though there are still many added
 
68
complexities for dealing with the idiosyncracies of the FITS format.
 
69
 
 
70
 
 
71
Deprecation Warnings
 
72
====================
 
73
 
 
74
A few old methods on the :class:`Header` class have been marked as deprecated,
 
75
either because they have been renamed to a more `PEP 8`_-compliant name, or
 
76
because have become redundant due to new features.  To check if your code is
 
77
using any deprecated methods or features, run your code with ``python -Wd``.
 
78
This will output any deprecation warnings to the console.
 
79
 
 
80
Two of the most common deprecation warnings related to Headers are for:
 
81
 
 
82
- :meth:`Header.has_key`--this has actually been deprecated since PyFITS 3.0,
 
83
  just as Python's `dict.has_key` is deprecated.  For checking a key's presence
 
84
  in a mapping object like `dict` or :class:`Header`, use the ``key in d``
 
85
  syntax.  This has long been the preference in Python.
 
86
 
 
87
- :meth:`Header.ascardlist()` and :attr:`Header.ascard`--these were used to
 
88
  access the :class:`CardList` object underlying a header.  They should still
 
89
  work, and return a skeleton CardList implementation that should support most
 
90
  of the old CardList functionality.  But try removing as much of this as
 
91
  possible.  If direct access to the :class:`Card` objects making up a header
 
92
  is necessary, use :attr:`Header.cards`, which returns an iterator over the
 
93
  cards.  More on that below.
 
94
 
 
95
.. _PEP 8: http://www.python.org/dev/peps/pep-0008/
 
96
 
 
97
New Header Design
 
98
=================
 
99
 
 
100
The new :class:`Header` class is designed to work as a drop-in replacement for
 
101
a `dict` via `duck typing`_.  That is, although it is not a subclass of `dict`,
 
102
it implements all the same methods and interfaces.  In particular, it is
 
103
similar to an :class:`~collections.OrderedDict` in that the order of insertions
 
104
is preserved.  However, Header also supports many additional features and
 
105
behaviors specific to the FITS format.  It should also be noted that while the
 
106
old Header implementation also had a dict-like interface, it did not implement
 
107
the *entire* dict interface as the new Header does.
 
108
 
 
109
Although the new Header is used like a dict/mapping in most cases, it also
 
110
supports a `list` interface.  The list-like interace is a bit idiosyncratic in
 
111
that in some contexts the Header acts like a list of values, in some like a
 
112
list of keywords, and in a few contexts like a list of :class:`Cards`.  This
 
113
may be the most difficult aspect of the new design, but there is logic to it.
 
114
 
 
115
As with the old Header implementation, integer index access is supported:
 
116
``header[0]`` returns the value of the first keyword.  However, the
 
117
:meth:`Header.index` method treats the header as though it's a list of
 
118
keywords, and returns the index of a given keyword.  For example::
 
119
 
 
120
    >>> header.index('BITPIX')
 
121
    2
 
122
 
 
123
:meth:`Header.count` is similar to `list.count`, and also takes a keyword as
 
124
its argument::
 
125
 
 
126
    >>> header.count('HISTORY')
 
127
    20
 
128
 
 
129
A good rule of thumb is that any item access using square brackets `[]` returns
 
130
*value* in the header, whether using keyword or index lookup.  Methods like
 
131
:meth:`~Header.index` and :meth:`~Header.count` that deal with the order and
 
132
quantity of items in the Header generally work on keywords.  Finally, methods
 
133
like :meth:`~Header.insert` and :meth:`~Header.append` that add new items to
 
134
the header work on cards.
 
135
 
 
136
Aside from the list-like methods, the new Header class works very similarly to
 
137
the old implementation for most basic use cases and should not present too many
 
138
surprises.  There are differences, however:
 
139
 
 
140
- As before, the Header() initializer can take a list of :class:`Card` objects
 
141
  with which to fill the header.  However, now any iterable may be used.  It is
 
142
  also important to note that *any* Header method that accepts :class:`Card`
 
143
  objects can also accept 2-tuples or 3-tuples in place of Cards.  That is,
 
144
  either a `(keyword, value, comment)` tuple or a `(keyword, value)` tuple
 
145
  (comment is assumed blank) may be used anywhere in place of a Card object.
 
146
  This is even preferred, as it simply involves less typing.  For example::
 
147
 
 
148
      >>> header = pyfits.Header([('A', 1), ('B', 2), ('C', 3, 'A comment')])
 
149
      >>> header
 
150
      A       =                    1
 
151
      B       =                    2
 
152
      C       =                    3 / A comment
 
153
 
 
154
- As demonstrated in the previous example, the ``repr()`` for a Header, that is
 
155
  the text that is displayed when entering a Header object in the Python
 
156
  console as an expression, shows the header as it would appear in a FITS file.
 
157
  This inserts newlines after each card so that it is easily readable
 
158
  regardless of terminal width.  It is *not* necessary to use ``print header``
 
159
  to view this.  Simply entering ``header`` displays the header contents as it
 
160
  would appear in the file (sans the END card).
 
161
 
 
162
- ``len(header)`` is now supported (previously it was necessary to do
 
163
  ``len(header.ascard)``.  This returns the total number of cards in the
 
164
  header, including blank cards, but excluding the END card.
 
165
 
 
166
- FITS supports having duplicate keywords, although they are generally in error
 
167
  except for commentary keywords like COMMENT and HISTORY.  PyFITS now supports
 
168
  reading, updating, and deleting duplicate keywords: Instead of using the
 
169
  keyword by itself, use a ``(keyword, index)`` tuple.  For example
 
170
  ``('HISTORY', 0)`` represents the first HISTORY card, ``('HISTORY', 1)``
 
171
  represents the second HISTORY card, and so on.  In fact, when a keyword is
 
172
  used by itself, it's really just shorthand for ``(keyword, 0)``.  Its is now
 
173
  possible to delete an accidental duplicate like so::
 
174
 
 
175
      >>> del header[('NAXIS', 1)]
 
176
 
 
177
  This will remove an accdential duplicate NAXIS card from the header.
 
178
 
 
179
- Even if there are duplicate keywords, keyword lookups like
 
180
  ``header['NAXIS']`` will always return the value associated with the first
 
181
  copy of that keyword, with one exception:  Commentary keywords like COMMENT
 
182
  and HISTORY are expected to have duplicates.  So ``header['HISTORY']``, for
 
183
  example, returns the whole sequence of HISTORY values in the correct order.
 
184
  This list of values can be sliced arbitrarily.  For example, to view the last
 
185
  3 history entries in a header::
 
186
 
 
187
      >>> hdulist[0].header['HISTORY'][-3:]
 
188
        reference table oref$laf13367o_pct.fits
 
189
        reference table oref$laf13369o_apt.fits
 
190
      Heliocentric correction = 16.225 km/s
 
191
 
 
192
- Subscript assignment can now be used to add new keywords to the header.  Just
 
193
  as with a normal `dict`, ``header['NAXIS'] = 1`` will either update the NAXIS
 
194
  keyword if it already exists, or add a new NAXIS keyword with a value of
 
195
  ``1`` if it does not exist.  In the old interface this would return a
 
196
  ``KeyError`` if NAXIS did not exist, and the only way to add a new keyword
 
197
  was through the update() method.
 
198
 
 
199
  By default, new keywords added in this manner are added to the end of the
 
200
  header, with a few FITS-specific exceptions:
 
201
 
 
202
  * If the header contains extra blank cards at the end, new keywords are added
 
203
    before the blanks.
 
204
 
 
205
  * If the header ends with a list of commentary cards--for example a sequence
 
206
    of HISTORY cards--those are kept at the end, and new keywords are inserted
 
207
    before the commentary cards.
 
208
 
 
209
  * If the keyword is a commentary keyword like COMMENT or HISTORY (or an empty
 
210
    string for blank keywords), a *new* commentary keyword is always added, and
 
211
    appended to the last commentary keyword of the same type.  For example,
 
212
    HISTORY keywords are always placed after the last history keyword::
 
213
 
 
214
        >>> header = pyfits.Header()
 
215
        >>> header['COMMENT'] = 'Comment 1'
 
216
        >>> header['HISTORY'] = 'History 1'
 
217
        >>> header['COMMENT'] = 'Comment 2'
 
218
        >>> header['HISTORY'] = 'History 2'
 
219
        >>> header
 
220
        COMMENT Comment 1
 
221
        COMMENT Comment 2
 
222
        HISTORY History 1
 
223
        HISTORY History 2
 
224
 
 
225
  These behaviors represent a sensible default behavior for keyword assignment,
 
226
  and represents the same behavior as :meth:`~Header.update` in the old Header
 
227
  implementation.  The default behaviors may still be bypassed through the use
 
228
  of other assignment methods like :meth:`Header.set` and :meth:`Header.append`
 
229
  described later.
 
230
 
 
231
- It is now also possible to assign a value and a comment to a keyword
 
232
  simultaneously using a tuple::
 
233
 
 
234
      >>> header['NAXIS'] = (2, 'Number of axis')
 
235
 
 
236
  This will update the value and comment of an existing keyword, or add a new
 
237
  keyword with the given value and comment.
 
238
 
 
239
- There is a new :attr:`Header.comments` attribute which lists all the comments
 
240
  associated with keywords in the header (not to be confused with COMMENT
 
241
  cards).  This allows viewing and updating the comments on specific cards::
 
242
 
 
243
      >>> header.comments['NAXIS']
 
244
      Number of axis
 
245
      >>> header.comments['NAXIS'] = 'Number of axes'
 
246
      >>> header.comments['NAXIS']
 
247
      Number of axes
 
248
 
 
249
- When deleting a keyword from a header, don't assume that the keyword already
 
250
  exists.  In the old Header implementation this would just silently do
 
251
  nothing.  For backwards-compatibility it is still okay to delete a
 
252
  non-existent keyword, but a warning will be raised.  In the future this
 
253
  *will* be changed so that trying to delete a non-existent keyword raises a
 
254
  `KeyError`.  This is for consistency with the behavior of Python dicts.  So
 
255
  unless you know for certain that a keyword exists before deleting it, it's
 
256
  best to do something like::
 
257
 
 
258
      >>> try:
 
259
      ...     del header['BITPIX']
 
260
      ... except KeyError:
 
261
      ...     pass
 
262
 
 
263
  Or if you prefer to look before you leap::
 
264
 
 
265
      >>> if 'BITPIX' in header:
 
266
      ...     del header['BITPIX']
 
267
 
 
268
- ``del header`` now supports slices.  For example, to delete the last three
 
269
  keywords from a header::
 
270
 
 
271
      >>> del header[-3:]
 
272
 
 
273
- Two headers can now be compared for equality--previously no two Header
 
274
  objects were the same.  Now they compare as equal if they contain the exact
 
275
  same content.  That is, this requires strict equality.
 
276
 
 
277
- Two headers can now be added with the '+' operator, which returns a copy of
 
278
  the left header extended by the right header with :meth:`~Header.extend`.
 
279
  Assignment addition is also possible.
 
280
 
 
281
- The Header.update() method used commonly with the old Header API has been
 
282
  renamed to :meth:`Header.set`.  The primary reason for this change is very
 
283
  simple:  Header implements the `dict` interface, which already has a method
 
284
  called update(), but that behaves differently from the old Header.update().
 
285
 
 
286
  The details of the new update() can be read in the API docs, but it is very
 
287
  similar to `dict.update`.  It also supports backwards compatibility with the
 
288
  old update() by analysis of the arguments passed to it, so existing code will
 
289
  not break immediately.  However, this *will* cause a deprecation warning to
 
290
  be output if they're enabled.  It is best, for starters, to replace all
 
291
  update() calls with set().  Recall, also, that direct assignment is now
 
292
  possible for adding new keywords to a header.  So by and large the only
 
293
  reason to prefer using :meth:`Header.set` is its capability of inserting or
 
294
  moving a keyword to a specific location using the ``before`` or ``after``
 
295
  arguments.
 
296
 
 
297
- Slicing a Header with a slice index returns a new Header containing only
 
298
  those cards contained in the slice.  As mentioned earlier, it used to be that
 
299
  slicing a Header returned a card list--something of a misfeature.  In
 
300
  general, objects that support slicing ought to return an object of the same
 
301
  type when you slice them.
 
302
 
 
303
  Likewise, wildcard keywords used to return a CardList object.  Now they
 
304
  return a new Header--similarly to a slice.  For example::
 
305
 
 
306
      >>> header['NAXIS*']
 
307
 
 
308
  returns a new header containing only the NAXIS and NAXISn cards from the
 
309
  original header.
 
310
 
 
311
.. _duck typing: http://en.wikipedia.org/wiki/Duck_typing
 
312
 
 
313
 
 
314
Transition Tips
 
315
===============
 
316
 
 
317
The above may seem like a lot, but the majority of existing code using PyFITS
 
318
to manipulate headers should not need to be updated, at least not immediately.
 
319
The most common operations still work the same.
 
320
 
 
321
As mentioned above, it would be helpful to run your code with ``python -Wd`` to
 
322
enable deprecation warnings--that should be a good idea of where to look to
 
323
update your code.
 
324
 
 
325
If your code needs to be able to support older versions of PyFITS
 
326
simultaneously with PyFITS 3.1, things are slightly trickier, but not by
 
327
much--the deprecated interfaces will not be removed for several more versions
 
328
because of this.
 
329
 
 
330
- The first change worth making, which is supported by any PyFITS version in
 
331
  the last several years, is remove any use of :meth:`Header.has_key` and
 
332
  replace it with ``keyword in header`` syntax.  It's worth making this change
 
333
  for any dict as well, since `dict.has_key` is deprecated.  Running the
 
334
  following regular expression over your code may help with most (but not all)
 
335
  cases::
 
336
 
 
337
      s/([^ ]+)\.has_key\(([^)]+)\)/\2 in \1/
 
338
 
 
339
- If possible, replace any calls to Header.update() with Header.set() (though
 
340
  don't bother with this if you need to support older PyFITS versions).  Also,
 
341
  if you have any calls to Header.update() that can be replaced with simple
 
342
  subscript assignments (eg. ``header['NAXIS'] = (2, 'Number of axes')``) do
 
343
  that too, if possible.
 
344
 
 
345
- Find any code that uses ``header.ascard`` or ``header.ascardlist()``.  First
 
346
  ascertain whether that code really needs to work directly on Card objects.
 
347
  If that is definitely the case, go ahead and replace those with
 
348
  ``header.cards``--that should work without too much fuss.  If you do need to
 
349
  support older versions, you may keep using ``header.ascard`` for now.
 
350
 
 
351
- In the off chance that you have any code that slices a header, it's best to
 
352
  take the result of that and create a new Header object from it.  For
 
353
  example::
 
354
 
 
355
      >>> new_header = pyfits.Header(old_header[2:])
 
356
 
 
357
  This avoids the problem that in PyFITS <= 3.0 slicing a Header returns a
 
358
  CardList by using the result to initialize a new Header object.  This will
 
359
  work in both cases (in PyFITS 3.1, initializing a Header with an existing
 
360
  Header just copies it, a la `list`).
 
361
 
 
362
- As mentioned earlier, locate any code that deletes keywords with ``del``, and
 
363
  make sure they either look before they leap (``if keyword in header:``) or
 
364
  ask forgiveness (``try/except KeyError:``).