Commit 013a064ea8b9ba4922bc1c73eebb9a3336a9b169
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 |