Commit 013a064ea8b9ba4922bc1c73eebb9a3336a9b169

Authored by Jürgen Knödlseder
1 parent 70ed8749

Adapt script to gammalib-00-08-00 version.

When releasing ctools-00-07-00 we forgot to update the script.
Showing 1 changed file with 935 additions and 934 deletions
scripts/cta_root2caldb.py
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 # This script generates IRFs in the CALDB format of HEASARC using ROOT 2D 3 # This script generates IRFs in the CALDB format of HEASARC using ROOT 2D
4 # performance files. 4 # performance files.
5 # ------------------------------------------------------------------------- 5 # -------------------------------------------------------------------------
6 -# Copyright (C) 2011-2012 Juergen Knoedlseder 6 +# Copyright (C) 2011-2014 Juergen Knoedlseder
7 # ------------------------------------------------------------------------- 7 # -------------------------------------------------------------------------
8 # 8 #
9 # This program is free software: you can redistribute it and/or modify 9 # This program is free software: you can redistribute it and/or modify
@@ -37,941 +37,942 @@ import os @@ -37,941 +37,942 @@ import os
37 # CalDB class # 37 # CalDB class #
38 # =========== # 38 # =========== #
39 class caldb(): 39 class caldb():
40 - """  
41 - """  
42 - def __init__(self, inst, obsid, path=""):  
43 - """  
44 - CALDB constructor. The constructor will create one CALDB entry for  
45 - an observation.  
46 -  
47 - Parameters:  
48 - inst - Instrument string.  
49 - obsid - Observation ID string.  
50 - Keywords:  
51 - path - CALDB Root path.  
52 - """  
53 - # Store root path  
54 - self.path = path  
55 -  
56 - # Initialise some members  
57 - self.cif = None  
58 - self.irf = None  
59 - self.ea = None  
60 - self.psf = None  
61 - self.edisp = None  
62 - self.bgd = None  
63 - self.hdu_cif = None  
64 -  
65 - # Store UTC date of job execution  
66 - self.utc = datetime.utcnow().isoformat()[:19]  
67 -  
68 - # Set CALDB information  
69 - self.cal_tel = "CTA"  
70 - self.cal_inst = inst.upper()  
71 - self.cal_obsid = obsid  
72 - self.cal_det = "NONE"  
73 - self.cal_flt = "NONE"  
74 - self.cal_class = "BCF"  
75 - self.cal_type = "DATA"  
76 - self.cal_qual = 0 # 0=good, 1=bad, 2=dubious, ...  
77 - self.cal_date = "11/01/01"  
78 - self.val_date = "2011-01-01"  
79 - self.val_time = "00:00:00"  
80 - self.ref_time = 51544.0  
81 - self.cal_version = "VERSION(DC1)"  
82 - self.cal_cut = "CLASS(BEST)"  
83 - self.cal_analysis = "ANALYSIS(IFAE)"  
84 - #  
85 - self.ea_name = "EFF_AREA"  
86 - self.ea_doc = "CAL/GEN/92-019"  
87 - self.ea_bounds = [self.cal_version, self.cal_cut, self.cal_analysis]  
88 - self.ea_desc = "CTA effective area"  
89 - #  
90 - self.psf_name = "RPSF"  
91 - self.psf_doc = "CAL/GEN/92-020"  
92 - self.psf_bounds = [self.cal_version, self.cal_cut, self.cal_analysis]  
93 - self.psf_desc = "CTA point spread function"  
94 - #  
95 - self.edisp_name = "EDISP"  
96 - self.edisp_doc = "???"  
97 - self.edisp_bounds = [self.cal_version, self.cal_cut, self.cal_analysis]  
98 - self.edisp_desc = "CTA energy dispersion"  
99 - #  
100 - self.bgd_name = "BGD"  
101 - self.bgd_doc = "???"  
102 - self.bgd_bounds = [self.cal_version, self.cal_cut, self.cal_analysis]  
103 - self.bgd_desc = "CTA background"  
104 -  
105 - # Create directory structure  
106 - self.make_dirs()  
107 -  
108 - # Return  
109 - return  
110 -  
111 - def __del__(self):  
112 - """  
113 - Destructor. Close all FITS files.  
114 - """  
115 - # Close calibration  
116 - self.close()  
117 -  
118 - # Return  
119 - return  
120 -  
121 - def make_dirs(self):  
122 - """  
123 - Generate CALDB directory structure for one observation identifier.  
124 - The structure is given by  
125 -  
126 - data/<tel>/<inst>/bcf/<obsid>  
127 -  
128 - where <tel> is "cta" and <inst> is the instrument specified in the  
129 - CALDB constructor (the instrument may be used for different array  
130 - configurations).  
131 -  
132 - Parameters:  
133 - None  
134 - Keywords:  
135 - None  
136 - """  
137 - # Set base path  
138 - self.base_dir = "data"  
139 - self.base_dir += "/"+self.cal_tel.lower()  
140 - self.base_dir += "/"+self.cal_inst.lower()  
141 -  
142 - # Set directory names  
143 - self.bcf_dir = self.base_dir+"/bcf"  
144 - self.obs_dir = self.bcf_dir+"/"+self.cal_obsid  
145 - self.ea_dir = self.obs_dir  
146 - self.psf_dir = self.obs_dir  
147 - self.edisp_dir = self.obs_dir  
148 - self.bgd_dir = self.obs_dir  
149 -  
150 - # Optionally prefix path  
151 - if len(self.path) > 0:  
152 - self.base_path = self.path+"/"+self.base_dir  
153 - self.bcf_path = self.path+"/"+self.bcf_dir  
154 - self.obs_path = self.path+"/"+self.obs_dir  
155 - self.ea_path = self.path+"/"+self.ea_dir  
156 - self.psf_path = self.path+"/"+self.psf_dir  
157 - self.edisp_path = self.path+"/"+self.edisp_dir  
158 - self.bgd_path = self.path+"/"+self.bgd_dir  
159 - else:  
160 - self.base_path = self.base_dir  
161 - self.bcf_path = self.bcf_dir  
162 - self.obs_path = self.obs_dir  
163 - self.ea_path = self.ea_dir  
164 - self.psf_path = self.psf_dir  
165 - self.edisp_path = self.edisp_dir  
166 - self.bgd_path = self.bgd_dir  
167 -  
168 - # Create directory structure  
169 - if not os.path.isdir(self.ea_path):  
170 - os.makedirs(self.ea_path)  
171 - if not os.path.isdir(self.psf_path):  
172 - os.makedirs(self.psf_path)  
173 - if not os.path.isdir(self.edisp_path):  
174 - os.makedirs(self.edisp_path)  
175 - if not os.path.isdir(self.bgd_path):  
176 - os.makedirs(self.bgd_path)  
177 -  
178 - # Return  
179 - return  
180 -  
181 - def open(self, version, split=False, clobber=True):  
182 - """  
183 - Open existing or create new calibration. The actual version will put  
184 - all calibrations in the same file, although each part of the response  
185 - function will have its own logical name. We can thus easily modify  
186 - the script to put each calibration information in a separate file.  
187 -  
188 - Parameters:  
189 - version - Filename version  
190 - Keywords:  
191 - split - Split IRF over several files?  
192 - clobber - Overwrite existing files?  
193 - """  
194 - # Set calibrate file names  
195 - if split:  
196 - self.ea_file = "ea_"+version+".fits"  
197 - self.psf_file = "psf_"+version+".fits"  
198 - self.edisp_file = "edisp_"+version+".fits"  
199 - self.bgd_file = "bgd_"+version+".fits"  
200 - else:  
201 - self.ea_file = "irf_"+version+".fits"  
202 - self.psf_file = "irf_"+version+".fits"  
203 - self.edisp_file = "irf_"+version+".fits"  
204 - self.bgd_file = "irf_"+version+".fits"  
205 -  
206 - # Open calibration database index  
207 - self.cif = GFits(self.base_path+"/caldb.indx", True)  
208 -  
209 - # If file has no CIF extension than create it now  
210 - try:  
211 - self.hdu_cif = self.cif.table("CIF")  
212 - except:  
213 - self.create_cif()  
214 - self.hdu_cif = self.cif.table("CIF")  
215 -  
216 - # Set filenames  
217 - ea_filename = self.ea_path+"/"+self.ea_file  
218 - psf_filename = self.psf_path+"/"+self.psf_file  
219 - edisp_filename = self.edisp_path+"/"+self.edisp_file  
220 - bgd_filename = self.bgd_path+"/"+self.bgd_file  
221 -  
222 - # Open files  
223 - if split:  
224 - self.ea = GFits(ea_filename, True)  
225 - self.psf = GFits(psf_filename, True)  
226 - self.edisp = GFits(edisp_filename, True)  
227 - self.bgd = GFits(bgd_filename, True)  
228 - else:  
229 - self.irf = GFits(ea_filename, True)  
230 - self.ea = self.irf  
231 - self.psf = self.irf  
232 - self.edisp = self.irf  
233 - self.bgd = self.irf  
234 -  
235 - # Open HDUs  
236 - self.open_ea()  
237 - self.open_psf()  
238 - self.open_edisp()  
239 - self.open_bgd()  
240 -  
241 - # Return  
242 - return  
243 -  
244 - def close(self):  
245 - """  
246 - Close calibration FITS files.  
247 -  
248 - Parameters:  
249 - None  
250 - Keywords:  
251 - None  
252 - """  
253 - # Add information to CIF. We do this now as before we did not  
254 - # necessarily have all the information at hand (in particular  
255 - # about the boundaries)  
256 - self.add_cif_info()  
257 -  
258 - # Close CIF  
259 - if self.cif != None:  
260 - self.cif.save(True)  
261 - self.cif.close()  
262 - self.cif = None  
263 -  
264 - # If IRF file exists then close it now. All file pointers will  
265 - # be set to None  
266 - if self.irf != None:  
267 - self.irf.save(True)  
268 - self.irf.close()  
269 - self.irf = None  
270 - self.ea = None  
271 - self.psf = None  
272 - self.edisp = None  
273 - self.bgd = None  
274 -  
275 - # ... otherwise we have split files, so we have to close them  
276 - # all  
277 - else:  
278 -  
279 - # Close effective area file  
280 - if self.ea != None:  
281 - self.ea.save(True)  
282 - self.ea.close()  
283 - self.ea = None  
284 -  
285 - # Close point spread function file  
286 - if self.psf != None:  
287 - self.psf.save(True)  
288 - self.psf.close()  
289 - self.psf = None  
290 -  
291 - # Close energy dispersion file  
292 - if self.edisp != None:  
293 - self.edisp.save(True)  
294 - self.edisp.close()  
295 - self.edisp = None  
296 -  
297 - # Close background file  
298 - if self.bgd != None:  
299 - self.bgd.save(True)  
300 - self.bgd.close()  
301 - self.bgd = None  
302 -  
303 - # Return  
304 - return  
305 -  
306 - def create_cif(self):  
307 - """  
308 - Create Calibration Index File (CIF) extension in CIF FITS file.  
309 -  
310 - Parameters:  
311 - None  
312 - Keywords:  
313 - None  
314 - """  
315 - # Create binary table  
316 - table = GFitsBinTable()  
317 -  
318 - # Set boundary  
319 -  
320 - # Attach columns. Reference: CAL/GEN/92-008  
321 - table.append_column(GFitsTableStringCol("TELESCOP", 0, 10))  
322 - table.append_column(GFitsTableStringCol("INSTRUME", 0, 10))  
323 - table.append_column(GFitsTableStringCol("DETNAM", 0, 20))  
324 - table.append_column(GFitsTableStringCol("FILTER", 0, 10))  
325 - table.append_column(GFitsTableStringCol("CAL_DEV", 0, 20))  
326 - table.append_column(GFitsTableStringCol("CAL_DIR", 0, 70))  
327 - table.append_column(GFitsTableStringCol("CAL_FILE", 0, 40))  
328 - table.append_column(GFitsTableStringCol("CAL_CLAS", 0, 3))  
329 - table.append_column(GFitsTableStringCol("CAL_DTYP", 0, 4))  
330 - table.append_column(GFitsTableStringCol("CAL_CNAM", 0, 20))  
331 - table.append_column(GFitsTableStringCol("CAL_CBD", 0, 70, 9))  
332 - table.append_column(GFitsTableShortCol("CAL_XNO", 0))  
333 - table.append_column(GFitsTableStringCol("CAL_VSD", 0, 10))  
334 - table.append_column(GFitsTableStringCol("CAL_VST", 0, 8))  
335 - table.append_column(GFitsTableDoubleCol("REF_TIME", 0))  
336 - table.append_column(GFitsTableShortCol("CAL_QUAL", 0))  
337 - table.append_column(GFitsTableStringCol("CAL_DATE", 0, 8))  
338 - table.append_column(GFitsTableStringCol("CAL_DESC", 0, 70))  
339 -  
340 - # Set keywords. Reference: CAL/GEN/92-008  
341 - table.extname("CIF")  
342 - table.card("CIFVERSN", "1992a", "Version of CIF format")  
343 -  
344 - # Attach table to FITS file. Note that at this moment the  
345 - # FITS table gets connected to the FITS file. Yet since  
346 - # nothing was yet written to the FITS file, we cannot read  
347 - # anything from it.  
348 - self.cif.append(table)  
349 -  
350 - # Return  
351 - return  
352 -  
353 - def add_cif_info(self):  
354 - """  
355 - Add information to CIF extension.  
356 -  
357 - Parameters:  
358 - None  
359 - Keywords:  
360 - None  
361 - """  
362 - # Append 3 rows to CIF extension  
363 - self.hdu_cif.append_rows(3)  
364 -  
365 - # Add generic information for these 3 rows  
366 - for i in range(3):  
367 -  
368 - # Set row number  
369 - row = i+self.hdu_cif.nrows()-3  
370 -  
371 - # Set element  
372 - self.hdu_cif["TELESCOP"][row] = self.cal_tel  
373 - self.hdu_cif["INSTRUME"][row] = self.cal_inst  
374 - self.hdu_cif["DETNAM"][row] = self.cal_det  
375 - self.hdu_cif["FILTER"][row] = self.cal_flt  
376 - self.hdu_cif["CAL_DEV"][row] = "ONLINE"  
377 - self.hdu_cif["CAL_CLAS"][row] = self.cal_class  
378 - self.hdu_cif["CAL_DTYP"][row] = self.cal_type  
379 - self.hdu_cif["CAL_VSD"][row] = self.val_date  
380 - self.hdu_cif["CAL_VST"][row] = self.val_time  
381 - self.hdu_cif["REF_TIME"][row] = self.ref_time  
382 - self.hdu_cif["CAL_QUAL"][row] = self.cal_qual  
383 - self.hdu_cif["CAL_DATE"][row] = self.cal_date  
384 -  
385 - # Add effective area information  
386 - row = self.hdu_cif.nrows()-3  
387 - self.hdu_cif["CAL_DIR"][row] = self.ea_dir  
388 - self.hdu_cif["CAL_FILE"][row] = self.ea_file  
389 - self.hdu_cif["CAL_CNAM"][row] = self.ea_name  
390 - self.hdu_cif["CAL_DESC"][row] = self.ea_desc  
391 - self.hdu_cif["CAL_XNO"][row] = 1  
392 - for n in range(9):  
393 - if n >= len(self.ea_bounds):  
394 - self.hdu_cif["CAL_CBD"][row, n] = "NONE"  
395 - else:  
396 - self.hdu_cif["CAL_CBD"][row, n] = self.ea_bounds[n]  
397 -  
398 - # Add point spread function information  
399 - row = self.hdu_cif.nrows()-2  
400 - self.hdu_cif["CAL_DIR"][row] = self.psf_dir  
401 - self.hdu_cif["CAL_FILE"][row] = self.psf_file  
402 - self.hdu_cif["CAL_CNAM"][row] = self.psf_name  
403 - self.hdu_cif["CAL_DESC"][row] = self.psf_desc  
404 - self.hdu_cif["CAL_XNO"][row] = 1  
405 - for n in range(9):  
406 - if n >= len(self.psf_bounds):  
407 - self.hdu_cif["CAL_CBD"][row, n] = "NONE"  
408 - else:  
409 - self.hdu_cif["CAL_CBD"][row, n] = self.psf_bounds[n]  
410 -  
411 - # Add energy dispersion information  
412 - row = self.hdu_cif.nrows()-1  
413 - self.hdu_cif["CAL_DIR"][row] = self.edisp_dir  
414 - self.hdu_cif["CAL_FILE"][row] = self.edisp_file  
415 - self.hdu_cif["CAL_CNAM"][row] = self.edisp_name  
416 - self.hdu_cif["CAL_DESC"][row] = self.edisp_desc  
417 - self.hdu_cif["CAL_XNO"][row] = 1  
418 - for n in range(9):  
419 - if n >= len(self.edisp_bounds):  
420 - self.hdu_cif["CAL_CBD"][row, n] = "NONE"  
421 - else:  
422 - self.hdu_cif["CAL_CBD"][row, n] = self.edisp_bounds[n]  
423 -  
424 - # Add background information  
425 - row = self.hdu_cif.nrows()-1  
426 - self.hdu_cif["CAL_DIR"][row] = self.bgd_dir  
427 - self.hdu_cif["CAL_FILE"][row] = self.bgd_file  
428 - self.hdu_cif["CAL_CNAM"][row] = self.bgd_name  
429 - self.hdu_cif["CAL_DESC"][row] = self.bgd_desc  
430 - self.hdu_cif["CAL_XNO"][row] = 1  
431 - for n in range(9):  
432 - if n >= len(self.bgd_bounds):  
433 - self.hdu_cif["CAL_CBD"][row, n] = "NONE"  
434 - else:  
435 - self.hdu_cif["CAL_CBD"][row, n] = self.bgd_bounds[n]  
436 -  
437 - # Return  
438 - return  
439 -  
440 - def open_ea(self):  
441 - """  
442 - Open effective area extension. If no extension was found  
443 - then create one. We do not yet set any data as we don't  
444 - know the dimensions of the table yet.  
445 -  
446 - Parameters:  
447 - None  
448 - Keywords:  
449 - None  
450 - """  
451 - # Get extension. If it does not exist then create it now.  
452 - try:  
453 - self.hdu_ea = self.ea.table("EFFECTIVE AREA")  
454 - except:  
455 - # Create binary table  
456 - table = GFitsBinTable()  
457 -  
458 - # Set extension name  
459 - table.extname("EFFECTIVE AREA")  
460 -  
461 - # Set OGIP keywords  
462 - self.set_ogip_keywords(table, self.ea_doc, \  
463 - ["RESPONSE", self.ea_name])  
464 -  
465 - # Append table  
466 - self.ea.append(table)  
467 -  
468 - # Get extension  
469 - self.hdu_ea = self.ea.table("EFFECTIVE AREA")  
470 -  
471 - # Return  
472 - return  
473 -  
474 - def open_psf(self):  
475 - """  
476 - Open point spread function extension. If no extension was  
477 - found then create one. We do not yet set any data as we don't  
478 - know the dimensions of the table yet.  
479 -  
480 - Parameters:  
481 - None  
482 - Keywords:  
483 - None  
484 - """  
485 - # Get extension. If it does not exist then create it now.  
486 - try:  
487 - self.hdu_psf = self.psf.table("POINT SPREAD FUNCTION")  
488 - except:  
489 - # Create binary table  
490 - table = GFitsBinTable()  
491 -  
492 - # Set extension name  
493 - table.extname("POINT SPREAD FUNCTION")  
494 -  
495 - # Set OGIP keywords  
496 - self.set_ogip_keywords(table, self.psf_doc, \  
497 - ["RESPONSE", self.psf_name])  
498 -  
499 - # Append table  
500 - self.psf.append(table)  
501 -  
502 - # Get extension  
503 - self.hdu_psf = self.psf.table("POINT SPREAD FUNCTION")  
504 -  
505 - # Return  
506 - return  
507 -  
508 - def open_edisp(self):  
509 - """  
510 - Open energy dispersion extension. If no extension was found  
511 - then create one. We do not yet set any data as we don't  
512 - know the dimensions of the table yet.  
513 -  
514 - Parameters:  
515 - None  
516 - Keywords:  
517 - None  
518 - """  
519 - # Get extension. If it does not exist then create it now.  
520 - try:  
521 - self.hdu_edisp = self.edisp.table("ENERGY DISPERSION")  
522 - except:  
523 - # Create binary table  
524 - table = GFitsBinTable()  
525 -  
526 - # Set extension name  
527 - table.extname("ENERGY DISPERSION")  
528 -  
529 - # Set OGIP keywords  
530 - self.set_ogip_keywords(table, self.edisp_doc, \  
531 - ["RESPONSE", self.edisp_name])  
532 -  
533 - # Append table  
534 - self.edisp.append(table)  
535 -  
536 - # Get extension  
537 - self.hdu_edisp = self.edisp.table("ENERGY DISPERSION")  
538 -  
539 - # Return  
540 - return  
541 -  
542 - def open_bgd(self):  
543 - """  
544 - Open background extension. If no extension was found  
545 - then create one. We do not yet set any data as we don't  
546 - know the dimensions of the table yet.  
547 -  
548 - Parameters:  
549 - None  
550 - Keywords:  
551 - None  
552 - """  
553 - # Get extension. If it does not exist then create it now.  
554 - try:  
555 - self.hdu_bgd = self.bgd.table("BACKGROUND")  
556 - except:  
557 - # Create binary table  
558 - table = GFitsBinTable()  
559 -  
560 - # Set extension name  
561 - table.extname("BACKGROUND")  
562 -  
563 - # Set OGIP keywords  
564 - self.set_ogip_keywords(table, self.bgd_doc, \  
565 - ["RESPONSE", self.bgd_name])  
566 -  
567 - # Append table  
568 - self.bgd.append(table)  
569 -  
570 - # Get extension  
571 - self.hdu_bgd = self.bgd.table("BACKGROUND")  
572 -  
573 - # Return  
574 - return  
575 -  
576 - def set_ogip_keywords(self, hdu, hdudoc, hduclas):  
577 - """  
578 - Set standard OGIP keywords for extension.  
579 -  
580 - Parameters:  
581 - hdu - Header Data Unit.  
582 - hdudoc - Documentation reference string  
583 - hduclas - Array of HDUCLAS fields  
584 - Keywords:  
585 - None  
586 - """  
587 - # Set keywords  
588 - hdu.card("ORIGIN", "IRAP", "Name of organization making this file")  
589 - hdu.card("DATE", self.utc, "File creation date (YYYY-MM-DDThh:mm:ss UTC)")  
590 - hdu.card("TELESCOP", self.cal_tel, "Name of telescope")  
591 - hdu.card("INSTRUME", self.cal_inst, "Name of instrument")  
592 - hdu.card("DETNAM", self.cal_det, "Name of detector")  
593 - hdu.card("HDUCLASS", "OGIP", "")  
594 - hdu.card("HDUDOC", hdudoc, "")  
595 - for i, item in enumerate(hduclas):  
596 - key = "HDUCLAS%d" % (i+1)  
597 - hdu.card(key, item, "")  
598 - hdu.card("HDUVERS", "1.0.0", "")  
599 -  
600 - # Return  
601 - return  
602 -  
603 - def make_2D(self, array, hdu, name, unit, scale=1.0):  
604 - """  
605 - Make 2D matrix as function of energy and offset angle from a  
606 - ROOT 2D histogram. If the HDU has already the energy and  
607 - offset angle columns, this method will simply add another data  
608 - column.  
609 - If name==None, the method will not append any data column.  
610 -  
611 - Parameters:  
612 - array - ROOT 2D histogram.  
613 - hdu - FITS HDU.  
614 - name - Data column name.  
615 - unit - Data unit.  
616 - Keywords:  
617 - scale - Scaling factor for histogram values.  
618 - """  
619 - # Extract energy and offset angle vectors  
620 - energies = array.GetXaxis()  
621 - offsets = array.GetYaxis()  
622 - neng = energies.GetNbins()  
623 - noffset = offsets.GetNbins()  
624 -  
625 - # Attach columns to HDU. We do this only if they do not yet  
626 - # exist. This allows adding columns to the same HDU in successive  
627 - # calls of this method  
628 - #  
629 - # ENERG_LO  
630 - if not hdu.hascolumn("ENERG_LO"):  
631 - hdu.append_column(GFitsTableFloatCol("ENERG_LO", 1, neng))  
632 - hdu["ENERG_LO"].unit("TeV")  
633 - for ieng in range(neng):  
634 - e_lo = pow(10.0, energies.GetBinLowEdge(ieng+1))  
635 - hdu["ENERG_LO"][0,ieng] = e_lo  
636 - #  
637 - # ENERG_HI  
638 - if not hdu.hascolumn("ENERG_HI"):  
639 - hdu.append_column(GFitsTableFloatCol("ENERG_HI", 1, neng))  
640 - hdu["ENERG_HI"].unit("TeV")  
641 - for ieng in range(neng):  
642 - e_hi = pow(10.0, energies.GetBinUpEdge(ieng+1))  
643 - hdu["ENERG_HI"][0,ieng] = e_hi  
644 - #  
645 - # THETA_LO  
646 - if not hdu.hascolumn("THETA_LO"):  
647 - hdu.append_column(GFitsTableFloatCol("THETA_LO", 1, noffset))  
648 - hdu["THETA_LO"].unit("deg")  
649 - for ioff in range(noffset):  
650 - o_lo = offsets.GetBinLowEdge(ioff+1)  
651 - hdu["THETA_LO"][0,ioff] = o_lo  
652 -  
653 - #  
654 - # THETA_LO  
655 - if not hdu.hascolumn("THETA_HI"):  
656 - hdu.append_column(GFitsTableFloatCol("THETA_HI", 1, noffset))  
657 - hdu["THETA_HI"].unit("deg")  
658 - for ioff in range(noffset):  
659 - o_hi = offsets.GetBinUpEdge(ioff+1)  
660 - hdu["THETA_HI"][0,ioff] = o_hi  
661 - #  
662 - # "NAME"  
663 - if name != None and not hdu.hascolumn(name):  
664 - hdu.append_column(GFitsTableFloatCol(name, 1, neng*noffset))  
665 - hdu[name].unit(unit)  
666 - hdu[name].dim([neng, noffset])  
667 - for ioff in range(noffset):  
668 - for ieng in range(neng):  
669 - index = ieng + ioff * neng  
670 - value = array.GetBinContent(ieng+1,ioff+1)  
671 - hdu[name][0,index] = value * scale  
672 -  
673 - # Collect boundary information  
674 - bd_eng = "ENERG(%.4f-%.2f)TeV" % \  
675 - (pow(10.0, energies.GetBinLowEdge(1)), \  
676 - pow(10.0, energies.GetBinUpEdge(neng)))  
677 - bd_off = "THETA(%.2f-%.2f)deg" % \  
678 - (offsets.GetBinLowEdge(1), \  
679 - offsets.GetBinUpEdge(noffset))  
680 - bd_phi = "PHI(0-360)deg"  
681 - bounds = [bd_eng, bd_off, bd_phi]  
682 -  
683 - # Return boundary information  
684 - return bounds  
685 -  
686 - def root2caldb(self, filename):  
687 - """  
688 - Translate ROOT to CALDB information.  
689 -  
690 - Parameters:  
691 - filename - ROOT 2D performance filename.  
692 - """  
693 - # Open ROOT performance file  
694 - file = TFile(filename)  
695 -  
696 - # Create effective area  
697 - self.root2ea(file)  
698 -  
699 - # Create point spread function  
700 - self.root2psf(file)  
701 -  
702 - # Create energy dispersion  
703 - self.root2edisp(file)  
704 -  
705 - # Create background  
706 - self.root2bgd(file)  
707 -  
708 - # Return  
709 - return  
710 -  
711 - def root2ea(self, file):  
712 - """  
713 - Translate ROOT to CALDB effective area extension. The following ROOT  
714 - histograms are used:  
715 -  
716 - EffectiveAreaEtrue_offaxis -> EFFAREA  
717 - EffectiveArea_offaxis -> EFFAREA_RECO  
718 -  
719 - Parameters:  
720 - file - ROOT file.  
721 - Keywords:  
722 - None  
723 - """  
724 - # Continue only if effective area HDU has been opened  
725 - if self.hdu_ea != None:  
726 -  
727 - # Allocate ROOT 2D arrays  
728 - etrue = TH2F()  
729 - ereco = TH2F()  
730 - file.GetObject("EffectiveAreaEtrue_offaxis", etrue)  
731 - file.GetObject("EffectiveArea_offaxis", ereco)  
732 - #print etrue.GetXaxis().GetBinLowEdge(1), etrue.GetXaxis().GetBinUpEdge(etrue.GetXaxis().GetNbins())  
733 - #print ereco.GetXaxis().GetBinLowEdge(1), ereco.GetXaxis().GetBinUpEdge(ereco.GetXaxis().GetNbins())  
734 -  
735 - # Rebin etrue histogram  
736 - etrue.RebinX(10)  
737 - neng = etrue.GetXaxis().GetNbins()  
738 - noffset = etrue.GetYaxis().GetNbins()  
739 - for ioff in range(noffset):  
740 - for ieng in range(neng):  
741 - value = etrue.GetBinContent(ieng+1,ioff+1) / 10.0  
742 - etrue.SetBinContent(ieng+1,ioff+1,value)  
743 -  
744 - # Write boundaries (use Ereco boundaries)  
745 - bounds = self.make_2D(ereco, self.hdu_ea, None, "m2")  
746 - for b in bounds:  
747 - self.ea_bounds.append(b)  
748 - self.set_cif_keywords(self.hdu_ea, self.ea_name, \  
749 - self.ea_bounds, self.ea_desc)  
750 -  
751 - # EFFAREA  
752 - self.make_2D(etrue, self.hdu_ea, "EFFAREA", "m2")  
753 -  
754 - # EFFAREA_RECO  
755 - self.make_2D(ereco, self.hdu_ea, "EFFAREA_RECO", "m2")  
756 -  
757 - # Return  
758 - return  
759 -  
760 - def root2psf(self, file):  
761 - """  
762 - Translate ROOT to CALDB point spread function extension. The following  
763 - ROOT histograms are used:  
764 -  
765 - 1/(2*pi*SIGMA_1) -> SCALE  
766 - AngRes_offaxis -> SIGMA_1 (scaling: 1/0.8)  
767 - 0.0 -> AMPL_2  
768 - 0.0 -> SIGMA_2  
769 - 0.0 -> AMPL_3  
770 - 0.0 -> SIGMA_3  
771 -  
772 - Parameters:  
773 - file - ROOT file.  
774 - Keywords:  
775 - None  
776 - """  
777 - # Continue only if point spread function HDU has been opened  
778 - if self.hdu_psf != None:  
779 -  
780 - # Allocate ROOT 2D array  
781 - r68 = TH2F()  
782 -  
783 - # Read 68% containment histogram  
784 - file.GetObject("AngRes_offaxis", r68)  
785 - neng = r68.GetXaxis().GetNbins()  
786 - noffset = r68.GetYaxis().GetNbins()  
787 -  
788 - # Compute scale histogram  
789 - scale = r68.Clone()  
790 - for ioff in range(noffset):  
791 - for ieng in range(neng):  
792 - integral = 2.0 * math.pi * r68.GetBinContent(ieng+1,ioff+1)  
793 - if integral > 0.0:  
794 - value = 1.0 / integral  
795 - else:  
796 - value = 0.0  
797 - scale.SetBinContent(ieng+1,ioff+1,value)  
798 -  
799 - # Set zero histogram  
800 - zero = r68.Clone()  
801 - for ioff in range(noffset):  
802 - for ieng in range(neng):  
803 - zero.SetBinContent(ieng+1,ioff+1,0.0)  
804 -  
805 - # Set boundaries  
806 - bounds = self.make_2D(r68, self.hdu_psf, None, "deg")  
807 - for b in bounds:  
808 - self.psf_bounds.append(b)  
809 - self.set_cif_keywords(self.hdu_psf, self.psf_name, \  
810 - self.psf_bounds, self.psf_desc)  
811 -  
812 - # SCALE  
813 - self.make_2D(scale, self.hdu_psf, "SCALE", "")  
814 -  
815 - # SIGMA_1  
816 - self.make_2D(r68, self.hdu_psf, "SIGMA_1", "deg")  
817 -  
818 - # AMPL_2  
819 - self.make_2D(zero, self.hdu_psf, "AMPL_2", "")  
820 -  
821 - # SIGMA_2  
822 - self.make_2D(zero, self.hdu_psf, "SIGMA_2", "deg")  
823 -  
824 - # AMPL_3  
825 - self.make_2D(zero, self.hdu_psf, "AMPL_3", "")  
826 -  
827 - # SIGMA_3  
828 - self.make_2D(zero, self.hdu_psf, "SIGMA_3", "deg")  
829 -  
830 - # Return  
831 - return  
832 -  
833 - def root2edisp(self, file):  
834 - """  
835 - Translate ROOT to CALDB energy dispersion extension. The following ROOT  
836 - histograms are used:  
837 -  
838 - ERes_offaxis -> ERES  
839 - Ebias_offaxis -> EBIAS  
840 -  
841 - Parameters:  
842 - file - ROOT file.  
843 - Keywords:  
844 - None  
845 - """  
846 - # Continue only if energy dispersion HDU has been opened  
847 - if self.hdu_edisp != None:  
848 -  
849 - # Allocate ROOT 2D array  
850 - eres = TH2F()  
851 - ebias = TH2F()  
852 - file.GetObject("ERes_offaxis", eres)  
853 - file.GetObject("Ebias_offaxis", ebias)  
854 -  
855 - # Set boundaries  
856 - bounds = self.make_2D(eres, self.hdu_edisp, None, "deg")  
857 - for b in bounds:  
858 - self.edisp_bounds.append(b)  
859 - self.set_cif_keywords(self.hdu_edisp, self.edisp_name, \  
860 - self.edisp_bounds, self.edisp_desc)  
861 -  
862 - # ERES  
863 - self.make_2D(eres, self.hdu_edisp, "ERES", "Ereco/Etrue")  
864 -  
865 - # EBIAS  
866 - self.make_2D(ebias, self.hdu_edisp, "EBIAS", "Ereco/Etrue")  
867 -  
868 - # Return  
869 - return  
870 -  
871 - def root2bgd(self, file):  
872 - """  
873 - Translate ROOT to CALDB background extension. The following ROOT  
874 - histograms are used:  
875 -  
876 - BGRatePerSqDeg_offaxis -> BGD  
877 - BGRatePerSqDeg_offaxis -> BGD_RECO  
878 -  
879 - Parameters:  
880 - file - ROOT file.  
881 - Keywords:  
882 - None  
883 - """  
884 - # Continue only if background HDU has been opened  
885 - if self.hdu_bgd != None:  
886 -  
887 - # Allocate ROOT 2D array  
888 - array = TH2F()  
889 - file.GetObject("BGRatePerSqDeg_offaxis", array)  
890 -  
891 - # Set boundaries  
892 - bounds = self.make_2D(array, self.hdu_bgd, None, "deg")  
893 - for b in bounds:  
894 - self.bgd_bounds.append(b)  
895 - self.set_cif_keywords(self.hdu_bgd, self.bgd_name, \  
896 - self.bgd_bounds, self.bgd_desc)  
897 -  
898 - # BGD  
899 - self.make_2D(array, self.hdu_bgd, "BGD", "")  
900 -  
901 - # BGD_RECO  
902 - self.make_2D(array, self.hdu_bgd, "BGD_RECO", "")  
903 -  
904 - # Return  
905 - return  
906 -  
907 - def set_cif_keywords(self, hdu, name, bounds,desc):  
908 - """  
909 - Set standard CIF keywords for extension.  
910 - """  
911 - # Set standard CIF keywords  
912 - hdu.card("CSYSNAME", "XMA_POL", "")  
913 - hdu.card("CCLS0001", self.cal_class, "Calibration class")  
914 - hdu.card("CDTP0001", self.cal_type, "Calibration type")  
915 - hdu.card("CCNM0001", name, "Calibration name")  
916 -  
917 - # Set boundary keywords  
918 - for n in range(9):  
919 - keyname = "CBD%d0001" % (n+1)  
920 - if n >= len(bounds):  
921 - value = "NONE"  
922 - else:  
923 - value = bounds[n]  
924 - hdu.card(keyname, value, "Calibration boundary")  
925 -  
926 - # Set validity keywords  
927 - hdu.card("CVSD0001", self.val_date, "Calibration validity start date (UTC)")  
928 - hdu.card("CVST0001", self.val_time, "Calibration validity start time (UTC)")  
929 - hdu.card("CDEC0001", desc, "Calibration description")  
930 -  
931 - # Return  
932 - return  
933 - 40 + """
  41 + """
  42 + def __init__(self, inst, obsid, path=""):
  43 + """
  44 + CALDB constructor. The constructor will create one CALDB entry for
  45 + an observation.
  46 +
  47 + Parameters:
  48 + inst - Instrument string.
  49 + obsid - Observation ID string.
  50 + Keywords:
  51 + path - CALDB Root path.
  52 + """
  53 + # Store root path
  54 + self.path = path
  55 +
  56 + # Initialise some members
  57 + self.cif = None
  58 + self.irf = None
  59 + self.ea = None
  60 + self.psf = None
  61 + self.edisp = None
  62 + self.bgd = None
  63 + self.hdu_cif = None
  64 +
  65 + # Store UTC date of job execution
  66 + self.utc = datetime.utcnow().isoformat()[:19]
  67 +
  68 + # Set CALDB information
  69 + self.cal_tel = "CTA"
  70 + self.cal_inst = inst.upper()
  71 + self.cal_obsid = obsid
  72 + self.cal_det = "NONE"
  73 + self.cal_flt = "NONE"
  74 + self.cal_class = "BCF"
  75 + self.cal_type = "DATA"
  76 + self.cal_qual = 0 # 0=good, 1=bad, 2=dubious, ...
  77 + self.cal_date = "11/01/01"
  78 + self.val_date = "2011-01-01"
  79 + self.val_time = "00:00:00"
  80 + self.ref_time = 51544.0
  81 + self.cal_version = "VERSION(DC1)"
  82 + self.cal_cut = "CLASS(BEST)"
  83 + self.cal_analysis = "ANALYSIS(IFAE)"
  84 + #
  85 + self.ea_name = "EFF_AREA"
  86 + self.ea_doc = "CAL/GEN/92-019"
  87 + self.ea_bounds = [self.cal_version, self.cal_cut, self.cal_analysis]
  88 + self.ea_desc = "CTA effective area"
  89 + #
  90 + self.psf_name = "RPSF"
  91 + self.psf_doc = "CAL/GEN/92-020"
  92 + self.psf_bounds = [self.cal_version, self.cal_cut, self.cal_analysis]
  93 + self.psf_desc = "CTA point spread function"
  94 + #
  95 + self.edisp_name = "EDISP"
  96 + self.edisp_doc = "???"
  97 + self.edisp_bounds = [self.cal_version, self.cal_cut, self.cal_analysis]
  98 + self.edisp_desc = "CTA energy dispersion"
  99 + #
  100 + self.bgd_name = "BGD"
  101 + self.bgd_doc = "???"
  102 + self.bgd_bounds = [self.cal_version, self.cal_cut, self.cal_analysis]
  103 + self.bgd_desc = "CTA background"
  104 +
  105 + # Create directory structure
  106 + self.make_dirs()
  107 +
  108 + # Return
  109 + return
  110 +
  111 + def __del__(self):
  112 + """
  113 + Destructor. Close all FITS files.
  114 + """
  115 + # Close calibration
  116 + self.close()
  117 +
  118 + # Return
  119 + return
  120 +
  121 + def make_dirs(self):
  122 + """
  123 + Generate CALDB directory structure for one observation identifier.
  124 + The structure is given by
  125 +
  126 + data/<tel>/<inst>/bcf/<obsid>
  127 +
  128 + where <tel> is "cta" and <inst> is the instrument specified in the
  129 + CALDB constructor (the instrument may be used for different array
  130 + configurations).
  131 +
  132 + Parameters:
  133 + None
  134 + Keywords:
  135 + None
  136 + """
  137 + # Set base path
  138 + self.base_dir = "data"
  139 + self.base_dir += "/"+self.cal_tel.lower()
  140 + self.base_dir += "/"+self.cal_inst.lower()
  141 +
  142 + # Set directory names
  143 + self.bcf_dir = self.base_dir+"/bcf"
  144 + self.obs_dir = self.bcf_dir+"/"+self.cal_obsid
  145 + self.ea_dir = self.obs_dir
  146 + self.psf_dir = self.obs_dir
  147 + self.edisp_dir = self.obs_dir
  148 + self.bgd_dir = self.obs_dir
  149 +
  150 + # Optionally prefix path
  151 + if len(self.path) > 0:
  152 + self.base_path = self.path+"/"+self.base_dir
  153 + self.bcf_path = self.path+"/"+self.bcf_dir
  154 + self.obs_path = self.path+"/"+self.obs_dir
  155 + self.ea_path = self.path+"/"+self.ea_dir
  156 + self.psf_path = self.path+"/"+self.psf_dir
  157 + self.edisp_path = self.path+"/"+self.edisp_dir
  158 + self.bgd_path = self.path+"/"+self.bgd_dir
  159 + else:
  160 + self.base_path = self.base_dir
  161 + self.bcf_path = self.bcf_dir
  162 + self.obs_path = self.obs_dir
  163 + self.ea_path = self.ea_dir
  164 + self.psf_path = self.psf_dir
  165 + self.edisp_path = self.edisp_dir
  166 + self.bgd_path = self.bgd_dir
  167 +
  168 + # Create directory structure
  169 + if not os.path.isdir(self.ea_path):
  170 + os.makedirs(self.ea_path)
  171 + if not os.path.isdir(self.psf_path):
  172 + os.makedirs(self.psf_path)
  173 + if not os.path.isdir(self.edisp_path):
  174 + os.makedirs(self.edisp_path)
  175 + if not os.path.isdir(self.bgd_path):
  176 + os.makedirs(self.bgd_path)
  177 +
  178 + # Return
  179 + return
  180 +
  181 + def open(self, version, split=False, clobber=True):
  182 + """
  183 + Open existing or create new calibration. The actual version will put
  184 + all calibrations in the same file, although each part of the response
  185 + function will have its own logical name. We can thus easily modify
  186 + the script to put each calibration information in a separate file.
  187 +
  188 + Parameters:
  189 + version - Filename version
  190 + Keywords:
  191 + split - Split IRF over several files?
  192 + clobber - Overwrite existing files?
  193 + """
  194 + # Set calibrate file names
  195 + if split:
  196 + self.ea_file = "ea_"+version+".fits"
  197 + self.psf_file = "psf_"+version+".fits"
  198 + self.edisp_file = "edisp_"+version+".fits"
  199 + self.bgd_file = "bgd_"+version+".fits"
  200 + else:
  201 + self.ea_file = "irf_"+version+".fits"
  202 + self.psf_file = "irf_"+version+".fits"
  203 + self.edisp_file = "irf_"+version+".fits"
  204 + self.bgd_file = "irf_"+version+".fits"
  205 +
  206 + # Open calibration database index
  207 + self.cif = GFits(self.base_path+"/caldb.indx", True)
  208 +
  209 + # If file has no CIF extension than create it now
  210 + try:
  211 + self.hdu_cif = self.cif.table("CIF")
  212 + except:
  213 + self.create_cif()
  214 + self.hdu_cif = self.cif.table("CIF")
  215 +
  216 + # Set filenames
  217 + ea_filename = self.ea_path+"/"+self.ea_file
  218 + psf_filename = self.psf_path+"/"+self.psf_file
  219 + edisp_filename = self.edisp_path+"/"+self.edisp_file
  220 + bgd_filename = self.bgd_path+"/"+self.bgd_file
  221 +
  222 + # Open files
  223 + if split:
  224 + self.ea = GFits(ea_filename, True)
  225 + self.psf = GFits(psf_filename, True)
  226 + self.edisp = GFits(edisp_filename, True)
  227 + self.bgd = GFits(bgd_filename, True)
  228 + else:
  229 + self.irf = GFits(ea_filename, True)
  230 + self.ea = self.irf
  231 + self.psf = self.irf
  232 + self.edisp = self.irf
  233 + self.bgd = self.irf
  234 +
  235 + # Open HDUs
  236 + self.open_ea()
  237 + self.open_psf()
  238 + self.open_edisp()
  239 + self.open_bgd()
  240 +
  241 + # Return
  242 + return
  243 +
  244 + def close(self):
  245 + """
  246 + Close calibration FITS files.
  247 +
  248 + Parameters:
  249 + None
  250 + Keywords:
  251 + None
  252 + """
  253 + # Add information to CIF. We do this now as before we did not
  254 + # necessarily have all the information at hand (in particular
  255 + # about the boundaries)
  256 + self.add_cif_info()
  257 +
  258 + # Close CIF
  259 + if self.cif != None:
  260 + self.cif.save(True)
  261 + self.cif.close()
  262 + self.cif = None
  263 +
  264 + # If IRF file exists then close it now. All file pointers will
  265 + # be set to None
  266 + if self.irf != None:
  267 + self.irf.save(True)
  268 + self.irf.close()
  269 + self.irf = None
  270 + self.ea = None
  271 + self.psf = None
  272 + self.edisp = None
  273 + self.bgd = None
  274 +
  275 + # ... otherwise we have split files, so we have to close them
  276 + # all
  277 + else:
  278 +
  279 + # Close effective area file
  280 + if self.ea != None:
  281 + self.ea.save(True)
  282 + self.ea.close()
  283 + self.ea = None
  284 +
  285 + # Close point spread function file
  286 + if self.psf != None:
  287 + self.psf.save(True)
  288 + self.psf.close()
  289 + self.psf = None
  290 +
  291 + # Close energy dispersion file
  292 + if self.edisp != None:
  293 + self.edisp.save(True)
  294 + self.edisp.close()
  295 + self.edisp = None
  296 +
  297 + # Close background file
  298 + if self.bgd != None:
  299 + self.bgd.save(True)
  300 + self.bgd.close()
  301 + self.bgd = None
  302 +
  303 + # Return
  304 + return
  305 +
  306 + def create_cif(self):
  307 + """
  308 + Create Calibration Index File (CIF) extension in CIF FITS file.
  309 +
  310 + Parameters:
  311 + None
  312 + Keywords:
  313 + None
  314 + """
  315 + # Create binary table
  316 + table = GFitsBinTable()
  317 +
  318 + # Set boundary
  319 +
  320 + # Attach columns. Reference: CAL/GEN/92-008
  321 + table.append(GFitsTableStringCol("TELESCOP", 0, 10))
  322 + table.append(GFitsTableStringCol("INSTRUME", 0, 10))
  323 + table.append(GFitsTableStringCol("DETNAM", 0, 20))
  324 + table.append(GFitsTableStringCol("FILTER", 0, 10))
  325 + table.append(GFitsTableStringCol("CAL_DEV", 0, 20))
  326 + table.append(GFitsTableStringCol("CAL_DIR", 0, 70))
  327 + table.append(GFitsTableStringCol("CAL_FILE", 0, 40))
  328 + table.append(GFitsTableStringCol("CAL_CLAS", 0, 3))
  329 + table.append(GFitsTableStringCol("CAL_DTYP", 0, 4))
  330 + table.append(GFitsTableStringCol("CAL_CNAM", 0, 20))
  331 + table.append(GFitsTableStringCol("CAL_CBD", 0, 70, 9))
  332 + table.append(GFitsTableShortCol("CAL_XNO", 0))
  333 + table.append(GFitsTableStringCol("CAL_VSD", 0, 10))
  334 + table.append(GFitsTableStringCol("CAL_VST", 0, 8))
  335 + table.append(GFitsTableDoubleCol("REF_TIME", 0))
  336 + table.append(GFitsTableShortCol("CAL_QUAL", 0))
  337 + table.append(GFitsTableStringCol("CAL_DATE", 0, 8))
  338 + table.append(GFitsTableStringCol("CAL_DESC", 0, 70))
  339 +
  340 + # Set keywords. Reference: CAL/GEN/92-008
  341 + table.extname("CIF")
  342 + table.card("CIFVERSN", "1992a", "Version of CIF format")
  343 +
  344 + # Attach table to FITS file. Note that at this moment the
  345 + # FITS table gets connected to the FITS file. Yet since
  346 + # nothing was yet written to the FITS file, we cannot read
  347 + # anything from it.
  348 + self.cif.append(table)
  349 +
  350 + # Return
  351 + return
  352 +
  353 + def add_cif_info(self):
  354 + """
  355 + Add information to CIF extension.
  356 +
  357 + Parameters:
  358 + None
  359 + Keywords:
  360 + None
  361 + """
  362 + # Append 3 rows to CIF extension
  363 + self.hdu_cif.append_rows(3)
  364 +
  365 + # Add generic information for these 3 rows
  366 + for i in range(3):
  367 +
  368 + # Set row number
  369 + row = i+self.hdu_cif.nrows()-3
  370 +
  371 + # Set element
  372 + self.hdu_cif["TELESCOP"][row] = self.cal_tel
  373 + self.hdu_cif["INSTRUME"][row] = self.cal_inst
  374 + self.hdu_cif["DETNAM"][row] = self.cal_det
  375 + self.hdu_cif["FILTER"][row] = self.cal_flt
  376 + self.hdu_cif["CAL_DEV"][row] = "ONLINE"
  377 + self.hdu_cif["CAL_CLAS"][row] = self.cal_class
  378 + self.hdu_cif["CAL_DTYP"][row] = self.cal_type
  379 + self.hdu_cif["CAL_VSD"][row] = self.val_date
  380 + self.hdu_cif["CAL_VST"][row] = self.val_time
  381 + self.hdu_cif["REF_TIME"][row] = self.ref_time
  382 + self.hdu_cif["CAL_QUAL"][row] = self.cal_qual
  383 + self.hdu_cif["CAL_DATE"][row] = self.cal_date
  384 +
  385 + # Add effective area information
  386 + row = self.hdu_cif.nrows()-3
  387 + self.hdu_cif["CAL_DIR"][row] = self.ea_dir
  388 + self.hdu_cif["CAL_FILE"][row] = self.ea_file
  389 + self.hdu_cif["CAL_CNAM"][row] = self.ea_name
  390 + self.hdu_cif["CAL_DESC"][row] = self.ea_desc
  391 + self.hdu_cif["CAL_XNO"][row] = 1
  392 + for n in range(9):
  393 + if n >= len(self.ea_bounds):
  394 + self.hdu_cif["CAL_CBD"][row, n] = "NONE"
  395 + else:
  396 + self.hdu_cif["CAL_CBD"][row, n] = self.ea_bounds[n]
  397 +
  398 + # Add point spread function information
  399 + row = self.hdu_cif.nrows()-2
  400 + self.hdu_cif["CAL_DIR"][row] = self.psf_dir
  401 + self.hdu_cif["CAL_FILE"][row] = self.psf_file
  402 + self.hdu_cif["CAL_CNAM"][row] = self.psf_name
  403 + self.hdu_cif["CAL_DESC"][row] = self.psf_desc
  404 + self.hdu_cif["CAL_XNO"][row] = 1
  405 + for n in range(9):
  406 + if n >= len(self.psf_bounds):
  407 + self.hdu_cif["CAL_CBD"][row, n] = "NONE"
  408 + else:
  409 + self.hdu_cif["CAL_CBD"][row, n] = self.psf_bounds[n]
  410 +
  411 + # Add energy dispersion information
  412 + row = self.hdu_cif.nrows()-1
  413 + self.hdu_cif["CAL_DIR"][row] = self.edisp_dir
  414 + self.hdu_cif["CAL_FILE"][row] = self.edisp_file
  415 + self.hdu_cif["CAL_CNAM"][row] = self.edisp_name
  416 + self.hdu_cif["CAL_DESC"][row] = self.edisp_desc
  417 + self.hdu_cif["CAL_XNO"][row] = 1
  418 + for n in range(9):
  419 + if n >= len(self.edisp_bounds):
  420 + self.hdu_cif["CAL_CBD"][row, n] = "NONE"
  421 + else:
  422 + self.hdu_cif["CAL_CBD"][row, n] = self.edisp_bounds[n]
  423 +
  424 + # Add background information
  425 + row = self.hdu_cif.nrows()-1
  426 + self.hdu_cif["CAL_DIR"][row] = self.bgd_dir
  427 + self.hdu_cif["CAL_FILE"][row] = self.bgd_file
  428 + self.hdu_cif["CAL_CNAM"][row] = self.bgd_name
  429 + self.hdu_cif["CAL_DESC"][row] = self.bgd_desc
  430 + self.hdu_cif["CAL_XNO"][row] = 1
  431 + for n in range(9):
  432 + if n >= len(self.bgd_bounds):
  433 + self.hdu_cif["CAL_CBD"][row, n] = "NONE"
  434 + else:
  435 + self.hdu_cif["CAL_CBD"][row, n] = self.bgd_bounds[n]
  436 +
  437 + # Return
  438 + return
  439 +
  440 + def open_ea(self):
  441 + """
  442 + Open effective area extension. If no extension was found
  443 + then create one. We do not yet set any data as we don't
  444 + know the dimensions of the table yet.
  445 +
  446 + Parameters:
  447 + None
  448 + Keywords:
  449 + None
  450 + """
  451 + # Get extension. If it does not exist then create it now.
  452 + try:
  453 + self.hdu_ea = self.ea.table("EFFECTIVE AREA")
  454 + except:
  455 + # Create binary table
  456 + table = GFitsBinTable()
  457 +
  458 + # Set extension name
  459 + table.extname("EFFECTIVE AREA")
  460 +
  461 + # Set OGIP keywords
  462 + self.set_ogip_keywords(table, self.ea_doc, \
  463 + ["RESPONSE", self.ea_name])
  464 +
  465 + # Append table
  466 + self.ea.append(table)
  467 +
  468 + # Get extension
  469 + self.hdu_ea = self.ea.table("EFFECTIVE AREA")
  470 +
  471 + # Return
  472 + return
  473 +
  474 + def open_psf(self):
  475 + """
  476 + Open point spread function extension. If no extension was
  477 + found then create one. We do not yet set any data as we don't
  478 + know the dimensions of the table yet.
  479 +
  480 + Parameters:
  481 + None
  482 + Keywords:
  483 + None
  484 + """
  485 + # Get extension. If it does not exist then create it now.
  486 + try:
  487 + self.hdu_psf = self.psf.table("POINT SPREAD FUNCTION")
  488 + except:
  489 + # Create binary table
  490 + table = GFitsBinTable()
  491 +
  492 + # Set extension name
  493 + table.extname("POINT SPREAD FUNCTION")
  494 +
  495 + # Set OGIP keywords
  496 + self.set_ogip_keywords(table, self.psf_doc, \
  497 + ["RESPONSE", self.psf_name])
  498 +
  499 + # Append table
  500 + self.psf.append(table)
  501 +
  502 + # Get extension
  503 + self.hdu_psf = self.psf.table("POINT SPREAD FUNCTION")
  504 +
  505 + # Return
  506 + return
  507 +
  508 + def open_edisp(self):
  509 + """
  510 + Open energy dispersion extension. If no extension was found
  511 + then create one. We do not yet set any data as we don't
  512 + know the dimensions of the table yet.
  513 +
  514 + Parameters:
  515 + None
  516 + Keywords:
  517 + None
  518 + """
  519 + # Get extension. If it does not exist then create it now.
  520 + try:
  521 + self.hdu_edisp = self.edisp.table("ENERGY DISPERSION")
  522 + except:
  523 + # Create binary table
  524 + table = GFitsBinTable()
  525 +
  526 + # Set extension name
  527 + table.extname("ENERGY DISPERSION")
  528 +
  529 + # Set OGIP keywords
  530 + self.set_ogip_keywords(table, self.edisp_doc, \
  531 + ["RESPONSE", self.edisp_name])
  532 +
  533 + # Append table
  534 + self.edisp.append(table)
  535 +
  536 + # Get extension
  537 + self.hdu_edisp = self.edisp.table("ENERGY DISPERSION")
  538 +
  539 + # Return
  540 + return
  541 +
  542 + def open_bgd(self):
  543 + """
  544 + Open background extension. If no extension was found
  545 + then create one. We do not yet set any data as we don't
  546 + know the dimensions of the table yet.
  547 +
  548 + Parameters:
  549 + None
  550 + Keywords:
  551 + None
  552 + """
  553 + # Get extension. If it does not exist then create it now.
  554 + try:
  555 + self.hdu_bgd = self.bgd.table("BACKGROUND")
  556 + except:
  557 + # Create binary table
  558 + table = GFitsBinTable()
  559 +
  560 + # Set extension name
  561 + table.extname("BACKGROUND")
  562 +
  563 + # Set OGIP keywords
  564 + self.set_ogip_keywords(table, self.bgd_doc, \
  565 + ["RESPONSE", self.bgd_name])
  566 +
  567 + # Append table
  568 + self.bgd.append(table)
  569 +
  570 + # Get extension
  571 + self.hdu_bgd = self.bgd.table("BACKGROUND")
  572 +
  573 + # Return
  574 + return
  575 +
  576 + def set_ogip_keywords(self, hdu, hdudoc, hduclas):
  577 + """
  578 + Set standard OGIP keywords for extension.
  579 +
  580 + Parameters:
  581 + hdu - Header Data Unit.
  582 + hdudoc - Documentation reference string
  583 + hduclas - Array of HDUCLAS fields
  584 + Keywords:
  585 + None
  586 + """
  587 + # Set keywords
  588 + hdu.card("ORIGIN", "IRAP", "Name of organization making this file")
  589 + hdu.card("DATE", self.utc, "File creation date (YYYY-MM-DDThh:mm:ss UTC)")
  590 + hdu.card("TELESCOP", self.cal_tel, "Name of telescope")
  591 + hdu.card("INSTRUME", self.cal_inst, "Name of instrument")
  592 + hdu.card("DETNAM", self.cal_det, "Name of detector")
  593 + hdu.card("HDUCLASS", "OGIP", "")
  594 + hdu.card("HDUDOC", hdudoc, "")
  595 + for i, item in enumerate(hduclas):
  596 + key = "HDUCLAS%d" % (i+1)
  597 + hdu.card(key, item, "")
  598 + hdu.card("HDUVERS", "1.0.0", "")
  599 +
  600 + # Return
  601 + return
  602 +
  603 + def make_2D(self, array, hdu, name, unit, scale=1.0):
  604 + """
  605 + Make 2D matrix as function of energy and offset angle from a
  606 + ROOT 2D histogram. If the HDU has already the energy and
  607 + offset angle columns, this method will simply add another data
  608 + column.
  609 + If name==None, the method will not append any data column.
  610 +
  611 + Parameters:
  612 + array - ROOT 2D histogram.
  613 + hdu - FITS HDU.
  614 + name - Data column name.
  615 + unit - Data unit.
  616 + Keywords:
  617 + scale - Scaling factor for histogram values.
  618 + """
  619 + # Extract energy and offset angle vectors
  620 + energies = array.GetXaxis()
  621 + offsets = array.GetYaxis()
  622 + neng = energies.GetNbins()
  623 + noffset = offsets.GetNbins()
  624 +
  625 + # Attach columns to HDU. We do this only if they do not yet
  626 + # exist. This allows adding columns to the same HDU in successive
  627 + # calls of this method
  628 + #
  629 + # ENERG_LO
  630 + if not hdu.contains("ENERG_LO"):
  631 + hdu.append(GFitsTableFloatCol("ENERG_LO", 1, neng))
  632 + hdu["ENERG_LO"].unit("TeV")
  633 + for ieng in range(neng):
  634 + e_lo = pow(10.0, energies.GetBinLowEdge(ieng+1))
  635 + hdu["ENERG_LO"][0,ieng] = e_lo
  636 + #
  637 + # ENERG_HI
  638 + if not hdu.contains("ENERG_HI"):
  639 + hdu.append(GFitsTableFloatCol("ENERG_HI", 1, neng))
  640 + hdu["ENERG_HI"].unit("TeV")
  641 + for ieng in range(neng):
  642 + e_hi = pow(10.0, energies.GetBinUpEdge(ieng+1))
  643 + hdu["ENERG_HI"][0,ieng] = e_hi
  644 + #
  645 + # THETA_LO
  646 + if not hdu.contains("THETA_LO"):
  647 + hdu.append(GFitsTableFloatCol("THETA_LO", 1, noffset))
  648 + hdu["THETA_LO"].unit("deg")
  649 + for ioff in range(noffset):
  650 + o_lo = offsets.GetBinLowEdge(ioff+1)
  651 + hdu["THETA_LO"][0,ioff] = o_lo
  652 +
  653 + #
  654 + # THETA_LO
  655 + if not hdu.contains("THETA_HI"):
  656 + hdu.append(GFitsTableFloatCol("THETA_HI", 1, noffset))
  657 + hdu["THETA_HI"].unit("deg")
  658 + for ioff in range(noffset):
  659 + o_hi = offsets.GetBinUpEdge(ioff+1)
  660 + hdu["THETA_HI"][0,ioff] = o_hi
  661 + #
  662 + # "NAME"
  663 + if name != None and not hdu.contains(name):
  664 + hdu.append(GFitsTableFloatCol(name, 1, neng*noffset))
  665 + hdu[name].unit(unit)
  666 + hdu[name].dim([neng, noffset])
  667 + for ioff in range(noffset):
  668 + for ieng in range(neng):
  669 + index = ieng + ioff * neng
  670 + value = array.GetBinContent(ieng+1,ioff+1)
  671 + hdu[name][0,index] = value * scale
  672 +
  673 + # Collect boundary information
  674 + bd_eng = "ENERG(%.4f-%.2f)TeV" % \
  675 + (pow(10.0, energies.GetBinLowEdge(1)), \
  676 + pow(10.0, energies.GetBinUpEdge(neng)))
  677 + bd_off = "THETA(%.2f-%.2f)deg" % \
  678 + (offsets.GetBinLowEdge(1), \
  679 + offsets.GetBinUpEdge(noffset))
  680 + bd_phi = "PHI(0-360)deg"
  681 + bounds = [bd_eng, bd_off, bd_phi]
  682 +
  683 + # Return boundary information
  684 + return bounds
  685 +
  686 + def root2caldb(self, filename):
  687 + """
  688 + Translate ROOT to CALDB information.
  689 +
  690 + Parameters:
  691 + filename - ROOT 2D performance filename.
  692 + """
  693 + # Open ROOT performance file
  694 + file = TFile(filename)
  695 +
  696 + # Create effective area
  697 + self.root2ea(file)
  698 +
  699 + # Create point spread function
  700 + self.root2psf(file)
  701 +
  702 + # Create energy dispersion
  703 + self.root2edisp(file)
  704 +
  705 + # Create background
  706 + self.root2bgd(file)
  707 +
  708 + # Return
  709 + return
  710 +
  711 + def root2ea(self, file):
  712 + """
  713 + Translate ROOT to CALDB effective area extension. The following ROOT
  714 + histograms are used:
  715 +
  716 + EffectiveAreaEtrue_offaxis -> EFFAREA
  717 + EffectiveArea_offaxis -> EFFAREA_RECO
  718 +
  719 + Parameters:
  720 + file - ROOT file.
  721 + Keywords:
  722 + None
  723 + """
  724 + # Continue only if effective area HDU has been opened
  725 + if self.hdu_ea != None:
  726 +
  727 + # Allocate ROOT 2D arrays
  728 + etrue = TH2F()
  729 + ereco = TH2F()
  730 + file.GetObject("EffectiveAreaEtrue_offaxis", etrue)
  731 + file.GetObject("EffectiveArea_offaxis", ereco)
  732 + #print etrue.GetXaxis().GetBinLowEdge(1), etrue.GetXaxis().GetBinUpEdge(etrue.GetXaxis().GetNbins())
  733 + #print ereco.GetXaxis().GetBinLowEdge(1), ereco.GetXaxis().GetBinUpEdge(ereco.GetXaxis().GetNbins())
  734 +
  735 + # Rebin etrue histogram
  736 + etrue.RebinX(10)
  737 + neng = etrue.GetXaxis().GetNbins()
  738 + noffset = etrue.GetYaxis().GetNbins()
  739 + for ioff in range(noffset):
  740 + for ieng in range(neng):
  741 + value = etrue.GetBinContent(ieng+1,ioff+1) / 10.0
  742 + etrue.SetBinContent(ieng+1,ioff+1,value)
  743 +
  744 + # Write boundaries (use Ereco boundaries)
  745 + bounds = self.make_2D(ereco, self.hdu_ea, None, "m2")
  746 + for b in bounds:
  747 + self.ea_bounds.append(b)
  748 + self.set_cif_keywords(self.hdu_ea, self.ea_name, \
  749 + self.ea_bounds, self.ea_desc)
  750 +
  751 + # EFFAREA
  752 + self.make_2D(etrue, self.hdu_ea, "EFFAREA", "m2")
  753 +
  754 + # EFFAREA_RECO
  755 + self.make_2D(ereco, self.hdu_ea, "EFFAREA_RECO", "m2")
  756 +
  757 + # Return
  758 + return
  759 +
  760 + def root2psf(self, file):
  761 + """
  762 + Translate ROOT to CALDB point spread function extension. The following
  763 + ROOT histograms are used:
  764 +
  765 + 1/(2*pi*SIGMA_1) -> SCALE
  766 + AngRes_offaxis -> SIGMA_1 (scaling: 1/0.8)
  767 + 0.0 -> AMPL_2
  768 + 0.0 -> SIGMA_2
  769 + 0.0 -> AMPL_3
  770 + 0.0 -> SIGMA_3
  771 +
  772 + Parameters:
  773 + file - ROOT file.
  774 + Keywords:
  775 + None
  776 + """
  777 + # Continue only if point spread function HDU has been opened
  778 + if self.hdu_psf != None:
  779 +
  780 + # Allocate ROOT 2D array
  781 + r68 = TH2F()
  782 +
  783 + # Read 68% containment histogram
  784 + file.GetObject("AngRes_offaxis", r68)
  785 + neng = r68.GetXaxis().GetNbins()
  786 + noffset = r68.GetYaxis().GetNbins()
  787 +
  788 + # Compute scale histogram
  789 + scale = r68.Clone()
  790 + for ioff in range(noffset):
  791 + for ieng in range(neng):
  792 + integral = 2.0 * math.pi * r68.GetBinContent(ieng+1,ioff+1)
  793 + if integral > 0.0:
  794 + value = 1.0 / integral
  795 + else:
  796 + value = 0.0
  797 + scale.SetBinContent(ieng+1,ioff+1,value)
  798 +
  799 + # Set zero histogram
  800 + zero = r68.Clone()
  801 + for ioff in range(noffset):
  802 + for ieng in range(neng):
  803 + zero.SetBinContent(ieng+1,ioff+1,0.0)
  804 +
  805 + # Set boundaries
  806 + bounds = self.make_2D(r68, self.hdu_psf, None, "deg")
  807 + for b in bounds:
  808 + self.psf_bounds.append(b)
  809 + self.set_cif_keywords(self.hdu_psf, self.psf_name, \
  810 + self.psf_bounds, self.psf_desc)
  811 +
  812 + # SCALE
  813 + self.make_2D(scale, self.hdu_psf, "SCALE", "")
  814 +
  815 + # SIGMA_1
  816 + self.make_2D(r68, self.hdu_psf, "SIGMA_1", "deg")
  817 +
  818 + # AMPL_2
  819 + self.make_2D(zero, self.hdu_psf, "AMPL_2", "")
  820 +
  821 + # SIGMA_2
  822 + self.make_2D(zero, self.hdu_psf, "SIGMA_2", "deg")
  823 +
  824 + # AMPL_3
  825 + self.make_2D(zero, self.hdu_psf, "AMPL_3", "")
  826 +
  827 + # SIGMA_3
  828 + self.make_2D(zero, self.hdu_psf, "SIGMA_3", "deg")
  829 +
  830 + # Return
  831 + return
  832 +
  833 + def root2edisp(self, file):
  834 + """
  835 + Translate ROOT to CALDB energy dispersion extension. The following ROOT
  836 + histograms are used:
  837 +
  838 + ERes_offaxis -> ERES
  839 + Ebias_offaxis -> EBIAS
  840 +
  841 + Parameters:
  842 + file - ROOT file.
  843 + Keywords:
  844 + None
  845 + """
  846 + # Continue only if energy dispersion HDU has been opened
  847 + if self.hdu_edisp != None:
  848 +
  849 + # Allocate ROOT 2D array
  850 + eres = TH2F()
  851 + ebias = TH2F()
  852 + file.GetObject("ERes_offaxis", eres)
  853 + file.GetObject("Ebias_offaxis", ebias)
  854 +
  855 + # Set boundaries
  856 + bounds = self.make_2D(eres, self.hdu_edisp, None, "deg")
  857 + for b in bounds:
  858 + self.edisp_bounds.append(b)
  859 + self.set_cif_keywords(self.hdu_edisp, self.edisp_name, \
  860 + self.edisp_bounds, self.edisp_desc)
  861 +
  862 + # ERES
  863 + self.make_2D(eres, self.hdu_edisp, "ERES", "Ereco/Etrue")
  864 +
  865 + # EBIAS
  866 + self.make_2D(ebias, self.hdu_edisp, "EBIAS", "Ereco/Etrue")
  867 +
  868 + # Return
  869 + return
  870 +
  871 + def root2bgd(self, file):
  872 + """
  873 + Translate ROOT to CALDB background extension. The following ROOT
  874 + histograms are used:
  875 +
  876 + BGRatePerSqDeg_offaxis -> BGD
  877 + BGRatePerSqDeg_offaxis -> BGD_RECO
  878 +
  879 + Parameters:
  880 + file - ROOT file.
  881 + Keywords:
  882 + None
  883 + """
  884 + # Continue only if background HDU has been opened
  885 + if self.hdu_bgd != None:
  886 +
  887 + # Allocate ROOT 2D array
  888 + array = TH2F()
  889 + file.GetObject("BGRatePerSqDeg_offaxis", array)
  890 +
  891 + # Set boundaries
  892 + bounds = self.make_2D(array, self.hdu_bgd, None, "deg")
  893 + for b in bounds:
  894 + self.bgd_bounds.append(b)
  895 + self.set_cif_keywords(self.hdu_bgd, self.bgd_name, \
  896 + self.bgd_bounds, self.bgd_desc)
  897 +
  898 + # BGD
  899 + self.make_2D(array, self.hdu_bgd, "BGD", "")
  900 +
  901 + # BGD_RECO
  902 + self.make_2D(array, self.hdu_bgd, "BGD_RECO", "")
  903 +
  904 + # Return
  905 + return
  906 +
  907 + def set_cif_keywords(self, hdu, name, bounds,desc):
  908 + """
  909 + Set standard CIF keywords for extension.
  910 + """
  911 + # Set standard CIF keywords
  912 + hdu.card("CSYSNAME", "XMA_POL", "")
  913 + hdu.card("CCLS0001", self.cal_class, "Calibration class")
  914 + hdu.card("CDTP0001", self.cal_type, "Calibration type")
  915 + hdu.card("CCNM0001", name, "Calibration name")
  916 +
  917 + # Set boundary keywords
  918 + for n in range(9):
  919 + keyname = "CBD%d0001" % (n+1)
  920 + if n >= len(bounds):
  921 + value = "NONE"
  922 + else:
  923 + value = bounds[n]
  924 + hdu.card(keyname, value, "Calibration boundary")
  925 +
  926 + # Set validity keywords
  927 + hdu.card("CVSD0001", self.val_date, "Calibration validity start date (UTC)")
  928 + hdu.card("CVST0001", self.val_time, "Calibration validity start time (UTC)")
  929 + hdu.card("CDEC0001", desc, "Calibration description")
  930 +
  931 + # Return
  932 + return
  933 +
934 934
935 #==========================# 935 #==========================#
936 # Main routine entry point # 936 # Main routine entry point #
937 #==========================# 937 #==========================#
938 if __name__ == '__main__': 938 if __name__ == '__main__':
939 - """  
940 - This script translates a CTA ROOT 2D performance files in a CALDB compliant  
941 - CTA response. The script will create the response for 3 observations to  
942 - illustrate the run-wise file structure.  
943 -  
944 - The CTA ROOT 2D performance files contain the following histograms:  
945 - - DiffSens_offaxis (2D) - Differential sensitivity  
946 - - EffectiveArea_offaxis (2D) - Effective area (full containment?, reco energy?)  
947 - - EffectiveArea80_offaxis (2D) - Effective area for 80% source containment  
948 - - AngRes_offaxis (2D) - Angular resolution 68% containment  
949 - - AngRes80_offaxis (2D) - Angular resolution 80% containment  
950 - - BGRatePerSqDeg_offaxis (2D) - Background rate per square degree  
951 - - BGRate_offaxis (2D) - Background rate  
952 - - EffectiveAreaEtrue_offaxis (2D) - Effective area in true energy (finer bins)  
953 - - MigMatrix_offaxis (3D)  
954 - - EestOverEtrue_offaxis (3D)  
955 - - ERes_offaxis (2D) - Energy Resolution  
956 - - Ebias_offaxis (2D) - Energy bias  
957 - """  
958 - # Set ROOT filename  
959 - path = "/Users/jurgen/Documents/Travail/Projects/CTA/WP-MC/root/IFAEOffaxisPerformanceBEI_May2012"  
960 - irf = "SubarrayE_IFAE_50hours_20120510_offaxis.root"  
961 - filename = path+"/"+irf  
962 -  
963 - # Set observation identifiers  
964 - obsids = ["000001", "000002", "000003"]  
965 -  
966 - # Generate CALDB entries, one for each observation (or run)  
967 - for obsid in obsids:  
968 -  
969 - # Allocate caldb  
970 - db = caldb("E", obsid)  
971 -  
972 - # Open calibration  
973 - db.open("test")  
974 -  
975 - # Translate ROOT to CALDB information  
976 - db.root2caldb(filename)  
977 -  
978 \ No newline at end of file 939 \ No newline at end of file
  940 + """
  941 + This script translates a CTA ROOT 2D performance files into a CALDB
  942 + compliant CTA response. The script will create the response for 3
  943 + observations to illustrate the run-wise file structure.
  944 +
  945 + The CTA ROOT 2D performance files contain the following histograms:
  946 + - DiffSens_offaxis (2D) - Differential sensitivity
  947 + - EffectiveArea_offaxis (2D) - Effective area (full containment?, reco energy?)
  948 + - EffectiveArea80_offaxis (2D) - Effective area for 80% source containment
  949 + - AngRes_offaxis (2D) - Angular resolution 68% containment
  950 + - AngRes80_offaxis (2D) - Angular resolution 80% containment
  951 + - BGRatePerSqDeg_offaxis (2D) - Background rate per square degree
  952 + - BGRate_offaxis (2D) - Background rate
  953 + - EffectiveAreaEtrue_offaxis (2D) - Effective area in true energy (finer bins)
  954 + - MigMatrix_offaxis (3D)
  955 + - EestOverEtrue_offaxis (3D)
  956 + - ERes_offaxis (2D) - Energy Resolution
  957 + - Ebias_offaxis (2D) - Energy bias
  958 + """
  959 + # Set ROOT filename
  960 + path = "/Users/jurgen/Documents/Travail/Projects/CTA/WP-MC/root/IFAEOffaxisPerformanceBEI_May2012"
  961 + irf = "SubarrayE_IFAE_50hours_20120510_offaxis.root"
  962 + filename = path+"/"+irf
  963 +
  964 + # Set observation identifiers
  965 + #obsids = ["000001", "000002", "000003"]
  966 + obsids = ["000001"]
  967 +
  968 + # Generate CALDB entries, one for each observation (or run)
  969 + for obsid in obsids:
  970 +
  971 + # Allocate caldb
  972 + db = caldb("E", obsid)
  973 +
  974 + # Open calibration
  975 + db.open("test")
  976 +
  977 + # Translate ROOT to CALDB information
  978 + db.root2caldb(filename)
  979 +
979 \ No newline at end of file 980 \ No newline at end of file