1 """
2 Helpers for manipulating FITS files.
3
4 In contrast to fitstools, this is not for online processing or import of
5 files, but just for the manipulation before processing.
6
7 Rough guideline: if it's about writing fixed fits files, it probably belongs
8 here, otherwise it goes to fitstools.
9
10 Realistically, this module has been hemorraghing functions to
11 fitstools and probably should be removed completely.
12
13 One important function it has grown is FITS header templates. These
14 can be used by processors. If these use custom templates, they should
15 register them (or regret it later). See registerTemplate's docstring.
16 """
17
18
19
20
21
22
23
24 import re
25
26 from gavo import base
27 from gavo.utils import pyfits
28
29 DEFAULT_IGNORED_HEADERS = ["simple", "bitpix", "naxis", "imageh",
30 "imagew", "naxis1", "naxis2", "datamin", "datamax", "date"]
31
34 """copies over all cards from cardList into header, excluding headers
35 named in ignoredHeaders.
36
37 ignoredHeaders must be all lowercase.
38 """
39 for card in cardList:
40 if card.keyword=="COMMENT":
41 header.add_comment(card.value)
42 elif card.keyword=="HISTORY":
43 header.add_history(card.value)
44 elif card.keyword=="":
45 header.append(pyfits.Card("", card.value), end=True)
46 elif card.keyword.lower() in ignoredHeaders:
47 pass
48 else:
49 header.set(card.keyword, card.value, card.comment)
50
51
53 """returns the "normal" key-value pairs from hdr in a dictionary.
54
55 Comment, history and blank cards are excluded; the comments from the
56 cards are lost, too.
57 """
58 ignored = frozenset(["", "HISTORY", "COMMENT"])
59 return dict((k,v) for k,v in hdr.iteritems()
60 if k not in ignored)
61
62
64 try:
65 return [
66 (keyTpl%ind, commentTpl%numeral)
67 for ind, numeral in [
68 (1, "1st"),
69 (2, "2nd"),
70 (3, "3rd"),]]
71 except TypeError:
72 raise base.ReportableError("Invalid header sequence templates: %r %r"%(
73 keyTpl, commentTpl))
74
75
76
77 MINIMAL_IMAGE_TEMPLATE = [
78 pyfits.Card("SIMPLE", True),
79 pyfits.Card("EXTEND", True),
80 ("BITPIX", "Array data type"),
81 pyfits.Card("NAXIS", 2),
82 ("NAXIS1", "Number of elements along 1st axis"),
83 ("NAXIS2", "Number of elements along 2nd axis"),
84 ("BZERO", "Zero point of pixel scaling function"),
85 ("BSCALE", "Slope of pixel scaling function"),]
86
87
88 WCS_TEMPLATE = [
89 pyfits.Card(value="-------------------- Spatial WCS"),
90 ('EQUNIOX', "Equinox of RA and Dec"),
91 ('RADESYS', "Reference System used for RA and Dec"),
92 ('WCSAXES', "Number of FITS axes covered by WCS"),
93 ('CTYPE1', "Projection on axis 1"),
94 ('CTYPE2', "Projection on axis 2"),
95 ('LONPOLE', "See sect 2.4 of WCS paper II"),
96 ('LATPOLE', "See sect 2.4 of WCS paper II"),
97 ('CRVAL1', "Longitude of reference point"),
98 ('CRVAL2', "Latitude of reference point"),
99 ('CRPIX1', "X reference pixel"),
100 ('CRPIX2', "Y reference pixel"),
101 ('CUNIT1', "X pixel scale units"),
102 ('CUNIT2', "Y pixel scale units"),
103 ('CD1_1', "(1,1) Full transformation matrix"),
104 ('CD1_2', "(1,2) Full transformation matrix"),
105 ('CD2_1', "(2,1) Full transformation matrix"),
106 ('CD2_2', "(2,2) Full transformation matrix"),
107 ('PC1_1', "(1,1) Transformation matrix"),
108 ('PC1_2', "(1,2) Transformation matrix"),
109 ('PC2_1', "(2,1) Transformation matrix"),
110 ('PC2_2', "(2,2) Transformation matrix"),
111 ('A_ORDER', "Correction polynomial order, axis 1"),
112 ('A_0_0', "Axis 1 correction polynomial, coefficient"),
113 ('A_0_1', "Axis 1 correction polynomial, coefficient"),
114 ('A_0_2', "Axis 1 correction polynomial, coefficient"),
115 ('A_1_0', "Axis 1 correction polynomial, coefficient"),
116 ('A_1_1', "Axis 1 correction polynomial, coefficient"),
117 ('A_2_0', "Axis 1 correction polynomial, coefficient"),
118 ('B_ORDER', "Correction polynomial order, axis 2"),
119 ('B_0_0', "Axis 2 correction polynomial, coefficient"),
120 ('B_0_1', "Axis 2 correction polynomial, coefficient"),
121 ('B_0_2', "Axis 2 correction polynomial, coefficient"),
122 ('B_1_0', "Axis 2 correction polynomial, coefficient"),
123 ('B_1_1', "Axis 2 correction polynomial, coefficient"),
124 ('B_2_0', "Axis 2 correction polynomial, coefficient"),
125 ('AP_ORDER', "Inverse polynomial order, axis 1"),
126 ('AP_0_0', "Axis 1 inverse polynomial, coefficient"),
127 ('AP_0_1', "Axis 1 inverse polynomial, coefficient"),
128 ('AP_0_2', "Axis 1 inverse polynomial, coefficient"),
129 ('AP_1_0', "Axis 1 inverse polynomial, coefficient"),
130 ('AP_1_1', "Axis 1 inverse polynomial, coefficient"),
131 ('AP_2_0', "Axis 1 inverse polynomial, coefficient"),
132 ('BP_ORDER', "Inverse polynomial order, axis 2"),
133 ('BP_0_0', "Axis 2 inverse polynomial, coefficient"),
134 ('BP_0_1', "Axis 2 inverse polynomial, coefficient"),
135 ('BP_0_2', "Axis 2 inverse polynomial, coefficient"),
136 ('BP_1_0', "Axis 2 inverse polynomial, coefficient"),
137 ('BP_1_1', "Axis 2 inverse polynomial, coefficient"),
138 ('BP_2_0', "Axis 2 inverse polynomial, coefficient"),]
139
140
141
142 WFPDB_TEMPLATE = MINIMAL_IMAGE_TEMPLATE+[
143 pyfits.Card(value="-------------------- Original data of observation"),
144 ("DATEORIG", "Original recorded date of the observation"),
145 ("TMS-ORIG", "Start of the observation (logs)"),
146 ("TME-ORIG", "End of the observation (logs)"),
147 ("TIMEFLAG", "Quality flag of the recorded observation time"),
148 ("RA-ORIG", "RA of plate center as given in source"),
149 ("DEC-ORIG", "Dec of plate center as given in source"),
150 ("COORFLAG", "Quality flag of the recorded coordinates"),
151 ("OBJECT", "Observed object or field"),
152 ("OBJTYPE", "Object type as in WFPDB"),
153 ("EXPTIME", " [s] Exposure time of the first exposure"),
154 ("NUMEXP", "Number of exposures"),
155 ]+_makeHeaderSequence(
156 "DATEOR%d", "Original recorded date of the %s exposure"
157 )+_makeHeaderSequence(
158 "TMS-OR%d", "Start of %s exposure (logs)"
159 )+_makeHeaderSequence(
160 "TME-OR%d", "End of %s exposure (logs)"
161 )+_makeHeaderSequence(
162 "OBJECT%d", "Object name for %s exposure"
163 )+_makeHeaderSequence(
164 "OBJTYP%d", "Object type of %s OBJECT"
165 )+_makeHeaderSequence(
166 "EXPTIM%d", " [s] Exposure time %s exposure")+[
167
168 pyfits.Card(value="-------------------- Observatory and instrument"),
169 ("OBSERVAT", "Observatory name"),
170 ("SITENAME", "Observatory site name."),
171 ("SITELONG", " [deg] East longitude of observatory"),
172 ("SITELAT", " [deg] Latitude of observatory"),
173 ("SITEELEV", " [m] Elevation of the observatory"),
174 ("TELESCOP", "Telescope name"),
175 ("TELAPER", " [m] Clear aperture of the telescope"),
176 ("TELFOC", " [m] Focal length of the telescope"),
177 ("TELSCALE", " [arcsec/mm] Plate scale"),
178 ("INSTRUME", "Instrument name"),
179 ("DETNAM", "Detector name"),
180 ("METHOD", "Observation method as in WFPDB"),
181 ("FILTER", "Filter type"),
182 ("PRISM", "Objective prism used"),
183 ("PRISMANG", " [deg] Angle of the objective prism"),
184 ("DISPERS", " [Angstrom/mm] Dispersion"),
185 ("GRATING", "Fix this comment."),
186 ("FOCUS", "Focus value (from logbook)."),
187 ("TEMPERAT", "Air temperature (from logbook)"),
188 ("CALMNESS", "Calmness (seeing conditions), scale 1-5"),
189 ("SHARPNES", "Sharpness, scale 1-5"),
190 ("TRANSPAR", "Transparency, scale 1-5"),
191 ("SKYCOND", "Notes on sky conditions (logs)"),
192 ("OBSERVER", ""),
193 ("OBSNOTES", "Observer notes (logs)"),
194
195 pyfits.Card(value="-------------------- Photographic plate"),
196 ("PLATENUM", "Plate number in logs"),
197 ("WFPDB-ID", "Plate identifier in WFPDB"),
198 ("SERIES", "Series or survey of plate"),
199 ("PLATEFMT", "Informal designation of plate format"),
200 ("PLATESZ1", " [cm] Plate size along axis1"),
201 ("PLATESZ2", " [cm] Plate size along axis2"),
202 ("FOV1", "Field of view along axis 1"),
203 ("FOV2", "Field of view along axis 2"),
204 ("EMULSION", "Type of the photographic emulsion"),
205 ("PQUALITY", "Quality of the plate"),
206 ("PLATNOTE", "Notes about the plate"),
207
208 pyfits.Card(value="-------------------- Derived observation data"),
209 ("DATE-OBS", "UT date and time of obs. start"),
210 ]+_makeHeaderSequence(
211 "DT-OBS%d", "UT d/t of start of %s exposure")+[
212 ("DATE-AVG", "UT d/t mid-point of observation"),
213 ]+_makeHeaderSequence(
214 "DT-AVG%d", "UT d/t mid-point of %s exposure")+[
215 ("DATE-END", "UT d/t end of observation"),
216 ]+_makeHeaderSequence(
217 "DT-END%d", "UT d/t of end of %s exposure")+[
218 ("YEAR", "Julian year at start of obs"),
219 ]+_makeHeaderSequence(
220 "YEAR%d", "Julian year at start of %s obs")+[
221 ("YEAR-AVG", "Julian year at mid-point of obs"),
222 ]+_makeHeaderSequence(
223 "YR-AVG%d", "Julian year at mid-point of %s obs")+[
224 ("JD", "Julian date at start of obs"),
225 ]+_makeHeaderSequence(
226 "JD%d", "Julian date at start of %s obs")+[
227 ("JD-AVG", "Julian date at mid-point of obs")
228 ]+_makeHeaderSequence(
229 "JD-AVG%d", "Julian date at mid-point of %s obs")+[
230 ("JD-AVG", "Julian date at mid-point of obs"),
231 ]+_makeHeaderSequence(
232 "JD-AVG%d", "Julian date at mid-point of %s obs")+[
233 ("RA", "ICRS center of plate RA h:m:s"),
234 ("DEC", "ICRS center of plate Dec d:m:s"),
235 ("RA_DEG", "[deg] ICRS center of plate RA"),
236 ("DEC_DEG", "[deg] ICRS center of plate Dec"),
237 ]+_makeHeaderSequence(
238 "RA_DEG%d", " [deg] ICRS center RA %s obs"
239 )+_makeHeaderSequence(
240 "DEC_DE%d", " [deg] ICRS center DEC %s obs")+[
241 ("AIRMASS", "Airmass at mean epoch"),
242 ("HA", "Hour angle at mean epoch"),
243 ("ZD", "Zenith distance at mean epoch"),
244
245 pyfits.Card(value="-------------------- Scan details"),
246 ("SCANNER", "Scanner hardware used"),
247 ("SCANRES1", " [in-1] Scan resolution along axis 1"),
248 ("SCANRES2", " [in-1] Scan resolution along axis 2"),
249 ("PIXSIZE1", " [um] Pixel size along axis 1"),
250 ("PIXSIZE2", " [um] Pixel size along axis 2"),
251 ("SCANSOFT", "Scan software used"),
252 ("SCANGAM", "Scan gamma value"),
253 ("SCANFOC", "Scan focus"),
254 ("WEDGE", "Photometric step-wedge type"),
255 ("DATESCAN", "UT scan date and time"),
256 ("SCANAUTH", "Author of the scan"),
257 ("SCANNOTE", "Notes about the scan"),
258 ("DATAMIN", "Min pixel value in image"),
259 ("DATAMAX", "Max pixel value in image"),
260
261 pyfits.Card(value="-------------------- Data files"),
262 ("FILENAME", "Filename of this file"),
263 ("FN-WEDGE", "Filename of the wedge scan"),
264 ("FN-PRE", "Filename of the preview image"),
265 ("FN-COVER", "Filename of the envelope image"),
266 ("FN-LOGB", "Filename of the logbook image"),
267 ("ORIGIN", "Origin of this file"),
268 ("DATE", "File last changed"),
269
270 ] + WCS_TEMPLATE + [
271
272 pyfits.Card(value="-------------------- Other header cards"),]
273
274
275 _TEMPLATE_NAMES = [
276 ("minimal", MINIMAL_IMAGE_TEMPLATE),
277 ("wfpdb", WFPDB_TEMPLATE),]
278
279
281 """registers a named FITS header template.
282
283 Registering lets DaCHS figure out the template from a history entry
284 it leaves, so it's certainly a good idea to do that.
285
286 For templateName, use something containing a bit of your signature
287 (e.g., ariAncientPlate rather than just ancientPlate).
288 """
289 _TEMPLATE_NAMES.append((templateName, template))
290
291
293 """returns the FITS template sequence for templateName.
294
295 A NotFoundError is raised if no such template exists.
296 """
297 for name, template in _TEMPLATE_NAMES:
298 if name==templateName:
299 return template
300 raise base.NotFoundError(templateName, "FITS template",
301 "registred templates", hint="If you used a custom template,"
302 " have you called fitstricks.registerTemplate(name, template)"
303 " for it?")
304
305
307 """returns the name under which the FITS template has been registred.
308 """
309 for name, namedTemplate in _TEMPLATE_NAMES:
310 if template is namedTemplate:
311 return name
312 raise base.NotFoundError("template "+str(id(template)),
313 "FITS template",
314 "registred templates", hint="If you used a custom template,"
315 " have you called fitstricks.registerTemplate(name, template)"
316 " for it?")
317
318
320 """returns the template name used for generating hdr.
321
322 A ReportableError is raised if the info signature is missing.
323 """
324 for card in hdr["HISTORY"]:
325 mat = re.search("GAVO DaCHS template used: (\w+)", card)
326 if mat:
327 return mat.group(1)
328 raise base.ReportableError("DaCHS template signature not found.",
329 hint="This means that a function needed to figure out which"
330 " FITS template DaCHS used to generate that header, and no"
331 " such information was found in the Header's HISTORY cards."
332 " Either this file hasn't been written by DaCHS FITS templating"
333 " engine, or some intervening thing hosed the history.")
334
335
337 """helps makeHeaderFromTemplate.
338
339 Specifically, it moves items in values mentioned in template into
340 header in template's order. hdr and values are modified in that process.
341 """
342 for tp in template:
343 if isinstance(tp, pyfits.Card):
344 tp.value = values.pop(tp.keyword, tp.value)
345 hdr.append(tp, end=True)
346 else:
347 key, comment = tp
348 argKey = key.replace("-", "_")
349 if values.get(argKey) is not None:
350 try:
351 val = values[argKey]
352 if isinstance(val, unicode):
353 val = val.encode("ascii")
354
355 hdr.append(pyfits.Card(key, val, comment), end=True)
356 except Exception as ex:
357 if hasattr(ex, "args") and isinstance(ex.args[0], basestring):
358 ex.args = ("While constructing card %s: %s"%(
359 key, ex.args[0]),)+ex.args[1:]
360 raise
361
362 values.pop(argKey, None)
363
364
366 """helps makeHeaderFromTemplate.
367
368 Specifically, it copies over all cards from oldHder to newHdr not yet
369 present there. It will also move over history and comment cards.
370
371 This will modify newHdr in place.
372 """
373 commentCs, historyCs = [], []
374
375 for card in oldHdr.cards:
376 if card.keyword=="COMMENT":
377 commentCs.append(card)
378 elif card.keyword=="HISTORY":
379 if not "GAVO DaCHS template used" in card.value:
380 historyCs.append(card)
381 elif card.keyword:
382 if card.keyword not in newHdr:
383 newHdr.append(card, end=True)
384
385 for card in historyCs:
386 newHdr.append(card, end=True)
387 newHdr.append(pyfits.Card(value=""), end=True)
388 for card in commentCs:
389 newHdr.append(card, end=True)
390
391
393 """returns a new pyfits.Header from template with values filled in.
394
395 template usually is the name of a template previously registered with
396 registerTemplate, or one of DaCHS predefined template names (currently,
397 minimal and wfpdb). In a pinch, you can also pass in an immediate
398 headers.
399
400 originalHeader can be a pre-existing header; the history and comment
401 cards are copied over from it, and if any of its other cards have not
402 yet been added to the header, they will be added in the order that they
403 apprear there.
404
405 values for which no template item is given are added in random order
406 after the template unless an originalHeader is passed. In that case,
407 they are assumed to originate there and are ignored.
408 """
409 values = values.copy()
410 hdr = pyfits.Header()
411
412 if isinstance(template, basestring):
413 templateName = template
414 template = getTemplateForName(templateName)
415 else:
416 try:
417 templateName = getNameForTemplate(template)
418 except base.NotFoundError:
419 base.notifyWarning("Using anonymous FITS template.")
420 templateName = "anonymous"
421
422 _applyTemplate(hdr, template, values)
423
424 if originalHeader:
425 _copyMissingCards(hdr, originalHeader)
426 elif values:
427 base.ui.notifyWarning("The following values were left after"
428 " applying a FITS template and will be added in random order: %s"%
429 (", ").join(values.keys()))
430 for key, value in values.iteritems():
431 hdr.append(pyfits.Card(key, value), end=True)
432
433
434 hdr.add_history("GAVO DaCHS template used: "+templateName)
435 return hdr
436
437
439 """return hdr updated with kwargs.
440
441 hdr is assumed to have been created with makeHeaderFromTemplate
442 and contain the template name in a history entry.
443
444 You can pass in templateName to keep DaCHS from trying to get things
445 from the header.
446
447 [It is probably better to use makeHeaderFromTemplate directly, passing
448 in the orginalHeader; that preserves the order of non-templated
449 headers].
450 """
451 if templateName is None:
452 templateName = getTemplateNameFromHistory(hdr)
453 template = getTemplateForName(templateName)
454
455 vals = getHeaderAsDict(hdr)
456 vals.update(kwargs)
457
458 res = makeHeaderFromTemplate(template, originalHeader=hdr, **vals)
459 return res
460